[insubstantial] 03/08: Imported Upstream version 7.3+dfsg1
Felix Natter
fnatter-guest at moszumanska.debian.org
Sat Jan 16 14:43:50 UTC 2016
This is an automated email from the git hooks/post-receive script.
fnatter-guest pushed a commit to branch master
in repository insubstantial.
commit 1ca3059b1ee327c05c33e8db5296ee724553d437
Author: Felix Natter <fnatter at gmx.net>
Date: Sat Jan 16 15:19:52 2016 +0100
Imported Upstream version 7.3+dfsg1
---
.gitignore | 11 +
README.markdown | 42 +
build.gradle | 142 +
debian/changelog | 11 -
debian/copyright | 944 -------
debian/rules | 14 -
debian/watch | 5 -
flamingo/README.md | 18 +
flamingo/build.gradle | 93 +
flamingo/lib/test/debug-1.0.jar | Bin 0 -> 18830 bytes
flamingo/settings.gradle | 1 +
.../flamingo/api/bcb/BreadcrumbBarCallBack.java | 128 +
.../flamingo/api/bcb/BreadcrumbBarException.java | 50 +
.../api/bcb/BreadcrumbBarExceptionHandler.java | 48 +
.../flamingo/api/bcb/BreadcrumbBarModel.java | 240 ++
.../flamingo/api/bcb/BreadcrumbItem.java | 146 +
.../flamingo/api/bcb/BreadcrumbPathEvent.java | 79 +
.../flamingo/api/bcb/BreadcrumbPathListener.java | 48 +
.../flamingo/api/bcb/JBreadcrumbBar.java | 232 ++
.../api/bcb/core/BreadcrumbFileSelector.java | 323 +++
.../bcb/core/BreadcrumbTreeAdapterSelector.java | 304 +++
.../flamingo/api/common/AbstractCommandButton.java | 848 ++++++
.../flamingo/api/common/AbstractFileViewPanel.java | 399 +++
.../api/common/AsynchronousLoadListener.java | 50 +
.../flamingo/api/common/AsynchronousLoading.java | 63 +
.../api/common/CommandButtonDisplayState.java | 172 ++
.../api/common/CommandButtonLayoutManager.java | 177 ++
.../api/common/CommandToggleButtonGroup.java | 285 ++
.../flamingo/api/common/HorizontalAlignment.java | 62 +
.../flamingo/api/common/JCommandButton.java | 871 ++++++
.../flamingo/api/common/JCommandButtonPanel.java | 835 ++++++
.../flamingo/api/common/JCommandButtonStrip.java | 413 +++
.../flamingo/api/common/JCommandMenuButton.java | 141 +
.../flamingo/api/common/JCommandToggleButton.java | 113 +
.../api/common/JCommandToggleMenuButton.java | 86 +
.../flamingo/api/common/JScrollablePanel.java | 158 ++
.../flamingo/api/common/KeyValuePair.java | 122 +
.../flamingo/api/common/PopupActionListener.java | 41 +
.../flamingo/api/common/ProgressEvent.java | 102 +
.../flamingo/api/common/ProgressListener.java | 48 +
.../flamingo/api/common/RichToolTipManager.java | 381 +++
.../flamingo/api/common/RichTooltip.java | 338 +++
.../api/common/RolloverActionListener.java | 41 +
.../flamingo/api/common/StringValuePair.java | 51 +
.../api/common/icon/DecoratedResizableIcon.java | 231 ++
.../api/common/icon/EmptyResizableIcon.java | 113 +
.../api/common/icon/FilteredResizableIcon.java | 145 +
.../flamingo/api/common/icon/IcoWrapperIcon.java | 684 +++++
.../api/common/icon/IcoWrapperResizableIcon.java | 102 +
.../api/common/icon/IconDeckResizableIcon.java | 178 ++
.../flamingo/api/common/icon/ImageWrapperIcon.java | 315 +++
.../api/common/icon/ImageWrapperResizableIcon.java | 129 +
.../flamingo/api/common/icon/LayeredIcon.java | 99 +
.../flamingo/api/common/icon/ResizableIcon.java | 49 +
.../api/common/model/ActionButtonModel.java | 62 +
.../common/model/ActionRepeatableButtonModel.java | 245 ++
.../api/common/model/ActionToggleButtonModel.java | 125 +
.../api/common/model/PopupButtonModel.java | 74 +
.../api/common/popup/JColorSelectorPopupMenu.java | 330 +++
.../api/common/popup/JCommandPopupMenu.java | 359 +++
.../flamingo/api/common/popup/JPopupPanel.java | 146 +
.../api/common/popup/PopupPanelCallback.java | 54 +
.../api/common/popup/PopupPanelManager.java | 370 +++
.../flamingo/api/ribbon/AbstractRibbonBand.java | 558 ++++
.../flamingo/api/ribbon/JFlowRibbonBand.java | 100 +
.../pushingpixels/flamingo/api/ribbon/JRibbon.java | 1057 +++++++
.../flamingo/api/ribbon/JRibbonBand.java | 483 ++++
.../flamingo/api/ribbon/JRibbonComponent.java | 347 +++
.../flamingo/api/ribbon/JRibbonFrame.java | 716 +++++
.../flamingo/api/ribbon/RibbonApplicationMenu.java | 268 ++
.../api/ribbon/RibbonApplicationMenuEntry.java | 342 +++
.../ribbon/RibbonApplicationMenuEntryFooter.java | 66 +
.../ribbon/RibbonApplicationMenuEntryPrimary.java | 245 ++
.../RibbonApplicationMenuEntrySecondary.java | 127 +
.../api/ribbon/RibbonContextualTaskGroup.java | 188 ++
.../flamingo/api/ribbon/RibbonElementPriority.java | 52 +
.../flamingo/api/ribbon/RibbonTask.java | 237 ++
.../ribbon/resize/BaseRibbonBandResizePolicy.java | 58 +
.../BaseRibbonBandResizeSequencingPolicy.java | 55 +
.../ribbon/resize/CoreRibbonResizePolicies.java | 966 +++++++
.../resize/CoreRibbonResizeSequencingPolicies.java | 136 +
.../ribbon/resize/IconRibbonBandResizePolicy.java | 73 +
.../api/ribbon/resize/RibbonBandResizePolicy.java | 161 ++
.../resize/RibbonBandResizeSequencingPolicy.java | 75 +
.../internal/ui/bcb/BasicBreadcrumbBarUI.java | 720 +++++
.../flamingo/internal/ui/bcb/BreadcrumbBarUI.java | 46 +
.../internal/ui/bcb/BreadcrumbItemChoices.java | 116 +
.../ui/common/BasicCommandButtonListener.java | 412 +++
.../ui/common/BasicCommandButtonPanelUI.java | 850 ++++++
.../ui/common/BasicCommandButtonStripUI.java | 297 ++
.../internal/ui/common/BasicCommandButtonUI.java | 1263 +++++++++
.../ui/common/BasicCommandMenuButtonUI.java | 129 +
.../ui/common/BasicCommandToggleButtonUI.java | 71 +
.../ui/common/BasicCommandToggleMenuButtonUI.java | 100 +
.../ui/common/BasicRichTooltipPanelUI.java | 575 ++++
.../internal/ui/common/BasicScrollablePanelUI.java | 544 ++++
.../ui/common/CommandButtonLayoutManagerBig.java | 417 +++
.../common/CommandButtonLayoutManagerCustom.java | 123 +
.../common/CommandButtonLayoutManagerMedium.java | 509 ++++
.../ui/common/CommandButtonLayoutManagerSmall.java | 347 +++
.../ui/common/CommandButtonLayoutManagerTile.java | 554 ++++
.../internal/ui/common/CommandButtonPanelUI.java | 42 +
.../internal/ui/common/CommandButtonStripUI.java | 42 +
.../internal/ui/common/CommandButtonUI.java | 53 +
.../internal/ui/common/JRichTooltipPanel.java | 97 +
.../ui/common/ResizableIconUIResource.java | 67 +
.../internal/ui/common/RichTooltipPanelUI.java | 40 +
.../internal/ui/common/ScrollablePanelUI.java | 45 +
.../popup/BasicColorSelectorComponentUI.java | 238 ++
.../ui/common/popup/BasicColorSelectorPanelUI.java | 229 ++
.../ui/common/popup/BasicCommandPopupMenuUI.java | 685 +++++
.../ui/common/popup/BasicPopupPanelUI.java | 683 +++++
.../ui/common/popup/ColorSelectorComponentUI.java | 40 +
.../ui/common/popup/ColorSelectorPanelUI.java | 40 +
.../ui/common/popup/JColorSelectorComponent.java | 125 +
.../ui/common/popup/JColorSelectorPanel.java | 93 +
.../internal/ui/common/popup/PopupPanelUI.java | 42 +
.../ui/ribbon/AbstractBandControlPanel.java | 67 +
.../ui/ribbon/AbstractBandControlPanelUI.java | 195 ++
.../internal/ui/ribbon/BandControlPanelUI.java | 47 +
.../ui/ribbon/BasicBandControlPanelUI.java | 605 ++++
.../ui/ribbon/BasicFlowBandControlPanelUI.java | 237 ++
.../internal/ui/ribbon/BasicRibbonBandUI.java | 978 +++++++
.../internal/ui/ribbon/BasicRibbonComponentUI.java | 457 ++++
.../internal/ui/ribbon/BasicRibbonGalleryUI.java | 865 ++++++
.../ui/ribbon/BasicRibbonTaskToggleButtonUI.java | 322 +++
.../flamingo/internal/ui/ribbon/BasicRibbonUI.java | 2176 +++++++++++++++
.../ribbon/CommandButtonLayoutManagerBigFixed.java | 163 ++
...ommandButtonLayoutManagerBigFixedLandscape.java | 163 ++
.../internal/ui/ribbon/JBandControlPanel.java | 554 ++++
.../internal/ui/ribbon/JFlowBandControlPanel.java | 133 +
.../internal/ui/ribbon/JRibbonGallery.java | 576 ++++
.../internal/ui/ribbon/JRibbonRootPane.java | 83 +
.../ui/ribbon/JRibbonTaskToggleButton.java | 130 +
.../flamingo/internal/ui/ribbon/RibbonBandUI.java | 49 +
.../internal/ui/ribbon/RibbonComponentUI.java | 49 +
.../internal/ui/ribbon/RibbonGalleryUI.java | 41 +
.../flamingo/internal/ui/ribbon/RibbonUI.java | 90 +
.../BasicRibbonApplicationMenuButtonUI.java | 295 ++
.../BasicRibbonApplicationMenuPopupPanelUI.java | 483 ++++
.../CommandButtonLayoutManagerMenuTileLevel1.java | 291 ++
.../CommandButtonLayoutManagerMenuTileLevel2.java | 398 +++
.../appmenu/JRibbonApplicationMenuButton.java | 180 ++
.../appmenu/JRibbonApplicationMenuPopupPanel.java | 99 +
.../JRibbonApplicationMenuPopupPanelSecondary.java | 85 +
.../flamingo/internal/utils/AbstractFilter.java | 189 ++
.../internal/utils/ArrowResizableIcon.java | 195 ++
.../flamingo/internal/utils/ButtonSizingUtils.java | 135 +
.../flamingo/internal/utils/ColorShiftFilter.java | 122 +
.../internal/utils/DoubleArrowResizableIcon.java | 191 ++
.../flamingo/internal/utils/FlamingoUtilities.java | 621 +++++
.../flamingo/internal/utils/KeyTipManager.java | 670 +++++
.../internal/utils/KeyTipRenderingUtilities.java | 185 ++
.../flamingo/internal/utils/RenderingUtils.java | 112 +
.../api/svg/SvgTranscoderTemplatePlain.templ | 72 +
.../api/svg/SvgTranscoderTemplateResizable.templ | 145 +
gradlew | 164 ++
gradlew.bat | 90 +
laf-plugin/build.gradle | 43 +
laf-plugin/settings.gradle | 1 +
.../lafplugin/ComponentPluginManager.java | 117 +
.../lafplugin/LafComponentPlugin.java | 72 +
.../org/pushingpixels/lafplugin/LafPlugin.java | 48 +
.../org/pushingpixels/lafplugin/PluginManager.java | 220 ++
.../org/pushingpixels/lafplugin/XMLElement.java | 2875 +++++++++++++++++++
.../pushingpixels/lafplugin/XMLParseException.java | 130 +
.../org/pushingpixels/lafplugin/LafPlugin.license | 26 +
.../org/pushingpixels/lafplugin/NanoXML.license | 21 +
laf-widget/3rd/augment-infonode.bat | 33 +
laf-widget/3rd/augment-liquid.bat | 31 +
laf-widget/3rd/augment-looks.bat | 31 +
laf-widget/3rd/augment-napkin.bat | 29 +
laf-widget/3rd/augment-pagosoft.bat | 31 +
laf-widget/3rd/augment-squareness.bat | 31 +
laf-widget/3rd/run-ghost-infonode.bat | 1 +
laf-widget/3rd/run-ghost-liquid.bat | 1 +
laf-widget/3rd/run-ghost-looks.bat | 1 +
laf-widget/3rd/run-ghost-napkin.bat | 1 +
laf-widget/3rd/run-ghost-pagosoft.bat | 1 +
laf-widget/3rd/run-ghost-squareness.bat | 1 +
laf-widget/3rd/run-ghost-substance.bat | 1 +
laf-widget/3rd/run-infonode.bat | 1 +
laf-widget/3rd/run-liquid.bat | 1 +
laf-widget/3rd/run-looks.bat | 1 +
laf-widget/3rd/run-napkin.bat | 1 +
laf-widget/3rd/run-pagosoft.bat | 1 +
laf-widget/3rd/run-squareness.bat | 1 +
laf-widget/3rd/run-substance.bat | 1 +
laf-widget/build.gradle | 63 +
laf-widget/settings.gradle | 1 +
.../org/pushingpixels/lafwidget/LAFAdapter.java | 244 ++
.../org/pushingpixels/lafwidget/LafWidget.java | 330 +++
.../pushingpixels/lafwidget/LafWidgetAdapter.java | 127 +
.../lafwidget/LafWidgetRepository.java | 435 +++
.../pushingpixels/lafwidget/LafWidgetSupport.java | 399 +++
.../lafwidget/LafWidgetUtilities.java | 662 +++++
.../lafwidget/LafWidgetUtilities2.java | 128 +
.../org/pushingpixels/lafwidget/Resettable.java | 42 +
.../lafwidget/UiThreadingViolationException.java | 48 +
.../animation/AnimationConfigurationManager.java | 334 +++
.../lafwidget/animation/AnimationFacet.java | 136 +
.../animation/effects/GhostAnimationWidget.java | 125 +
.../animation/effects/GhostPaintingUtils.java | 430 +++
.../animation/effects/GhostingListener.java | 319 +++
.../ant/AugmentContainerGhostingTask.java | 198 ++
.../lafwidget/ant/AugmentException.java | 60 +
.../lafwidget/ant/AugmentIconGhostingTask.java | 207 ++
.../lafwidget/ant/AugmentMainTask.java | 223 ++
.../pushingpixels/lafwidget/ant/AugmentTask.java | 194 ++
.../lafwidget/ant/AugmentUpdateTask.java | 194 ++
.../lafwidget/ant/ContainerGhostingAugmenter.java | 536 ++++
.../lafwidget/ant/ContainerGhostingType.java | 114 +
.../lafwidget/ant/IconGhostingAugmenter.java | 759 +++++
.../lafwidget/ant/IconGhostingType.java | 107 +
.../lafwidget/ant/InfoClassVisitor.java | 106 +
.../lafwidget/ant/LafMainClassAugmenter.java | 543 ++++
.../lafwidget/ant/UiDelegateAugmenter.java | 717 +++++
.../lafwidget/ant/UiDelegateType.java | 79 +
.../lafwidget/ant/UiDelegateUpdateAugmenter.java | 444 +++
.../lafwidget/ant/UiDelegateWriterEmptyCtr.java | 100 +
.../lafwidget/ant/UiDelegateWriterOneParamCtr.java | 108 +
.../org/pushingpixels/lafwidget/ant/Utils.java | 239 ++
.../contrib/blogofbug/swing/SwingBugUtilities.java | 52 +
.../swing/borders/AbstractImageBorder.java | 155 ++
.../blogofbug/swing/borders/ImageBorder.java | 119 +
.../components/AbstractCarouselMenuAction.java | 43 +
.../blogofbug/swing/components/GradientPanel.java | 155 ++
.../blogofbug/swing/components/ImageLabel.java | 74 +
.../blogofbug/swing/components/JCarosel.java | 451 +++
.../blogofbug/swing/components/JCarouselMenu.java | 788 ++++++
.../swing/components/ReflectedImageLabel.java | 366 +++
.../blogofbug/swing/components/RichComponent.java | 81 +
.../blogofbug/swing/layout/CaroselLayout.java | 685 +++++
.../swing/layout/OffsetCaroselLayout.java | 111 +
.../contrib/blogofbug/utility/ImageUtilities.java | 241 ++
.../desktop/DesktopIconHoverPreviewWidget.java | 331 +++
.../lafwidget/menu/MenuSearchWidget.java | 948 +++++++
.../lafwidget/preview/DefaultPreviewPainter.java | 128 +
.../preview/InternalFramePreviewPainter.java | 101 +
.../lafwidget/preview/PreviewPainter.java | 172 ++
.../lafwidget/scroll/AutoScrollActivator.java | 275 ++
.../lafwidget/scroll/AutoScrollWidget.java | 107 +
.../lafwidget/scroll/ScrollPaneSelector.java | 443 +++
.../lafwidget/scroll/ScrollPaneSelectorWidget.java | 169 ++
.../lafwidget/scroll/TweakedScrollPaneLayout.java | 359 +++
.../lafwidget/tabbed/DefaultTabPreviewPainter.java | 140 +
.../lafwidget/tabbed/TabHoverPreviewWidget.java | 227 ++
.../lafwidget/tabbed/TabOverviewButton.java | 143 +
.../lafwidget/tabbed/TabOverviewDialog.java | 1252 +++++++++
.../lafwidget/tabbed/TabOverviewDialogWidget.java | 235 ++
.../lafwidget/tabbed/TabPagerManager.java | 564 ++++
.../tabbed/TabPagerMouseWheelListener.java | 98 +
.../lafwidget/tabbed/TabPagerWidget.java | 343 +++
.../lafwidget/tabbed/TabPreviewControl.java | 225 ++
.../lafwidget/tabbed/TabPreviewPainter.java | 239 ++
.../lafwidget/tabbed/TabPreviewThread.java | 483 ++++
.../lafwidget/tabbed/TabPreviewWindow.java | 343 +++
.../lafwidget/text/EditContextMenuWidget.java | 336 +++
.../pushingpixels/lafwidget/text/LockBorder.java | 148 +
.../lafwidget/text/LockBorderWidget.java | 200 ++
.../lafwidget/text/PasswordStrengthChecker.java | 59 +
.../text/PasswordStrengthCheckerWidget.java | 202 ++
.../lafwidget/text/SelectAllOnFocusGainWidget.java | 96 +
.../lafwidget/text/SelectOnEscapeWidget.java | 163 ++
.../tree/dnd/AutoScrollingTreeDropTarget.java | 101 +
.../lafwidget/tree/dnd/DnDBorderFactory.java | 250 ++
.../lafwidget/tree/dnd/DnDCellRendererProxy.java | 188 ++
.../lafwidget/tree/dnd/DnDVetoException.java | 49 +
.../lafwidget/tree/dnd/StringTreeDnDEvent.java | 90 +
.../lafwidget/tree/dnd/StringTreeDnDListener.java | 63 +
.../lafwidget/tree/dnd/TransferableTreeNode.java | 199 ++
.../lafwidget/tree/dnd/TreeDragAndDropWidget.java | 776 ++++++
.../lafwidget/tree/dnd/TreeTreeDnDEvent.java | 103 +
.../lafwidget/tree/dnd/TreeTreeDnDListener.java | 62 +
.../pushingpixels/lafwidget/utils/DeltaQueue.java | 336 +++
.../lafwidget/utils/LafConstants.java | 117 +
.../pushingpixels/lafwidget/utils/LookUtils.java | 448 +++
.../lafwidget/utils/RenderingUtils.java | 151 +
.../lafwidget/utils/ShadowPopupBorder.java | 134 +
.../lafwidget/utils/TrackableThread.java | 71 +
.../main/resources/META-INF/lafwidget.properties | 14 +
.../org/pushingpixels/lafwidget/ASM.license | 29 +
.../org/pushingpixels/lafwidget/BlogOfBug.license | 11 +
.../org/pushingpixels/lafwidget/JiBX.license | 25 +
.../org/pushingpixels/lafwidget/LafWidget.license | 26 +
.../org/pushingpixels/lafwidget/Looks.license | 26 +
.../org/pushingpixels/lafwidget/TangoIcons.license | 260 ++
.../lafwidget/resources/Labels.properties | 25 +
.../lafwidget/resources/Labels_ar.properties | 25 +
.../lafwidget/resources/Labels_bg.properties | 25 +
.../lafwidget/resources/Labels_cs.properties | 25 +
.../lafwidget/resources/Labels_da.properties | 25 +
.../lafwidget/resources/Labels_de.properties | 25 +
.../lafwidget/resources/Labels_el.properties | 24 +
.../lafwidget/resources/Labels_en_GB.properties | 24 +
.../lafwidget/resources/Labels_en_US.properties | 24 +
.../lafwidget/resources/Labels_es.properties | 14 +
.../lafwidget/resources/Labels_es_AR.properties | 25 +
.../lafwidget/resources/Labels_es_MX.properties | 24 +
.../lafwidget/resources/Labels_fi.properties | 24 +
.../lafwidget/resources/Labels_fr.properties | 26 +
.../lafwidget/resources/Labels_fr_CA.properties | 24 +
.../lafwidget/resources/Labels_hu.properties | 24 +
.../lafwidget/resources/Labels_it.properties | 26 +
.../lafwidget/resources/Labels_iw.properties | 25 +
.../lafwidget/resources/Labels_ja.properties | 24 +
.../lafwidget/resources/Labels_nl.properties | 25 +
.../lafwidget/resources/Labels_no.properties | 7 +
.../lafwidget/resources/Labels_pl.properties | 26 +
.../lafwidget/resources/Labels_pt.properties | 24 +
.../lafwidget/resources/Labels_pt_BR.properties | 25 +
.../lafwidget/resources/Labels_ro.properties | 25 +
.../lafwidget/resources/Labels_ru.properties | 25 +
.../lafwidget/resources/Labels_sv.properties | 24 +
.../lafwidget/resources/Labels_th.properties | 24 +
.../lafwidget/resources/Labels_tr.properties | 24 +
.../lafwidget/resources/Labels_vi.properties | 16 +
.../lafwidget/resources/Labels_zh_CN.properties | 24 +
.../lafwidget/resources/Labels_zh_HK.properties | 24 +
.../lafwidget/resources/Labels_zh_TW.properties | 24 +
.../lafwidget/scroll/resource/autoscroll_all.png | Bin 0 -> 1632 bytes
.../lafwidget/scroll/resource/autoscroll_h.png | Bin 0 -> 1413 bytes
.../lafwidget/scroll/resource/autoscroll_v.png | Bin 0 -> 1453 bytes
.../org/pushingpixels/lafwidget/text/edit-copy.png | Bin 0 -> 498 bytes
.../org/pushingpixels/lafwidget/text/edit-cut.png | Bin 0 -> 807 bytes
.../pushingpixels/lafwidget/text/edit-delete.png | Bin 0 -> 680 bytes
.../pushingpixels/lafwidget/text/edit-paste.png | Bin 0 -> 561 bytes
.../lafwidget/text/edit-select-all.png | Bin 0 -> 441 bytes
.../lafwidget/tree/dnd/icons/drop-not-allowed.png | Bin 0 -> 144 bytes
.../lafwidget/tree/dnd/icons/drop-on-leaf.png | Bin 0 -> 528 bytes
.../org/pushingpixels/lafwidget/utils/shadow.png | Bin 0 -> 315 bytes
settings.gradle | 1 +
substance-flamingo/build.gradle | 153 ++
substance-flamingo/doc-robot-skins.sh | 29 +
substance-flamingo/settings.gradle | 1 +
.../substance/flamingo/FlamingoPlugin.java | 219 ++
.../flamingo/bcb/ui/SubstanceBreadcrumbBarUI.java | 103 +
.../common/TransitionAwareResizableIcon.java | 273 ++
.../common/ui/ActionPopupTransitionAwareUI.java | 39 +
.../common/ui/SubstanceColorSelectorPanelUI.java | 106 +
.../common/ui/SubstanceCommandButtonPanelUI.java | 148 +
.../common/ui/SubstanceCommandButtonUI.java | 767 ++++++
.../common/ui/SubstanceCommandMenuButtonUI.java | 133 +
.../common/ui/SubstanceCommandPopupMenuUI.java | 124 +
.../common/ui/SubstanceCommandToggleButtonUI.java | 514 ++++
.../ui/SubstanceCommandToggleMenuButtonUI.java | 203 ++
.../flamingo/common/ui/SubstancePopupPanelUI.java | 78 +
.../common/ui/SubstanceScrollablePanelUI.java | 192 ++
.../flamingo/ribbon/RibbonBackgroundDelegate.java | 297 ++
.../gallery/oob/ColorSchemeResizableIcon.java | 217 ++
.../ribbon/gallery/oob/SkinResizableIcon.java | 58 +
.../ribbon/gallery/oob/SubstanceRibbonTask.java | 56 +
.../gallery/oob/SubstanceSkinRibbonGallery.java | 99 +
.../ribbon/gallery/oob/WatermarkResizableIcon.java | 222 ++
.../flamingo/ribbon/ui/RibbonBorderShaper.java | 108 +
.../ribbon/ui/SubstanceBandControlPanelUI.java | 81 +
.../ribbon/ui/SubstanceFlowBandControlPanelUI.java | 82 +
.../ui/SubstanceRibbonApplicationMenuButtonUI.java | 247 ++
...SubstanceRibbonApplicationMenuPopupPanelUI.java | 170 ++
.../ribbon/ui/SubstanceRibbonBandBorder.java | 106 +
.../flamingo/ribbon/ui/SubstanceRibbonBandUI.java | 382 +++
.../ribbon/ui/SubstanceRibbonComponentUI.java | 83 +
.../ribbon/ui/SubstanceRibbonFrameTitlePane.java | 772 ++++++
.../ribbon/ui/SubstanceRibbonGalleryUI.java | 209 ++
.../ribbon/ui/SubstanceRibbonRootPaneUI.java | 218 ++
.../ui/SubstanceRibbonTaskToggleButtonUI.java | 319 +++
.../flamingo/ribbon/ui/SubstanceRibbonUI.java | 434 +++
.../ribbon/ui/SubstanceRichTooltipPanelUI.java | 68 +
.../utils/CommandButtonBackgroundDelegate.java | 511 ++++
.../utils/CommandButtonVisualStateTracker.java | 105 +
...bonApplicationMenuButtonBackgroundDelegate.java | 211 ++
.../utils/SubstanceDisabledResizableIcon.java | 137 +
.../main/resources/META-INF/substance-plugin.xml | 3 +
.../src/tools/java/tools/docrobot/RobotMain.java | 62 +
.../src/tools/java/tools/docrobot/SkinRobot.java | 175 ++
.../tools/java/tools/docrobot/skins/Autumn.java | 50 +
.../tools/java/tools/docrobot/skins/Business.java | 50 +
.../tools/docrobot/skins/BusinessBlackSteel.java | 50 +
.../tools/docrobot/skins/BusinessBlueSteel.java | 50 +
.../tools/java/tools/docrobot/skins/Cerulean.java | 50 +
.../java/tools/docrobot/skins/ChallengerDeep.java | 50 +
.../src/tools/java/tools/docrobot/skins/Creme.java | 50 +
.../java/tools/docrobot/skins/CremeCoffee.java | 50 +
.../src/tools/java/tools/docrobot/skins/Dust.java | 49 +
.../java/tools/docrobot/skins/DustCoffee.java | 50 +
.../java/tools/docrobot/skins/EmeraldDusk.java | 50 +
.../tools/java/tools/docrobot/skins/Gemini.java | 50 +
.../tools/java/tools/docrobot/skins/Graphite.java | 50 +
.../java/tools/docrobot/skins/GraphiteAqua.java | 50 +
.../java/tools/docrobot/skins/GraphiteGlass.java | 50 +
.../tools/java/tools/docrobot/skins/Magellan.java | 50 +
.../tools/java/tools/docrobot/skins/MistAqua.java | 50 +
.../java/tools/docrobot/skins/MistSilver.java | 50 +
.../tools/java/tools/docrobot/skins/Moderate.java | 50 +
.../tools/java/tools/docrobot/skins/Nebula.java | 50 +
.../java/tools/docrobot/skins/NebulaBrickWall.java | 50 +
.../java/tools/docrobot/skins/OfficeBlack2007.java | 50 +
.../java/tools/docrobot/skins/OfficeBlue2007.java | 50 +
.../tools/docrobot/skins/OfficeSilver2007.java | 50 +
.../src/tools/java/tools/docrobot/skins/Raven.java | 50 +
.../tools/java/tools/docrobot/skins/Sahara.java | 50 +
.../tools/java/tools/docrobot/skins/Twilight.java | 50 +
substance-swingx/build.gradle | 140 +
substance-swingx/run doc robot.bat | 9 +
substance-swingx/settings.gradle | 1 +
.../substance/swingx/SubstanceDatePickerUI.java | 268 ++
.../substance/swingx/SubstanceErrorPaneUI.java | 103 +
.../substance/swingx/SubstanceHeaderUI.java | 132 +
.../substance/swingx/SubstanceHyperlinkUI.java | 38 +
.../substance/swingx/SubstanceLoginPaneUI.java | 191 ++
.../substance/swingx/SubstanceMonthViewUI.java | 1073 ++++++++
.../substance/swingx/SubstancePanelUI.java | 57 +
.../substance/swingx/SubstanceStatusBarUI.java | 156 ++
.../SubstanceSwingxFillBackgroundDelegate.java | 52 +
.../substance/swingx/SubstanceSwingxPlugin.java | 340 +++
.../substance/swingx/SubstanceTableUI.java | 61 +
.../swingx/SubstanceTaskPaneContainerUI.java | 118 +
.../substance/swingx/SubstanceTaskPaneUI.java | 504 ++++
.../substance/swingx/SubstanceTipOfTheDayUI.java | 118 +
.../substance/swingx/SubstanceTitledPanelUI.java | 94 +
.../main/resources/META-INF/substance-plugin.xml | 3 +
.../resources/resource/22/dialog-information.png | Bin 0 -> 1204 bytes
.../src/main/resources/resource/32/login-new32.png | Bin 0 -> 1303 bytes
.../src/tools/java/docrobot/Configure.java | 25 +
.../src/tools/java/docrobot/ErrorPaneRobot.java | 214 ++
.../src/tools/java/docrobot/ErrorPaneRunner.java | 76 +
.../src/tools/java/docrobot/FrameRobot.java | 178 ++
.../src/tools/java/docrobot/HeaderFrame.java | 67 +
.../src/tools/java/docrobot/HeaderRunner.java | 59 +
.../src/tools/java/docrobot/LoginDialogRobot.java | 208 ++
.../src/tools/java/docrobot/LoginDialogRunner.java | 58 +
.../src/tools/java/docrobot/MonthViewFrame.java | 58 +
.../src/tools/java/docrobot/MonthViewRunner.java | 60 +
.../src/tools/java/docrobot/RobotUtilities.java | 91 +
.../src/tools/java/docrobot/StatusBarFrame.java | 142 +
.../src/tools/java/docrobot/StatusBarRunner.java | 65 +
.../src/tools/java/docrobot/TaskPaneFrame.java | 139 +
.../src/tools/java/docrobot/TaskPaneRunner.java | 60 +
.../src/tools/java/docrobot/TitledPanelFrame.java | 56 +
.../src/tools/java/docrobot/TitledPanelRunner.java | 60 +
.../resources/docrbot/applications-internet.png | Bin 0 -> 2464 bytes
.../src/tools/resources/docrbot/tips.properties | 3 +
substance/build.gradle | 237 ++
substance/doc-robot-schemes.sh | 34 +
substance/doc-robot-skins.sh | 27 +
substance/doc-robot-watermarks.sh | 10 +
substance/settings.gradle | 1 +
.../substance/api/ColorSchemeAssociationKind.java | 185 ++
.../substance/api/ColorSchemeSingleColorQuery.java | 125 +
.../substance/api/ColorSchemeTransform.java | 46 +
.../substance/api/ComponentState.java | 714 +++++
.../substance/api/ComponentStateFacet.java | 128 +
.../substance/api/DecorationAreaType.java | 119 +
.../substance/api/SchemeBaseColors.java | 90 +
.../substance/api/SchemeDerivedColors.java | 102 +
.../substance/api/SchemeDerivedColorsResolver.java | 107 +
.../substance/api/SubstanceColorScheme.java | 164 ++
.../substance/api/SubstanceColorSchemeBundle.java | 565 ++++
.../substance/api/SubstanceConstants.java | 661 +++++
.../substance/api/SubstanceLookAndFeel.java | 2463 +++++++++++++++++
.../pushingpixels/substance/api/SubstanceSkin.java | 1137 ++++++++
.../api/UiThreadingViolationException.java | 49 +
.../substance/api/colorscheme/AquaColorScheme.java | 151 +
.../api/colorscheme/BarbyPinkColorScheme.java | 151 +
.../substance/api/colorscheme/BaseColorScheme.java | 383 +++
.../api/colorscheme/BaseDarkColorScheme.java | 47 +
.../api/colorscheme/BaseLightColorScheme.java | 47 +
.../api/colorscheme/BottleGreenColorScheme.java | 151 +
.../api/colorscheme/BrownColorScheme.java | 151 +
.../api/colorscheme/CharcoalColorScheme.java | 151 +
.../api/colorscheme/CremeColorScheme.java | 151 +
.../api/colorscheme/DarkGrayColorScheme.java | 152 ++
.../api/colorscheme/DarkMetallicColorScheme.java | 152 ++
.../api/colorscheme/DarkVioletColorScheme.java | 151 +
.../api/colorscheme/DerivedColorsResolverDark.java | 143 +
.../colorscheme/DerivedColorsResolverLight.java | 107 +
.../api/colorscheme/DesertSandColorScheme.java | 151 +
.../api/colorscheme/EbonyColorScheme.java | 151 +
.../api/colorscheme/JadeForestColorScheme.java | 151 +
.../api/colorscheme/LightAquaColorScheme.java | 151 +
.../api/colorscheme/LightGrayColorScheme.java | 152 ++
.../api/colorscheme/LimeGreenColorScheme.java | 151 +
.../api/colorscheme/MetallicColorScheme.java | 152 ++
.../api/colorscheme/OliveColorScheme.java | 151 +
.../api/colorscheme/OrangeColorScheme.java | 151 +
.../api/colorscheme/PurpleColorScheme.java | 151 +
.../api/colorscheme/RaspberryColorScheme.java | 151 +
.../api/colorscheme/SepiaColorScheme.java | 151 +
.../api/colorscheme/SteelBlueColorScheme.java | 151 +
.../api/colorscheme/SunGlareColorScheme.java | 151 +
.../api/colorscheme/SunfireRedColorScheme.java | 151 +
.../api/colorscheme/SunsetColorScheme.java | 151 +
.../api/colorscheme/TerracottaColorScheme.java | 151 +
.../api/colorscheme/UltramarineColorScheme.java | 151 +
.../api/combo/ComboPopupPrototypeCallback.java | 49 +
.../api/combo/WidestComboPopupPrototype.java | 73 +
.../substance/api/fonts/FontPolicy.java | 71 +
.../pushingpixels/substance/api/fonts/FontSet.java | 88 +
.../api/fonts/SubstanceFontUtilities.java | 227 ++
.../substance/api/inputmaps/InputMapSet.java | 86 +
.../substance/api/inputmaps/SubstanceInputMap.java | 61 +
.../api/inputmaps/SubstanceInputMapUtilities.java | 67 +
.../api/painter/FractionBasedPainter.java | 140 +
.../api/painter/SubstancePainterUtils.java | 94 +
.../api/painter/border/ClassicBorderPainter.java | 72 +
.../api/painter/border/CompositeBorderPainter.java | 99 +
.../api/painter/border/DelegateBorderPainter.java | 188 ++
.../border/DelegateFractionBasedBorderPainter.java | 171 ++
.../api/painter/border/FlatBorderPainter.java | 58 +
.../painter/border/FractionBasedBorderPainter.java | 114 +
.../api/painter/border/GlassBorderPainter.java | 72 +
.../api/painter/border/StandardBorderPainter.java | 145 +
.../api/painter/border/SubstanceBorderPainter.java | 84 +
.../painter/decoration/ArcDecorationPainter.java | 203 ++
.../decoration/BrushedMetalDecorationPainter.java | 85 +
.../decoration/ClassicDecorationPainter.java | 135 +
.../painter/decoration/FlatDecorationPainter.java | 79 +
.../decoration/FractionBasedDecorationPainter.java | 151 +
.../decoration/Glass3DDecorationPainter.java | 78 +
.../decoration/ImageWrapperDecorationPainter.java | 281 ++
.../decoration/MarbleNoiseDecorationPainter.java | 67 +
.../painter/decoration/MatteDecorationPainter.java | 181 ++
.../decoration/SubstanceDecorationPainter.java | 66 +
.../api/painter/fill/ClassicFillPainter.java | 73 +
.../api/painter/fill/FractionBasedFillPainter.java | 84 +
.../api/painter/fill/GlassFillPainter.java | 74 +
.../api/painter/fill/MatteFillPainter.java | 80 +
.../api/painter/fill/StandardFillPainter.java | 239 ++
.../api/painter/fill/SubduedFillPainter.java | 55 +
.../api/painter/fill/SubstanceFillPainter.java | 68 +
.../painter/highlight/ClassicHighlightPainter.java | 79 +
.../highlight/FractionBasedHighlightPainter.java | 82 +
.../painter/highlight/GlassHighlightPainter.java | 78 +
.../highlight/SubstanceHighlightPainter.java | 63 +
.../painter/overlay/BottomLineOverlayPainter.java | 92 +
.../overlay/BottomShadowOverlayPainter.java | 108 +
.../painter/overlay/SubstanceOverlayPainter.java | 66 +
.../painter/overlay/TopBezelOverlayPainter.java | 113 +
.../api/painter/overlay/TopLineOverlayPainter.java | 96 +
.../painter/overlay/TopShadowOverlayPainter.java | 101 +
.../SubstanceDefaultComboBoxRenderer.java | 269 ++
.../SubstanceDefaultListCellRenderer.java | 254 ++
.../SubstanceDefaultTableCellRenderer.java | 448 +++
.../SubstanceDefaultTableHeaderCellRenderer.java | 260 ++
.../SubstanceDefaultTreeCellRenderer.java | 466 ++++
.../substance/api/renderers/SubstanceRenderer.java | 42 +
.../substance/api/shaper/ClassicButtonShaper.java | 296 ++
.../api/shaper/RectangularButtonShaper.java | 52 +
.../substance/api/shaper/StandardButtonShaper.java | 328 +++
.../api/shaper/SubstanceButtonShaper.java | 105 +
.../substance/api/skin/AutumnSkin.java | 149 +
.../substance/api/skin/BusinessBlackSteelSkin.java | 150 +
.../substance/api/skin/BusinessBlueSteelSkin.java | 125 +
.../substance/api/skin/BusinessSkin.java | 133 +
.../substance/api/skin/CeruleanSkin.java | 287 ++
.../substance/api/skin/ChallengerDeepSkin.java | 92 +
.../substance/api/skin/CremeCoffeeSkin.java | 108 +
.../substance/api/skin/CremeSkin.java | 109 +
.../substance/api/skin/DustCoffeeSkin.java | 116 +
.../pushingpixels/substance/api/skin/DustSkin.java | 194 ++
.../substance/api/skin/EmeraldDuskSkin.java | 93 +
.../substance/api/skin/GeminiSkin.java | 333 +++
.../substance/api/skin/GraphiteAquaSkin.java | 184 ++
.../substance/api/skin/GraphiteGlassSkin.java | 207 ++
.../substance/api/skin/GraphiteSkin.java | 173 ++
.../substance/api/skin/MagellanSkin.java | 289 ++
.../substance/api/skin/MarinerSkin.java | 449 +++
.../substance/api/skin/MistAquaSkin.java | 102 +
.../substance/api/skin/MistSilverSkin.java | 113 +
.../substance/api/skin/ModerateSkin.java | 101 +
.../substance/api/skin/NebulaBrickWallSkin.java | 72 +
.../substance/api/skin/NebulaSkin.java | 187 ++
.../substance/api/skin/OfficeBlack2007Skin.java | 312 +++
.../substance/api/skin/OfficeBlue2007Skin.java | 260 ++
.../substance/api/skin/OfficeSilver2007Skin.java | 267 ++
.../substance/api/skin/RavenSkin.java | 166 ++
.../substance/api/skin/SaharaSkin.java | 99 +
.../substance/api/skin/SkinChangeListener.java | 50 +
.../pushingpixels/substance/api/skin/SkinInfo.java | 75 +
.../api/skin/SubstanceAutumnLookAndFeel.java | 55 +
.../SubstanceBusinessBlackSteelLookAndFeel.java | 56 +
.../SubstanceBusinessBlueSteelLookAndFeel.java | 55 +
.../api/skin/SubstanceBusinessLookAndFeel.java | 55 +
.../api/skin/SubstanceCeruleanLookAndFeel.java | 55 +
.../skin/SubstanceChallengerDeepLookAndFeel.java | 55 +
.../api/skin/SubstanceCremeCoffeeLookAndFeel.java | 56 +
.../api/skin/SubstanceCremeLookAndFeel.java | 55 +
.../api/skin/SubstanceDustCoffeeLookAndFeel.java | 56 +
.../api/skin/SubstanceDustLookAndFeel.java | 55 +
.../api/skin/SubstanceEmeraldDuskLookAndFeel.java | 56 +
.../api/skin/SubstanceGeminiLookAndFeel.java | 55 +
.../api/skin/SubstanceGraphiteAquaLookAndFeel.java | 55 +
.../skin/SubstanceGraphiteGlassLookAndFeel.java | 56 +
.../api/skin/SubstanceGraphiteLookAndFeel.java | 55 +
.../api/skin/SubstanceMagellanLookAndFeel.java | 55 +
.../api/skin/SubstanceMarinerLookAndFeel.java | 56 +
.../api/skin/SubstanceMistAquaLookAndFeel.java | 55 +
.../api/skin/SubstanceMistSilverLookAndFeel.java | 56 +
.../api/skin/SubstanceModerateLookAndFeel.java | 55 +
.../skin/SubstanceNebulaBrickWallLookAndFeel.java | 55 +
.../api/skin/SubstanceNebulaLookAndFeel.java | 55 +
.../skin/SubstanceOfficeBlack2007LookAndFeel.java | 56 +
.../skin/SubstanceOfficeBlue2007LookAndFeel.java | 55 +
.../skin/SubstanceOfficeSilver2007LookAndFeel.java | 55 +
.../api/skin/SubstanceRavenLookAndFeel.java | 55 +
.../api/skin/SubstanceSaharaLookAndFeel.java | 55 +
.../api/skin/SubstanceTwilightLookAndFeel.java | 55 +
.../substance/api/skin/TwilightSkin.java | 255 ++
.../substance/api/tabbed/BaseTabCloseListener.java | 40 +
.../api/tabbed/MultipleTabCloseListener.java | 63 +
.../substance/api/tabbed/TabCloseCallback.java | 94 +
.../substance/api/tabbed/TabCloseListener.java | 61 +
.../tabbed/VetoableMultipleTabCloseListener.java | 57 +
.../api/tabbed/VetoableTabCloseListener.java | 54 +
.../substance/api/trait/SubstanceTrait.java | 49 +
.../substance/api/trait/SubstanceTraitInfo.java | 69 +
.../watermark/SubstanceCrosshatchWatermark.java | 204 ++
.../api/watermark/SubstanceImageWatermark.java | 434 +++
.../api/watermark/SubstanceNullWatermark.java | 116 +
.../api/watermark/SubstanceStripeWatermark.java | 185 ++
.../api/watermark/SubstanceWatermark.java | 98 +
.../internal/animation/IconGlowTracker.java | 78 +
.../internal/animation/ModificationAwareUI.java | 36 +
.../animation/RootPaneDefaultButtonTracker.java | 407 +++
.../internal/animation/StateTransitionEvent.java | 55 +
.../animation/StateTransitionListener.java | 38 +
.../animation/StateTransitionMultiTracker.java | 99 +
.../internal/animation/StateTransitionTracker.java | 780 ++++++
.../internal/animation/TransitionAwareUI.java | 60 +
.../internal/colorscheme/BlendBiColorScheme.java | 236 ++
.../internal/colorscheme/HueShiftColorScheme.java | 207 ++
.../internal/colorscheme/InvertedColorScheme.java | 189 ++
.../internal/colorscheme/NegatedColorScheme.java | 190 ++
.../internal/colorscheme/SaturatedColorScheme.java | 207 ++
.../internal/colorscheme/ShadeColorScheme.java | 56 +
.../internal/colorscheme/ShiftColorScheme.java | 344 +++
.../internal/colorscheme/TintColorScheme.java | 56 +
.../internal/colorscheme/ToneColorScheme.java | 56 +
.../contrib/jgoodies/looks/FontSizeHints.java | 190 ++
.../internal/contrib/jgoodies/looks/Options.java | 501 ++++
.../contrib/jgoodies/looks/common/ShadowPopup.java | 407 +++
.../jgoodies/looks/common/ShadowPopupBorder.java | 131 +
.../jgoodies/looks/common/ShadowPopupFactory.java | 182 ++
.../randelshofer/quaqua/ButtonStateIcon.java | 194 ++
.../contrib/randelshofer/quaqua/MultiIcon.java | 130 +
.../quaqua/Quaqua13ColorChooserUI.java | 226 ++
.../quaqua/Quaqua14ColorChooserUI.java | 159 ++
.../randelshofer/quaqua/QuaquaIconFactory.java | 136 +
.../randelshofer/quaqua/QuaquaUtilities.java | 703 +++++
.../contrib/randelshofer/quaqua/VisualMargin.java | 185 ++
.../quaqua/colorchooser/CMYKChooser.form | 236 ++
.../quaqua/colorchooser/CMYKChooser.java | 422 +++
.../quaqua/colorchooser/ColorChooserMainPanel.form | 78 +
.../quaqua/colorchooser/ColorChooserMainPanel.java | 166 ++
.../quaqua/colorchooser/ColorPalettesChooser.form | 60 +
.../quaqua/colorchooser/ColorPalettesChooser.java | 282 ++
.../quaqua/colorchooser/ColorPicker.java | 337 +++
.../quaqua/colorchooser/ColorSliderModel.java | 196 ++
.../colorchooser/ColorSliderTextFieldHandler.java | 73 +
.../quaqua/colorchooser/ColorSliderUI.java | 586 ++++
.../quaqua/colorchooser/ColorSlidersChooser.form | 31 +
.../quaqua/colorchooser/ColorSlidersChooser.java | 153 ++
.../quaqua/colorchooser/ColorWheel.form | 6 +
.../quaqua/colorchooser/ColorWheel.java | 169 ++
.../quaqua/colorchooser/ColorWheelChooser.form | 27 +
.../quaqua/colorchooser/ColorWheelChooser.java | 114 +
.../colorchooser/ColorWheelImageProducer.java | 121 +
.../randelshofer/quaqua/colorchooser/Crayons.form | 13 +
.../randelshofer/quaqua/colorchooser/Crayons.java | 214 ++
.../quaqua/colorchooser/CrayonsChooser.form | 6 +
.../quaqua/colorchooser/CrayonsChooser.java | 98 +
.../quaqua/colorchooser/DefaultPalettes.java | 468 ++++
.../quaqua/colorchooser/GrayChooser.form | 168 ++
.../quaqua/colorchooser/GrayChooser.java | 319 +++
.../quaqua/colorchooser/GrayColorSliderModel.java | 56 +
.../quaqua/colorchooser/HSBChooser.form | 184 ++
.../quaqua/colorchooser/HSBChooser.java | 323 +++
.../quaqua/colorchooser/HSBColorSliderModel.java | 62 +
.../quaqua/colorchooser/HTMLChooser.form | 196 ++
.../quaqua/colorchooser/HTMLChooser.java | 467 ++++
.../quaqua/colorchooser/HTMLColorSliderModel.java | 104 +
.../colorchooser/HTMLSliderTextFieldHandler.java | 77 +
.../colorchooser/ICC_CMYKColorSliderModel.java | 86 +
.../colorchooser/NominalCMYKColorSliderModel.java | 107 +
.../quaqua/colorchooser/PaletteEntry.java | 44 +
.../colorchooser/PaletteEntryCellRenderer.java | 131 +
.../quaqua/colorchooser/PaletteListModel.java | 134 +
.../quaqua/colorchooser/Quaqua15ColorPicker.form | 31 +
.../quaqua/colorchooser/Quaqua15ColorPicker.java | 340 +++
.../colorchooser/QuaquaColorPreviewPanel.form | 11 +
.../colorchooser/QuaquaColorPreviewPanel.java | 80 +
.../quaqua/colorchooser/RGBChooser.form | 144 +
.../quaqua/colorchooser/RGBChooser.java | 287 ++
.../quaqua/colorchooser/RGBColorSliderModel.java | 57 +
.../quaqua/colorchooser/SmallColorWellBorder.java | 51 +
.../quaqua/colorchooser/SwatchPanel.form | 6 +
.../quaqua/colorchooser/SwatchPanel.java | 176 ++
.../quaqua/colorchooser/SwatchesChooser.form | 24 +
.../quaqua/colorchooser/SwatchesChooser.java | 403 +++
.../randelshofer/quaqua/colorchooser/package.html | 25 +
.../contrib/randelshofer/quaqua/util/Images.java | 289 ++
.../contrib/randelshofer/quaqua/util/Methods.java | 515 ++++
.../quaqua/util/ResourceBundleUtil.java | 204 ++
.../randelshofer/quaqua/util/ShiftedIcon.java | 59 +
.../contrib/randelshofer/quaqua/util/Worker.java | 88 +
.../contrib/randelshofer/quaqua/util/package.html | 25 +
.../xoetrope/editor/color/ColorWheelPanel.java | 1493 ++++++++++
.../contrib/xoetrope/editor/color/ModelColor.java | 409 +++
.../internal/fonts/DefaultGnomeFontPolicy.java | 169 ++
.../internal/fonts/DefaultKDEFontPolicy.java | 343 +++
.../internal/fonts/DefaultMacFontPolicy.java | 66 +
.../substance/internal/fonts/FontPolicies.java | 387 +++
.../substance/internal/fonts/FontSets.java | 316 +++
.../substance/internal/fonts/Fonts.java | 410 +++
.../substance/internal/fonts/ScaledFontSet.java | 108 +
.../internal/inputmaps/AquaInputMapSet.java | 468 ++++
.../internal/inputmaps/BaseInputMapSet.java | 1219 +++++++++
.../internal/inputmaps/GnomeInputMapSet.java | 90 +
.../internal/inputmaps/WindowsInputMapSet.java | 167 ++
.../internal/painter/BackgroundPaintingUtils.java | 216 ++
.../internal/painter/DecorationPainterUtils.java | 216 ++
.../internal/painter/HighlightPainterUtils.java | 211 ++
.../internal/painter/OverlayPainterUtils.java | 75 +
.../internal/painter/SeparatorPainterUtils.java | 463 ++++
.../internal/painter/SimplisticFillPainter.java | 76 +
.../painter/SimplisticSoftBorderPainter.java | 63 +
.../substance/internal/plugin/BasePlugin.java | 366 +++
.../substance/internal/plugin/BaseSkinPlugin.java | 124 +
.../internal/plugin/SubstanceSkinPlugin.java | 61 +
.../substance/internal/ui/SubstanceButtonUI.java | 516 ++++
.../internal/ui/SubstanceCheckBoxMenuItemUI.java | 266 ++
.../substance/internal/ui/SubstanceCheckBoxUI.java | 262 ++
.../internal/ui/SubstanceColorChooserUI.java | 91 +
.../substance/internal/ui/SubstanceComboBoxUI.java | 781 ++++++
.../internal/ui/SubstanceDesktopIconUI.java | 307 +++
.../internal/ui/SubstanceDesktopPaneUI.java | 107 +
.../internal/ui/SubstanceEditorPaneUI.java | 219 ++
.../internal/ui/SubstanceFileChooserUI.java | 179 ++
.../internal/ui/SubstanceFormattedTextFieldUI.java | 246 ++
.../internal/ui/SubstanceInternalFrameUI.java | 244 ++
.../substance/internal/ui/SubstanceLabelUI.java | 200 ++
.../substance/internal/ui/SubstanceListUI.java | 921 +++++++
.../substance/internal/ui/SubstanceMenuBarUI.java | 111 +
.../substance/internal/ui/SubstanceMenuItemUI.java | 257 ++
.../substance/internal/ui/SubstanceMenuUI.java | 354 +++
.../internal/ui/SubstanceOptionPaneUI.java | 169 ++
.../substance/internal/ui/SubstancePanelUI.java | 111 +
.../internal/ui/SubstancePasswordFieldUI.java | 507 ++++
.../internal/ui/SubstancePopupMenuSeparatorUI.java | 103 +
.../internal/ui/SubstancePopupMenuUI.java | 144 +
.../internal/ui/SubstanceProgressBarUI.java | 767 ++++++
.../ui/SubstanceRadioButtonMenuItemUI.java | 271 ++
.../internal/ui/SubstanceRadioButtonUI.java | 427 +++
.../substance/internal/ui/SubstanceRootPaneUI.java | 1728 ++++++++++++
.../internal/ui/SubstanceScrollBarUI.java | 2886 ++++++++++++++++++++
.../internal/ui/SubstanceScrollPaneUI.java | 691 +++++
.../internal/ui/SubstanceSeparatorUI.java | 112 +
.../substance/internal/ui/SubstanceSliderUI.java | 952 +++++++
.../substance/internal/ui/SubstanceSpinnerUI.java | 505 ++++
.../internal/ui/SubstanceSplitPaneUI.java | 102 +
.../internal/ui/SubstanceTabbedPaneUI.java | 2804 +++++++++++++++++++
.../internal/ui/SubstanceTableHeaderUI.java | 1059 +++++++
.../substance/internal/ui/SubstanceTableUI.java | 2652 ++++++++++++++++++
.../substance/internal/ui/SubstanceTextAreaUI.java | 224 ++
.../internal/ui/SubstanceTextFieldUI.java | 251 ++
.../substance/internal/ui/SubstanceTextPaneUI.java | 213 ++
.../internal/ui/SubstanceToggleButtonUI.java | 433 +++
.../internal/ui/SubstanceToolBarSeparatorUI.java | 108 +
.../substance/internal/ui/SubstanceToolBarUI.java | 133 +
.../substance/internal/ui/SubstanceToolTipUI.java | 124 +
.../substance/internal/ui/SubstanceTreeUI.java | 1199 ++++++++
.../substance/internal/ui/SubstanceViewportUI.java | 103 +
.../internal/utils/ButtonBackgroundDelegate.java | 507 ++++
.../internal/utils/ButtonVisualStateTracker.java | 138 +
.../substance/internal/utils/HashMapKey.java | 77 +
.../internal/utils/LazyResettableHashMap.java | 207 ++
.../internal/utils/LocaleChangeListener.java | 42 +
.../substance/internal/utils/MemoryAnalyzer.java | 304 +++
.../substance/internal/utils/NoiseFactory.java | 114 +
.../utils/PairwiseButtonBackgroundDelegate.java | 355 +++
.../internal/utils/PerlinNoiseGenerator.java | 115 +
.../internal/utils/RolloverButtonListener.java | 234 ++
.../internal/utils/RolloverControlListener.java | 246 ++
.../internal/utils/RolloverMenuItemListener.java | 167 ++
.../utils/RolloverTextControlListener.java | 271 ++
.../substance/internal/utils/Sideable.java | 49 +
.../substance/internal/utils/SkinUtilities.java | 1376 ++++++++++
.../substance/internal/utils/SoftHashMap.java | 173 ++
.../internal/utils/SubstanceColorResource.java | 56 +
.../utils/SubstanceColorSchemeUtilities.java | 780 ++++++
.../internal/utils/SubstanceColorUtilities.java | 807 ++++++
.../internal/utils/SubstanceCoreUtilities.java | 2282 ++++++++++++++++
.../internal/utils/SubstanceDropDownButton.java | 200 ++
.../internal/utils/SubstanceImageCreator.java | 2337 ++++++++++++++++
.../utils/SubstanceInternalArrowButton.java | 38 +
.../internal/utils/SubstanceInternalButton.java | 38 +
.../utils/SubstanceInternalFrameTitlePane.java | 940 +++++++
.../internal/utils/SubstanceOutlineUtilities.java | 297 ++
.../internal/utils/SubstanceSizeUtils.java | 984 +++++++
.../internal/utils/SubstanceSpinnerButton.java | 296 ++
.../internal/utils/SubstanceSplitPaneDivider.java | 557 ++++
.../internal/utils/SubstanceStripingUtils.java | 119 +
.../internal/utils/SubstanceTextUtilities.java | 600 ++++
.../internal/utils/SubstanceTitleButton.java | 89 +
.../internal/utils/SubstanceTitlePane.java | 1871 +++++++++++++
.../internal/utils/SubstanceWidgetManager.java | 126 +
.../internal/utils/SubstanceWidgetSupport.java | 286 ++
.../internal/utils/TabCloseListenerManager.java | 194 ++
.../substance/internal/utils/TraitInfoImpl.java | 107 +
.../internal/utils/UpdateOptimizationAware.java | 34 +
.../internal/utils/UpdateOptimizationInfo.java | 159 ++
.../internal/utils/border/SubstanceBorder.java | 240 ++
.../utils/border/SubstanceButtonBorder.java | 89 +
.../utils/border/SubstanceEtchedBorder.java | 150 +
.../internal/utils/border/SubstancePaneBorder.java | 276 ++
.../utils/border/SubstanceTableCellBorder.java | 236 ++
.../utils/border/SubstanceTextComponentBorder.java | 305 +++
.../utils/border/SubstanceToolBarBorder.java | 151 +
.../utils/combo/ComboBoxBackgroundDelegate.java | 295 ++
.../utils/combo/SubstanceComboBoxEditor.java | 60 +
.../internal/utils/combo/SubstanceComboPopup.java | 220 ++
.../internal/utils/filters/AbstractFilter.java | 162 ++
.../internal/utils/filters/ColorSchemeFilter.java | 212 ++
.../internal/utils/filters/GrayscaleFilter.java | 75 +
.../internal/utils/filters/NegatedFilter.java | 73 +
.../internal/utils/filters/TranslucentFilter.java | 75 +
.../utils/icon/ArrowButtonTransitionAwareIcon.java | 364 +++
.../internal/utils/icon/CheckBoxMenuItemIcon.java | 241 ++
.../substance/internal/utils/icon/GlowingIcon.java | 131 +
.../internal/utils/icon/MenuArrowIcon.java | 101 +
.../utils/icon/RadioButtonMenuItemIcon.java | 237 ++
.../internal/utils/icon/SubstanceIconFactory.java | 1049 +++++++
.../internal/utils/icon/TransitionAware.java | 38 +
.../internal/utils/icon/TransitionAwareIcon.java | 296 ++
.../internal/utils/menu/MenuUtilities.java | 1018 +++++++
.../internal/utils/menu/SubstanceMenu.java | 86 +
.../menu/SubstanceMenuBackgroundDelegate.java | 230 ++
.../utils/scroll/SubstanceScrollButton.java | 107 +
.../utils/scroll/SubstanceScrollPaneBorder.java | 229 ++
substance/src/main/resources/Looks.license | 26 +
.../main/resources/META-INF/substance-plugin.xml | 4 +
.../META-INF/substance.highlight.properties | 6 +
substance/src/main/resources/Quaqua.license | 632 +++++
substance/src/main/resources/Substance.license | 33 +
substance/src/main/resources/XUI.license | 470 ++++
.../substance/api/skin/autumn.colorschemes | 33 +
.../substance/api/skin/coffee-active.colorscheme | 9 +
.../substance/api/skin/dust.colorschemes | 131 +
.../substance/api/skin/gemini.colorschemes | 121 +
.../substance/api/skin/graphite.colorschemes | 186 ++
.../substance/api/skin/kitchen-sink.colorschemes | 55 +
.../skin/lightgray-general-watermark.colorscheme | 9 +
.../substance/api/skin/magellan.colorschemes | 242 ++
.../substance/api/skin/mariner.colorschemes | 187 ++
.../substance/api/skin/nebula.colorschemes | 110 +
.../substance/api/skin/office2007.colorschemes | 494 ++++
.../substance/api/skin/twilight.colorschemes | 133 +
.../contrib/jgoodies/looks/common/shadow.png | Bin 0 -> 315 bytes
.../contrib/randelshofer/quaqua/Labels.properties | 242 ++
.../randelshofer/quaqua/Labels_de.properties | 271 ++
.../randelshofer/quaqua/Labels_en.properties | 230 ++
.../randelshofer/quaqua/Labels_fr.properties | 274 ++
.../randelshofer/quaqua/Labels_it.properties | 270 ++
.../randelshofer/quaqua/Labels_zh_CN.properties | 245 ++
.../randelshofer/quaqua/images/big_crayons.png | Bin 0 -> 25646 bytes
.../randelshofer/quaqua/images/chart_bar.png | Bin 0 -> 935 bytes
.../randelshofer/quaqua/images/color_swatch.png | Bin 0 -> 1286 bytes
.../randelshofer/quaqua/images/color_wheel.png | Bin 0 -> 1024 bytes
.../contrib/randelshofer/quaqua/images/palette.png | Bin 0 -> 934 bytes
.../contrib/randelshofer/quaqua/images/pencil.png | Bin 0 -> 511 bytes
.../substance/internal/resources/Labels.properties | 31 +
.../internal/resources/Labels_ar.properties | 32 +
.../internal/resources/Labels_bg.properties | 32 +
.../internal/resources/Labels_cs.properties | 32 +
.../internal/resources/Labels_da.properties | 32 +
.../internal/resources/Labels_de.properties | 146 +
.../internal/resources/Labels_el.properties | 19 +
.../internal/resources/Labels_en_GB.properties | 27 +
.../internal/resources/Labels_es.properties | 146 +
.../internal/resources/Labels_es_AR.properties | 105 +
.../internal/resources/Labels_es_MX.properties | 20 +
.../internal/resources/Labels_fi.properties | 19 +
.../internal/resources/Labels_fr.properties | 146 +
.../internal/resources/Labels_fr_CA.properties | 19 +
.../internal/resources/Labels_hu.properties | 19 +
.../internal/resources/Labels_it.properties | 148 +
.../internal/resources/Labels_iw.properties | 32 +
.../internal/resources/Labels_ja.properties | 134 +
.../internal/resources/Labels_ko.properties | 114 +
.../internal/resources/Labels_nl.properties | 31 +
.../internal/resources/Labels_no.properties | 32 +
.../internal/resources/Labels_pl.properties | 32 +
.../internal/resources/Labels_pt.properties | 19 +
.../internal/resources/Labels_pt_BR.properties | 33 +
.../internal/resources/Labels_ro.properties | 21 +
.../internal/resources/Labels_ru.properties | 32 +
.../internal/resources/Labels_sv.properties | 142 +
.../internal/resources/Labels_th.properties | 19 +
.../internal/resources/Labels_tr.properties | 29 +
.../internal/resources/Labels_vi.properties | 19 +
.../internal/resources/Labels_zh_CN.properties | 144 +
.../internal/resources/Labels_zh_HK.properties | 134 +
.../internal/resources/Labels_zh_TW.properties | 134 +
.../main/resources/resource/32/dialog-error.png | Bin 0 -> 1645 bytes
.../resources/resource/32/dialog-information.png | Bin 0 -> 1910 bytes
.../main/resources/resource/32/dialog-warning.png | Bin 0 -> 1391 bytes
.../main/resources/resource/32/help-browser.png | Bin 0 -> 2231 bytes
.../resources/resource/TangoFamfamIcons.license | 260 ++
.../resources/resource/application_view_detail.png | Bin 0 -> 576 bytes
.../resources/resource/application_view_list.png | Bin 0 -> 473 bytes
substance/src/main/resources/resource/brushed.gif | Bin 0 -> 20067 bytes
substance/src/main/resources/resource/computer.png | Bin 0 -> 443 bytes
.../src/main/resources/resource/drive-harddisk.png | Bin 0 -> 603 bytes
.../src/main/resources/resource/folder-new.png | Bin 0 -> 635 bytes
substance/src/main/resources/resource/folder.png | Bin 0 -> 498 bytes
substance/src/main/resources/resource/go-up.png | Bin 0 -> 652 bytes
.../src/main/resources/resource/katakana.license | 1 +
substance/src/main/resources/resource/katakana.ttf | Bin 0 -> 15584 bytes
.../src/main/resources/resource/media-floppy.png | Bin 0 -> 561 bytes
.../src/main/resources/resource/text-x-generic.png | Bin 0 -> 333 bytes
.../src/main/resources/resource/user-home.png | Bin 0 -> 528 bytes
.../tools/java/tools/common/JImageComponent.java | 389 +++
.../src/tools/java/tools/docrobot/BaseRobot.java | 168 ++
.../java/tools/docrobot/ColorSchemeRobot.java | 53 +
.../java/tools/docrobot/ImageWatermarkRobot.java | 164 ++
.../java/tools/docrobot/RobotDefaultDarkSkin.java | 134 +
.../java/tools/docrobot/RobotDefaultSkin.java | 116 +
.../src/tools/java/tools/docrobot/RobotMain.java | 62 +
.../src/tools/java/tools/docrobot/SkinRobot.java | 211 ++
.../tools/java/tools/docrobot/WatermarkRobot.java | 65 +
.../docrobot/painters/WatermarkOverlaying.java | 66 +
.../java/tools/docrobot/schemes/AquaScheme.java | 50 +
.../tools/docrobot/schemes/BarbyPinkScheme.java | 50 +
.../tools/docrobot/schemes/BottleGreenScheme.java | 50 +
.../java/tools/docrobot/schemes/BrownScheme.java | 50 +
.../tools/docrobot/schemes/CharcoalScheme.java | 50 +
.../java/tools/docrobot/schemes/CremeScheme.java | 50 +
.../tools/docrobot/schemes/DarkVioletScheme.java | 50 +
.../docrobot/schemes/DerivedDesaturatedScheme.java | 51 +
.../docrobot/schemes/DerivedHueShiftedScheme.java | 51 +
.../docrobot/schemes/DerivedInvertedScheme.java | 51 +
.../docrobot/schemes/DerivedNegatedScheme.java | 51 +
.../docrobot/schemes/DerivedSaturatedScheme.java | 51 +
.../docrobot/schemes/DerivedShadedScheme.java | 51 +
.../schemes/DerivedShiftedBackgroundScheme.java | 55 +
.../docrobot/schemes/DerivedShiftedScheme.java | 55 +
.../docrobot/schemes/DerivedTintedScheme.java | 51 +
.../tools/docrobot/schemes/DerivedTonedScheme.java | 51 +
.../tools/docrobot/schemes/DesertSandScheme.java | 50 +
.../java/tools/docrobot/schemes/EbonyScheme.java | 50 +
.../tools/docrobot/schemes/JadeForestScheme.java | 50 +
.../tools/docrobot/schemes/LightAquaScheme.java | 50 +
.../tools/docrobot/schemes/LimeGreenScheme.java | 50 +
.../java/tools/docrobot/schemes/OliveScheme.java | 50 +
.../java/tools/docrobot/schemes/OrangeScheme.java | 50 +
.../java/tools/docrobot/schemes/PurpleScheme.java | 50 +
.../tools/docrobot/schemes/RaspberryScheme.java | 50 +
.../java/tools/docrobot/schemes/SepiaScheme.java | 50 +
.../tools/docrobot/schemes/SteelBlueScheme.java | 50 +
.../tools/docrobot/schemes/SunGlareScheme.java | 50 +
.../java/tools/docrobot/schemes/SunsetScheme.java | 50 +
.../tools/docrobot/schemes/TerracottaScheme.java | 50 +
.../tools/docrobot/schemes/UltramarineScheme.java | 50 +
.../tools/java/tools/docrobot/skins/Autumn.java | 49 +
.../tools/java/tools/docrobot/skins/Business.java | 49 +
.../tools/docrobot/skins/BusinessBlackSteel.java | 50 +
.../tools/docrobot/skins/BusinessBlueSteel.java | 50 +
.../tools/java/tools/docrobot/skins/Cerulean.java | 47 +
.../java/tools/docrobot/skins/ChallengerDeep.java | 50 +
.../src/tools/java/tools/docrobot/skins/Creme.java | 49 +
.../java/tools/docrobot/skins/CremeCoffee.java | 49 +
.../src/tools/java/tools/docrobot/skins/Dust.java | 49 +
.../java/tools/docrobot/skins/DustCoffee.java | 49 +
.../java/tools/docrobot/skins/EmeraldDusk.java | 49 +
.../tools/java/tools/docrobot/skins/Gemini.java | 49 +
.../tools/java/tools/docrobot/skins/Graphite.java | 49 +
.../java/tools/docrobot/skins/GraphiteAqua.java | 49 +
.../java/tools/docrobot/skins/GraphiteGlass.java | 49 +
.../tools/java/tools/docrobot/skins/Magellan.java | 49 +
.../tools/java/tools/docrobot/skins/Mariner.java | 49 +
.../tools/java/tools/docrobot/skins/MistAqua.java | 49 +
.../java/tools/docrobot/skins/MistSilver.java | 49 +
.../tools/java/tools/docrobot/skins/Moderate.java | 49 +
.../tools/java/tools/docrobot/skins/Nebula.java | 49 +
.../java/tools/docrobot/skins/NebulaBrickWall.java | 50 +
.../src/tools/java/tools/docrobot/skins/Raven.java | 49 +
.../tools/java/tools/docrobot/skins/Sahara.java | 49 +
.../tools/java/tools/docrobot/skins/Twilight.java | 49 +
.../docrobot/watermarks/CrosshatchWatermark.java | 50 +
.../watermarks/ImageWatermarkAngelina.java | 67 +
.../docrobot/watermarks/ImageWatermarkBeyonce.java | 67 +
.../docrobot/watermarks/ImageWatermarkDominic.java | 67 +
.../docrobot/watermarks/ImageWatermarkTerry.java | 67 +
.../tools/docrobot/watermarks/NullWatermark.java | 49 +
.../docrobot/watermarks/StripesWatermark.java | 50 +
.../src/tools/java/tools/electra/Electra.java | 152 ++
.../tools/electra/JElectrifiedImageComponent.java | 996 +++++++
.../java/tools/jitterbug/JColorComponent.java | 209 ++
.../tools/jitterbug/JColorSchemeComponent.java | 304 +++
.../java/tools/jitterbug/JColorSchemeList.java | 492 ++++
.../src/tools/java/tools/jitterbug/JHsvGraph.java | 180 ++
.../java/tools/jitterbug/JitterbugEditor.java | 385 +++
.../java/tools/jitterbug/StateChangeEvent.java | 49 +
.../java/tools/jitterbug/StateChangeListener.java | 36 +
.../src/tools/java/tools/jitterbug/substance.java | 116 +
.../java/tools/uidebug/ColorBlindColorScheme.java | 337 +++
.../tools/uidebug/DeuteranopiaColorScheme.java | 51 +
.../java/tools/uidebug/ProtanopiaColorScheme.java | 51 +
.../tools/uidebug/RootPaneTitlePaneUiDebugger.java | 464 ++++
.../java/tools/uidebug/ScrollBarUiDebugger.java | 114 +
.../java/tools/uidebug/TritanopiaColorScheme.java | 51 +
.../tools/resources/META-INF/lafwidget.properties | 2 +
.../src/tools/resources/tools/jitterbug/add.png | Bin 0 -> 733 bytes
.../tools/resources/tools/jitterbug/arrow_down.png | Bin 0 -> 379 bytes
.../tools/resources/tools/jitterbug/arrow_up.png | Bin 0 -> 372 bytes
.../resources/tools/jitterbug/chart_line_edit.png | Bin 0 -> 718 bytes
.../src/tools/resources/tools/jitterbug/delete.png | Bin 0 -> 715 bytes
.../resources/tools/jitterbug/exclamation.png | Bin 0 -> 701 bytes
.../tools/resources/tools/jitterbug/page_save.png | Bin 0 -> 774 bytes
trident/build.gradle | 161 ++
trident/settings.gradle | 1 +
.../java/org/pushingpixels/trident/Timeline.java | 626 +++++
.../org/pushingpixels/trident/TimelineEngine.java | 1030 +++++++
.../trident/TimelinePropertyBuilder.java | 444 +++
.../pushingpixels/trident/TimelineRunnable.java | 65 +
.../pushingpixels/trident/TimelineScenario.java | 357 +++
.../org/pushingpixels/trident/TridentConfig.java | 283 ++
.../pushingpixels/trident/UIToolkitHandler.java | 38 +
.../android/AndroidPropertyInterpolators.java | 164 ++
.../trident/android/AndroidRepaintCallback.java | 76 +
.../trident/android/AndroidRepaintTimeline.java | 81 +
.../trident/android/AndroidToolkitHandler.java | 51 +
.../trident/android/TimelineAsyncTask.java | 52 +
.../trident/callback/RunOnUIThread.java | 42 +
.../trident/callback/TimelineCallback.java | 81 +
.../trident/callback/TimelineCallbackAdapter.java | 49 +
.../trident/callback/TimelineScenarioCallback.java | 45 +
.../callback/UIThreadTimelineCallbackAdapter.java | 40 +
.../org/pushingpixels/trident/ease/Linear.java | 38 +
.../java/org/pushingpixels/trident/ease/Sine.java | 38 +
.../org/pushingpixels/trident/ease/Spline.java | 219 ++
.../pushingpixels/trident/ease/TimelineEase.java | 34 +
.../interpolator/CorePropertyInterpolators.java | 102 +
.../trident/interpolator/KeyFrames.java | 225 ++
.../trident/interpolator/KeyInterpolators.java | 70 +
.../trident/interpolator/KeyTimes.java | 105 +
.../trident/interpolator/KeyValues.java | 185 ++
.../trident/interpolator/PropertyInterpolator.java | 36 +
.../interpolator/PropertyInterpolatorSource.java | 36 +
.../trident/swing/AWTPropertyInterpolators.java | 162 ++
.../trident/swing/SwingRepaintCallback.java | 111 +
.../trident/swing/SwingRepaintTimeline.java | 95 +
.../trident/swing/SwingToolkitHandler.java | 56 +
.../trident/swing/TimelineSwingWorker.java | 52 +
.../trident/swt/SWTPropertyInterpolators.java | 141 +
.../trident/swt/SWTRepaintCallback.java | 115 +
.../trident/swt/SWTRepaintTimeline.java | 94 +
.../trident/swt/SWTToolkitHandler.java | 50 +
.../META-INF/trident-plugin-android.properties | 4 +
.../META-INF/trident-plugin-base.properties | 1 +
.../META-INF/trident-plugin-swing.properties | 4 +
.../META-INF/trident-plugin-swt.properties | 4 +
.../resources/META-INF/trident-plugin.properties | 10 +
1063 files changed, 203347 insertions(+), 974 deletions(-)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2a63085
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+build
+drop
+www/webstart/*.jar
+.gradle
+.idea
+*.iml
+*.iws
+*.ipr
+.classpath
+.project
+.settings
diff --git a/README.markdown b/README.markdown
new file mode 100755
index 0000000..7693b86
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,42 @@
+To Whom It May Concern:
+
+Insubstantial is currently not releasing any more builds and is [actively looking for a new maintainer](http://speling.shemnon.com/blog/2013/06/08/insubstantial-needs-a-new-maintainer/). It could be you!
+
+
+---
+
+This is a fork of several of Kirill Grouchnikov's Java Swing libraries.
+
+BuildHive status:
+[![Build Status](https://buildhive.cloudbees.com/job/Insubstantial/job/insubstantial/badge/icon)](https://buildhive.cloudbees.com/job/Insubstantial/job/insubstantial/)
+
+##To perform a full release
+
+###Prerequisites
+You need to have your GPG keys set up and your sonatype password handy. To set up your GPG key set this in
+your `~/.gradle/gradle.properties (you will need to replace ABCDEF12 and password with your key ID and password).
+
+ signing.keyId=ABCDEF12
+ signing.password=password
+ signing.secretKeyRingFile=~/.gnupg/secring.gpg
+
+If you are alergic to storing passwords on the file system, the passwords can be passed into the command line
+of your gradle invocation via the `-Dsigning.password=password` flag. If you have no such allergies you can also store your sonatype
+credentails
+
+ sonatypeUsername=username
+ sonatypePassword=password
+
+###The Release Build
+
+1. Update the version in the root build.gradle file to remove the SNAPSHOT. There should be two locations.
+2. Commit the change to git
+3. perform the build:
+ gradle clean githubDist uploadPublished
+4. Perform smoke testing so you are sure you are happy with the bits.
+5. Upload jars to the github site
+6. Go to oss.sonatype.org and close and release the repo
+7. Bump the version numbers in the root build.gradle, being sure to add SNAPSHOT
+8. Commit the change to git
+9. Push changes to github
+10. After the jars appear in the central repo, post release notes
diff --git a/build.gradle b/build.gradle
new file mode 100755
index 0000000..5dafef8
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,142 @@
+group = 'com.github.insubstantial'
+version = '7.3.1-SNAPSHOT'
+
+if (JavaVersion.current().isJava8Compatible()) {
+ allprojects {
+ tasks.withType(Javadoc) {
+ options.addStringOption('Xdoclint:none', '-quiet')
+ }
+ }
+}
+
+subprojects {
+ apply plugin: 'java'
+ apply plugin: 'maven'
+ apply plugin: 'signing'
+
+ group = 'com.github.insubstantial'
+ version = '7.3.1-SNAPSHOT'
+ ext.versionKey = "7.3-destroyer"
+
+ sourceCompatibility = 1.6
+ targetCompatibility = 1.6
+
+ configurations {
+ maven { extendsFrom archives }
+ published { extendsFrom archives, signatures}
+ distro {extendsFrom runtime}
+ }
+
+ signing {
+ required = { gradle.taskGraph.hasTask(uploadPublished) && !version.endsWith("SNAPSHOT") }
+ sign configurations.archives
+ }
+
+ repositories {
+ maven { url 'https://oss.sonatype.org/content/groups/staging' }
+ mavenCentral()
+ maven { url new File(System.getProperty('user.home'), '.m2/repository').toURI().toString() }
+ }
+
+ task sourceJar(type: Jar) {
+ from sourceSets.main.java
+ from sourceSets.main.resources
+ classifier = 'sources'
+ }
+
+ task javadocJar(type: Jar) {
+ dependsOn javadoc
+ from javadoc.destinationDir
+ classifier = 'javadoc'
+ }
+
+ artifacts {
+ archives sourceJar
+ archives javadocJar
+ }
+
+ uploadPublished {
+ doFirst {
+ // configure repositories ina doFirst so we can late bind the proeprties
+ checkPasswords()
+ repositories {
+ mavenDeployer {
+ beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+ snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
+ authentication userName: sonatypeUsername, password: sonatypePassword
+ }
+ repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
+ authentication userName: sonatypeUsername, password: sonatypePassword
+ }
+ }
+ }
+ }
+ configuration = configurations.published
+ }
+
+ install {
+ configuration = configurations.published
+ }
+
+ ext.configureBasePom = { pom ->
+ pom.project {
+ modelVersion '4.0.0'
+ packaging 'jar'
+ scm {
+ connection 'scm:git:git at github.com:Insubstantial/insubstantial.git'
+ developerConnection 'scm:git:git at github.com:Insubstantial/insubstantial.git'
+ url 'scm:git:git at github.com:Insubstantial/insubstantial.git'
+ }
+ developers {
+ developer {
+ name 'Kirill Grouchnikov'
+ email 'kirillcool at yahoo.com'
+ roles {
+ role 'author'
+ role 'developer'
+ }
+ }
+ developer {
+ name 'Danno Ferrin'
+ email 'danno.ferrin at shemnon.com'
+ roles {
+ role 'maintainer'
+ }
+ }
+ }
+ }
+ }
+}
+
+task githubDist(type: Zip) {
+ archiveName = "insubstantial-libraries-${project.version}.zip"
+ from subprojects*.configurations['distro']*.allArtifacts.files
+}
+
+task wrapper(type: Wrapper) {
+ gradleVersion = '1.0-rc-3'
+}
+
+def checkPasswords() {
+ try {
+ check = [ext.sonatypeUsername, ext.sonatypePassword]
+ println "Using sonatype user $sonatypeUsername"
+ } catch (MissingPropertyException e) {
+ Console console = System.console()
+ console.printf "\n\nIn order to upload to Sonatype we need your username and password.\nEnter a blank username or password to skip upload\n\n"
+ ext.sonatypeUsername = console.readLine("Sonatype Username: ")
+ ext.sonatypePassword = new String(console.readPassword("Sonatype Password: "))
+ if (!sonatypePassword || !sonatypeUsername) {
+ console.printf("\n\nSonatype upload aborted")
+ subprojects {
+ signing {
+ enabled = false
+ }
+ uploadPublished {
+ enabled = false
+ }
+ }
+ throw new StopExecutionException()
+ }
+ }
+}
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 31d9313..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,11 +0,0 @@
-insubstantial (7.3+dfsg1-1) experimental; urgency=medium
-
- * New upstream release (Closes: #787953)
- * This source package contains flamingo, laf-plugin, laf.widget,
- substance, substance-flamingo, substance-swingx and trident
- * Used some bits / copyright infos from those packages
- (see copyright for some credits)
- * TODO: from git!
- * TODO: control + git repo!
-
- -- Felix Natter <fnatter at gmx.net> Sat, 09 Jan 2016 17:48:45 +0100
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index aecbf0d..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,944 +0,0 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: insubstantial
-Source: https://github.com/Insubstantial/insubstantial
-Files-Excluded: flamingo/www/*
- flamingo/src/test/*
- laf-plugin/www/*
- laf-plugin/src/test/*
- laf-widget/www/*
- laf-widget/src/test/*
- substance/www/*
- substance/src/test/*
- substance-flamingo/www/*
- substance-flamingo/src/test/*
- substance-swingx/www/*
- substance-swingx/src/test/*
- trident/www/*
- trident/src/test/*
- gradle/wrapper/*
- substance/src/tools/resources/tools/docrobot/watermarks/*
-Comment: src/test/* misses some copyrights and we don't run tests.
- www/* (extensive documentation with examples) not used and includes flash,
- misses copyrights
- substance/src/tools/resources/tools/docrobot/watermarks/*:
- copyrighted pictures
-
-Files: debian/*
-Copyright: 2011 Andrew Ross <ubuntu at rossfamily.co.uk> (flamingo)
- 2007 Torsten Werner <twerner at debian.org> (laf-plugin)
- 2007 Varun Hiremath <varunhiremath at gmail.com> (laf-widgets)
- 2009 Damien Raude-Morvan <drazzib at debian.org> (laf-widgets)
- 2015-2016 Felix Natter <fnatter at gmx.net> (combined 7.3 source package)
-License: Apache-2.0
-
-Files: build.gradle settings.gradle .gitignore README.markdown
-Copyright: 2005-2010 Kirill Grouchnikov
-License: BSD-3-clause
-Comment: Miscellaneous build files
-
-Files: gradle*
-Copyright: Copyright 2010 gradle original author or authors.
-License: Apache-2.0
-Comment: Generated by gradle wrapper (incomplete as binary jar is excluded)
-
-Files: flamingo/*
-Copyright: 2003-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
-License: BSD-3-clause
-
-Files: ./flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/AbstractFilter.java
- ./substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/AbstractFilter.java
- ./substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/ColorSchemeFilter.java
-Copyright: Copyright 2005 Sun Microsystems, Inc. (Romain Guy <romain.guy at mac.com>), 4150 Network Circle, Santa Clara, California 95054, U.S.A. All rights reserved.
-License: BSD-3-clause
-
-Files: ./flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/RenderingUtils.java
- ./laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/RenderingUtils.java
- ./laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/LookUtils.java
- ./laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/ShadowPopupBorder.java
- ./substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/*
-Copyright: (c) 2001-2011 JGoodies Karsten Lentzsch. All Rights Reserved.
-License: BSD-3-clause
-
-Files: laf-plugin/*
-Copyright: 2005-2010 Laf-Plugin Kirill Grouchnikov and contributors. All Rights Reserved
- 2005 Kirill Grouchnikov and Erik Vickroy, Robert Beeger, Frederic Lavigne, Pattrick Gotthardt
-License: BSD-3-clause
-
-Files: laf-plugin/src/main/java/org/pushingpixels/lafplugin/XMLElement.java
- laf-plugin/src/main/java/org/pushingpixels/lafplugin/XMLParseException.java
-Copyright: (C) 2000-2002 Marc De Scheemaecker, All Rights Reserved.
-License: NanoXML2
- * This software is provided 'as-is', without any express or implied warranty.
- * In no event will the authors be held liable for any damages arising from the
- * use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- * claim that you wrote the original software. If you use this software in
- * a product, an acknowledgment in the product documentation would be
- * appreciated but is not required.
- *
- * 2. Altered source versions must be plainly marked as such, and must not be
- * misrepresented as being the original software.
- *
- * 3. This notice may not be removed or altered from any source distribution.
-
-Files: laf-widget/*
-Copyright: 2005-2010, Kirill Grouchnikov and contributors, All rights reserved.
-License: BSD-3-clause
-
-Files: ./laf-widget/src/main/java/org/pushingpixels/lafwidget/LAFAdapter.java
- ./laf-widget/src/main/java/org/pushingpixels/lafwidget/preview/InternalFramePreviewPainter.java
- ./laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewControl.java
-Copyright: 2005-2010, Kirill Grouchnikov and contributors, All rights reserved.
-License: BSD-3-clause
-Comment: Most probably covered by LafWidget.license
-
-Files: ./laf-widget/src/main/*.png
- ./substance/*.png
-Copyright: Tango Desktop Project
-License: CC-BY-SA-2.5
- THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS
- CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK
- IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE
- OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT
- LAW IS PROHIBITED.
- .
- BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT
- AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR
- GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR
- ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
- .
- 1. Definitions
- .
- 1. "Collective Work" means a work, such as a periodical issue,
- anthology or encyclopedia, in which the Work in its entirety
- in unmodified form, along with a number of other contributions,
- constituting separate and independent works in themselves,
- are assembled into a collective whole. A work that constitutes
- a Collective Work will not be considered a Derivative Work
- (as defined below) for the purposes of this License.
- 2. "Derivative Work" means a work based upon the Work or upon
- the Work and other pre-existing works, such as a translation,
- musical arrangement, dramatization, fictionalization, motion
- picture version, sound recording, art reproduction, abridgment,
- condensation, or any other form in which the Work may be recast,
- transformed, or adapted, except that a work that constitutes
- a Collective Work will not be considered a Derivative Work for
- the purpose of this License. For the avoidance of doubt, where
- the Work is a musical composition or sound recording, the
- synchronization of the Work in timed-relation with a moving
- image ("synching") will be considered a Derivative Work for
- the purpose of this License.
- 3. "Licensor" means the individual or entity that offers the
- Work under the terms of this License.
- 4. "Original Author" means the individual or entity who
- created the Work.
- 5. "Work" means the copyrightable work of authorship offered
- under the terms of this License.
- 6. "You" means an individual or entity exercising rights under
- this License who has not previously violated the terms of this
- License with respect to the Work, or who has received express
- permission from the Licensor to exercise rights under this
- License despite a previous violation.
- 7. "License Elements" means the following high-level license
- attributes as selected by Licensor and indicated in the title
- of this License: Attribution, ShareAlike.
- .
- 2. Fair Use Rights. Nothing in this license is intended to reduce,
- limit, or restrict any rights arising from fair use, first sale
- or other limitations on the exclusive rights of the copyright
- owner under copyright law or other applicable laws.
- .
- 3. License Grant. Subject to the terms and conditions of this
- License, Licensor hereby grants You a worldwide, royalty-free,
- non-exclusive, perpetual (for the duration of the applicable
- copyright) license to exercise the rights in the Work as stated
- below:
- .
- 1. to reproduce the Work, to incorporate the Work into one
- or more Collective Works, and to reproduce the Work as
- incorporated in the Collective Works;
- 2. to create and reproduce Derivative Works;
- 3. to distribute copies or phonorecords of, display publicly,
- perform publicly, and perform publicly by means of a digital
- audio transmission the Work including as incorporated in
- Collective Works;
- 4. to distribute copies or phonorecords of, display publicly,
- perform publicly, and perform publicly by means of a digital
- audio transmission Derivative Works.
- 5.
- For the avoidance of doubt, where the work is a musical composition:
- 1. Performance Royalties Under Blanket Licenses. Licensor
- waives the exclusive right to collect, whether individually
- or via a performance rights society (e.g. ASCAP, BMI,
- SESAC), royalties for the public performance or public
- digital performance (e.g. webcast) of the Work.
- 2. Mechanical Rights and Statutory Royalties. Licensor
- waives the exclusive right to collect, whether individually
- or via a music rights society or designated agent (e.g.
- Harry Fox Agency), royalties for any phonorecord You
- create from the Work ("cover version") and distribute, subject
- to the compulsory license created by 17 USC Section 115 of the
- US Copyright Act (or the equivalent in other jurisdictions).
- 6. Webcasting Rights and Statutory Royalties. For the avoidance
- of doubt, where the Work is a sound recording, Licensor waives
- the exclusive right to collect, whether individually or via a
- performance-rights society (e.g. SoundExchange), royalties for
- the public digital performance (e.g. webcast) of the Work, subject
- to the compulsory license created by 17 USC Section 114 of the US
- Copyright Act (or the equivalent in other jurisdictions).
- .
- The above rights may be exercised in all media and formats whether
- now known or hereafter devised. The above rights include the right
- to make such modifications as are technically necessary to exercise
- the rights in other media and formats. All rights not expressly
- granted by Licensor are hereby reserved.
- .
- 4. Restrictions.The license granted in Section 3 above is expressly
- made subject to and limited by the following restrictions:
- .
- 1. You may distribute, publicly display, publicly perform,
- or publicly digitally perform the Work only under the terms
- of this License, and You must include a copy of, or the
- Uniform Resource Identifier for, this License with every copy
- or phonorecord of the Work You distribute, publicly display,
- publicly perform, or publicly digitally perform. You may not
- offer or impose any terms on the Work that alter or restrict
- the terms of this License or the recipients' exercise of the
- rights granted hereunder. You may not sublicense the Work.
- You must keep intact all notices that refer to this License
- and to the disclaimer of warranties. You may not distribute,
- publicly display, publicly perform, or publicly digitally
- perform the Work with any technological measures that control
- access or use of the Work in a manner inconsistent with the
- terms of this License Agreement. The above applies to the Work
- as incorporated in a Collective Work, but this does not require
- the Collective Work apart from the Work itself to be made
- subject to the terms of this License. If You create a Collective
- Work, upon notice from any Licensor You must, to the extent
- practicable, remove from the Collective Work any credit as
- required by clause 4(c), as requested. If You create a Derivative
- Work, upon notice from any Licensor You must, to the extent
- practicable, remove from the Derivative Work any credit as required
- by clause 4(c), as requested.
- 2. You may distribute, publicly display, publicly perform, or
- publicly digitally perform a Derivative Work only under the
- terms of this License, a later version of this License with
- the same License Elements as this License, or a Creative Commons
- iCommons license that contains the same License Elements as this
- License (e.g. Attribution-ShareAlike 2.5 Japan). You must
- include a copy of, or the Uniform Resource Identifier for,
- this License or other license specified in the previous sentence
- with every copy or phonorecord of each Derivative Work You
- distribute, publicly display, publicly perform, or publicly
- digitally perform. You may not offer or impose any terms on the
- Derivative Works that alter or restrict the terms of this License
- or the recipients' exercise of the rights granted hereunder, and
- You must keep intact all notices that refer to this License and
- to the disclaimer of warranties. You may not distribute, publicly
- display, publicly perform, or publicly digitally perform the Derivative
- Work with any technological measures that control access or use of the
- Work in a manner inconsistent with the terms of this License Agreement.
- The above applies to the Derivative Work as incorporated in a Collective
- Work, but this does not require the Collective Work apart from the
- Derivative Work itself to be made subject to the terms of this License.
- 3. If you distribute, publicly display, publicly perform, or
- publicly digitally perform the Work or any Derivative Works or
- Collective Works, You must keep intact all copyright notices for
- the Work and provide, reasonable to the medium or means You are
- utilizing: (i) the name of the Original Author (or pseudonym, if
- applicable) if supplied, and/or (ii) if the Original Author and/or
- Licensor designate another party or parties (e.g. a sponsor institute,
- publishing entity, journal) for attribution in Licensor's copyright
- notice, terms of service or by other reasonable means, the name of
- such party or parties; the title of the Work if supplied; to the
- extent reasonably practicable, the Uniform Resource Identifier, if
- any, that Licensor specifies to be associated with the Work, unless
- such URI does not refer to the copyright notice or licensing
- information for the Work; and in the case of a Derivative Work, a
- credit identifying the use of the Work in the Derivative Work (e.g.,
- "French translation of the Work by Original Author," or "Screenplay
- based on original Work by Original Author"). Such credit may be
- implemented in any reasonable manner; provided, however, that in
- the case of a Derivative Work or Collective Work, at a minimum
- such credit will appear where any other comparable authorship credit
- appears and in a manner at least as prominent as such other comparable
- authorship credit.
- .
- 5. Representations, Warranties and Disclaimer
- .
- UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR
- OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES
- OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY
- OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE,
- MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT,
- OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE
- OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS
- DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION
- MAY NOT APPLY TO YOU.
- .
- 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY
- APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON
- ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL,
- PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR
- THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE
- POSSIBILITY OF SUCH DAMAGES.
- .
- 7. Termination
- .
- 1. This License and the rights granted hereunder will
- terminate automatically upon any breach by You of the terms
- of this License. Individuals or entities who have received
- Derivative Works or Collective Works from You under this
- License, however, will not have their licenses terminated
- provided such individuals or entities remain in full compliance
- with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive
- any termination of this License.
- .
- 2. Subject to the above terms and conditions, the license granted
- here is perpetual (for the duration of the applicable copyright in
- the Work). Notwithstanding the above, Licensor reserves the right
- to release the Work under different license terms or to stop
- distributing the Work at any time; provided, however that any such
- election will not serve to withdraw this License (or any other
- license that has been, or is required to be, granted under the
- terms of this License), and this License will continue in full
- force and effect unless terminated as stated above.
- .
- 8. Miscellaneous
- .
- 1. Each time You distribute or publicly digitally perform
- the Work or a Collective Work, the Licensor offers to the
- recipient a license to the Work on the same terms and conditions
- as the license granted to You under this License.
- 2. Each time You distribute or publicly digitally perform a
- Derivative Work, Licensor offers to the recipient a license
- to the original Work on the same terms and conditions as the
- license granted to You under this License.
- 3. If any provision of this License is invalid or unenforceable
- under applicable law, it shall not affect the validity or
- enforceability of the remainder of the terms of this License,
- and without further action by the parties to this agreement,
- such provision shall be reformed to the minimum extent necessary
- to make such provision valid and enforceable.
- 4. No term or provision of this License shall be deemed waived
- and no breach consented to unless such waiver or consent shall
- be in writing and signed by the party to be charged with such
- waiver or consent.
- 5. This License constitutes the entire agreement between the
- parties with respect to the Work licensed here. There are no
- understandings, agreements or representations with respect to
- the Work not specified here. Licensor shall not be bound by
- any additional provisions that may appear in any communication
- from You. This License may not be modified without the mutual
- written agreement of the Licensor and You.
- .
- Creative Commons is not a party to this License, and makes no
- warranty whatsoever in connection with the Work. Creative Commons
- will not be liable to You or any party on any legal theory for
- any damages whatsoever, including without limitation any general,
- special, incidental or consequential damages arising in connection
- to this license. Notwithstanding the foregoing two (2) sentences,
- if Creative Commons has expressly identified itself as the Licensor
- hereunder, it shall have all rights and obligations of Licensor.
- .
- Except for the limited purpose of indicating to the public that
- the Work is licensed under the CCPL, neither party will use the
- trademark "Creative Commons" or any related trademark or logo
- of Creative Commons without the prior written consent of Creative
- Commons. Any permitted use will be in compliance with Creative
- Commons' then-current trademark usage guidelines, as may be
- published on its website or otherwise made available upon request
- from time to time.
- .
- Creative Commons may be contacted at http://creativecommons.org/.
-
-Files: laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/*
-Copyright: 2006-2007 Nigel Hughes
-License: Apache-2.0
-
-Files: substance/*
-Copyright: 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved
-License: BSD-3-clause
-
-Files: ./substance/src/tools/java/tools/jitterbug/substance.java
- ./substance/src/tools/java/tools/docrobot/schemes/TerracottaScheme.java
- ./substance/src/main/java/org/pushingpixels/substance/internal/utils/PerlinNoiseGenerator.java
- ./substance/src/main/java/org/pushingpixels/substance/internal/utils/SkinUtilities.java
-Copyright: 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved
-License: BSD-3-clause
-Comment: Most probably covered by Substance.license
-
-Files: ./substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/*
- ./substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/*
-Copyright: 2003-2006 Werner Randelshofer, Staldenmattweg 2, Immensee, CH-6405, Switzerland
- Christopher Atlan, Steve Roy
-License: LGPL-2.1 or BSD-3-clause
- Use of the Quaqua Look and Feel is entirely at your own risk.
- I will not be liable for any data loss, hardware damage or
- whatever this program might cause.
- .
- Permission to use this release of the Quaqua Look and Feel is
- granted provided you agree with its license terms, that the
- license fee is paid and the copyright notice and this license
- notice appear in all copies and in supporting documentation.
- .
- The license terms applies to the version of the Quaqua Look and
- Feel, which is stated at the top of this document. Different
- versions may have different license terms.
- .
- The license terms of the Quaqua Look and Feel do not apply to
- third party libraries that are included with the Quaqua Look and
- Feel. See section License Terms of Third Party Libraries.
- License Terms
- .
- Source code, documentation and binaries of the Quaqua Look and
- Feel (also called "this software") are subject to the GNU Lesser
- General Public License (LGPL).
- .
- On Debian systems, the complete text of the LGPL License can be
- found in `/usr/share/common-licenses/LGPL-2.1'.
- .
- Alternatively, this software may be used under the terms of the
- Modified BSD License.
-Comment: does quaqua include nanoxml/base64/swing-layout/jbrowser??
-
-Files: ./substance/src/main/java/org/pushingpixels/substance/internal/contrib/xoetrope/editor/*
-Copyright: Xoetrope Ltd. 2002-2005?
-License: MPL-1.1
- MOZILLA PUBLIC LICENSE
- Version 1.1
- .
- ---------------
- .
- 1. Definitions.
- .
- 1.0.1. "Commercial Use" means distribution or otherwise making the
- Covered Code available to a third party.
- .
- 1.1. "Contributor" means each entity that creates or contributes to
- the creation of Modifications.
- .
- 1.2. "Contributor Version" means the combination of the Original
- Code, prior Modifications used by a Contributor, and the Modifications
- made by that particular Contributor.
- .
- 1.3. "Covered Code" means the Original Code or Modifications or the
- combination of the Original Code and Modifications, in each case
- including portions thereof.
- .
- 1.4. "Electronic Distribution Mechanism" means a mechanism generally
- accepted in the software development community for the electronic
- transfer of data.
- .
- 1.5. "Executable" means Covered Code in any form other than Source
- Code.
- .
- 1.6. "Initial Developer" means the individual or entity identified
- as the Initial Developer in the Source Code notice required by Exhibit
- A.
- .
- 1.7. "Larger Work" means a work which combines Covered Code or
- portions thereof with code not governed by the terms of this License.
- .
- 1.8. "License" means this document.
- .
- 1.8.1. "Licensable" means having the right to grant, to the maximum
- extent possible, whether at the time of the initial grant or
- subsequently acquired, any and all of the rights conveyed herein.
- .
- 1.9. "Modifications" means any addition to or deletion from the
- substance or structure of either the Original Code or any previous
- Modifications. When Covered Code is released as a series of files, a
- Modification is:
- A. Any addition to or deletion from the contents of a file
- containing Original Code or previous Modifications.
- .
- B. Any new file that contains any part of the Original Code or
- previous Modifications.
- .
- 1.10. "Original Code" means Source Code of computer software code
- which is described in the Source Code notice required by Exhibit A as
- Original Code, and which, at the time of its release under this
- License is not already Covered Code governed by this License.
- .
- 1.10.1. "Patent Claims" means any patent claim(s), now owned or
- hereafter acquired, including without limitation, method, process,
- and apparatus claims, in any patent Licensable by grantor.
- .
- 1.11. "Source Code" means the preferred form of the Covered Code for
- making modifications to it, including all modules it contains, plus
- any associated interface definition files, scripts used to control
- compilation and installation of an Executable, or source code
- differential comparisons against either the Original Code or another
- well known, available Covered Code of the Contributor's choice. The
- Source Code can be in a compressed or archival form, provided the
- appropriate decompression or de-archiving software is widely available
- for no charge.
- .
- 1.12. "You" (or "Your") means an individual or a legal entity
- exercising rights under, and complying with all of the terms of, this
- License or a future version of this License issued under Section 6.1.
- For legal entities, "You" includes any entity which controls, is
- controlled by, or is under common control with You. For purposes of
- this definition, "control" means (a) the power, direct or indirect,
- to cause the direction or management of such entity, whether by
- contract or otherwise, or (b) ownership of more than fifty percent
- (50%) of the outstanding shares or beneficial ownership of such
- entity.
- .
- 2. Source Code License.
- .
- 2.1. The Initial Developer Grant.
- The Initial Developer hereby grants You a world-wide, royalty-free,
- non-exclusive license, subject to third party intellectual property
- claims:
- (a) under intellectual property rights (other than patent or
- trademark) Licensable by Initial Developer to use, reproduce,
- modify, display, perform, sublicense and distribute the Original
- Code (or portions thereof) with or without Modifications, and/or
- as part of a Larger Work; and
- .
- (b) under Patents Claims infringed by the making, using or
- selling of Original Code, to make, have made, use, practice,
- sell, and offer for sale, and/or otherwise dispose of the
- Original Code (or portions thereof).
- .
- (c) the licenses granted in this Section 2.1(a) and (b) are
- effective on the date Initial Developer first distributes
- Original Code under the terms of this License.
- .
- (d) Notwithstanding Section 2.1(b) above, no patent license is
- granted: 1) for code that You delete from the Original Code; 2)
- separate from the Original Code; or 3) for infringements caused
- by: i) the modification of the Original Code or ii) the
- combination of the Original Code with other software or devices.
- .
- 2.2. Contributor Grant.
- Subject to third party intellectual property claims, each Contributor
- hereby grants You a world-wide, royalty-free, non-exclusive license
- .
- (a) under intellectual property rights (other than patent or
- trademark) Licensable by Contributor, to use, reproduce, modify,
- display, perform, sublicense and distribute the Modifications
- created by such Contributor (or portions thereof) either on an
- unmodified basis, with other Modifications, as Covered Code
- and/or as part of a Larger Work; and
- .
- (b) under Patent Claims infringed by the making, using, or
- selling of Modifications made by that Contributor either alone
- and/or in combination with its Contributor Version (or portions
- of such combination), to make, use, sell, offer for sale, have
- made, and/or otherwise dispose of: 1) Modifications made by that
- Contributor (or portions thereof); and 2) the combination of
- Modifications made by that Contributor with its Contributor
- Version (or portions of such combination).
- .
- (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
- effective on the date Contributor first makes Commercial Use of
- the Covered Code.
- .
- (d) Notwithstanding Section 2.2(b) above, no patent license is
- granted: 1) for any code that Contributor has deleted from the
- Contributor Version; 2) separate from the Contributor Version;
- 3) for infringements caused by: i) third party modifications of
- Contributor Version or ii) the combination of Modifications made
- by that Contributor with other software (except as part of the
- Contributor Version) or other devices; or 4) under Patent Claims
- infringed by Covered Code in the absence of Modifications made by
- that Contributor.
- .
- 3. Distribution Obligations.
- .
- 3.1. Application of License.
- The Modifications which You create or to which You contribute are
- governed by the terms of this License, including without limitation
- Section 2.2. The Source Code version of Covered Code may be
- distributed only under the terms of this License or a future version
- of this License released under Section 6.1, and You must include a
- copy of this License with every copy of the Source Code You
- distribute. You may not offer or impose any terms on any Source Code
- version that alters or restricts the applicable version of this
- License or the recipients' rights hereunder. However, You may include
- an additional document offering the additional rights described in
- Section 3.5.
- .
- 3.2. Availability of Source Code.
- Any Modification which You create or to which You contribute must be
- made available in Source Code form under the terms of this License
- either on the same media as an Executable version or via an accepted
- Electronic Distribution Mechanism to anyone to whom you made an
- Executable version available; and if made available via Electronic
- Distribution Mechanism, must remain available for at least twelve (12)
- months after the date it initially became available, or at least six
- (6) months after a subsequent version of that particular Modification
- has been made available to such recipients. You are responsible for
- ensuring that the Source Code version remains available even if the
- Electronic Distribution Mechanism is maintained by a third party.
- .
- 3.3. Description of Modifications.
- You must cause all Covered Code to which You contribute to contain a
- file documenting the changes You made to create that Covered Code and
- the date of any change. You must include a prominent statement that
- the Modification is derived, directly or indirectly, from Original
- Code provided by the Initial Developer and including the name of the
- Initial Developer in (a) the Source Code, and (b) in any notice in an
- Executable version or related documentation in which You describe the
- origin or ownership of the Covered Code.
- .
- 3.4. Intellectual Property Matters
- (a) Third Party Claims.
- If Contributor has knowledge that a license under a third party's
- intellectual property rights is required to exercise the rights
- granted by such Contributor under Sections 2.1 or 2.2,
- Contributor must include a text file with the Source Code
- distribution titled "LEGAL" which describes the claim and the
- party making the claim in sufficient detail that a recipient will
- know whom to contact. If Contributor obtains such knowledge after
- the Modification is made available as described in Section 3.2,
- Contributor shall promptly modify the LEGAL file in all copies
- Contributor makes available thereafter and shall take other steps
- (such as notifying appropriate mailing lists or newsgroups)
- reasonably calculated to inform those who received the Covered
- Code that new knowledge has been obtained.
- .
- (b) Contributor APIs.
- If Contributor's Modifications include an application programming
- interface and Contributor has knowledge of patent licenses which
- are reasonably necessary to implement that API, Contributor must
- also include this information in the LEGAL file.
- .
- (c) Representations.
- Contributor represents that, except as disclosed pursuant to
- Section 3.4(a) above, Contributor believes that Contributor's
- Modifications are Contributor's original creation(s) and/or
- Contributor has sufficient rights to grant the rights conveyed by
- this License.
- .
- 3.5. Required Notices.
- You must duplicate the notice in Exhibit A in each file of the Source
- Code. If it is not possible to put such notice in a particular Source
- Code file due to its structure, then You must include such notice in a
- location (such as a relevant directory) where a user would be likely
- to look for such a notice. If You created one or more Modification(s)
- You may add your name as a Contributor to the notice described in
- Exhibit A. You must also duplicate this License in any documentation
- for the Source Code where You describe recipients' rights or ownership
- rights relating to Covered Code. You may choose to offer, and to
- charge a fee for, warranty, support, indemnity or liability
- obligations to one or more recipients of Covered Code. However, You
- may do so only on Your own behalf, and not on behalf of the Initial
- Developer or any Contributor. You must make it absolutely clear than
- any such warranty, support, indemnity or liability obligation is
- offered by You alone, and You hereby agree to indemnify the Initial
- Developer and every Contributor for any liability incurred by the
- Initial Developer or such Contributor as a result of warranty,
- support, indemnity or liability terms You offer.
- .
- 3.6. Distribution of Executable Versions.
- You may distribute Covered Code in Executable form only if the
- requirements of Section 3.1-3.5 have been met for that Covered Code,
- and if You include a notice stating that the Source Code version of
- the Covered Code is available under the terms of this License,
- including a description of how and where You have fulfilled the
- obligations of Section 3.2. The notice must be conspicuously included
- in any notice in an Executable version, related documentation or
- collateral in which You describe recipients' rights relating to the
- Covered Code. You may distribute the Executable version of Covered
- Code or ownership rights under a license of Your choice, which may
- contain terms different from this License, provided that You are in
- compliance with the terms of this License and that the license for the
- Executable version does not attempt to limit or alter the recipient's
- rights in the Source Code version from the rights set forth in this
- License. If You distribute the Executable version under a different
- license You must make it absolutely clear that any terms which differ
- from this License are offered by You alone, not by the Initial
- Developer or any Contributor. You hereby agree to indemnify the
- Initial Developer and every Contributor for any liability incurred by
- the Initial Developer or such Contributor as a result of any such
- terms You offer.
- .
- 3.7. Larger Works.
- You may create a Larger Work by combining Covered Code with other code
- not governed by the terms of this License and distribute the Larger
- Work as a single product. In such a case, You must make sure the
- requirements of this License are fulfilled for the Covered Code.
- .
- 4. Inability to Comply Due to Statute or Regulation.
- .
- If it is impossible for You to comply with any of the terms of this
- License with respect to some or all of the Covered Code due to
- statute, judicial order, or regulation then You must: (a) comply with
- the terms of this License to the maximum extent possible; and (b)
- describe the limitations and the code they affect. Such description
- must be included in the LEGAL file described in Section 3.4 and must
- be included with all distributions of the Source Code. Except to the
- extent prohibited by statute or regulation, such description must be
- sufficiently detailed for a recipient of ordinary skill to be able to
- understand it.
- .
- 5. Application of this License.
- .
- This License applies to code to which the Initial Developer has
- attached the notice in Exhibit A and to related Covered Code.
- .
- 6. Versions of the License.
- .
- 6.1. New Versions.
- Netscape Communications Corporation ("Netscape") may publish revised
- and/or new versions of the License from time to time. Each version
- will be given a distinguishing version number.
- .
- 6.2. Effect of New Versions.
- Once Covered Code has been published under a particular version of the
- License, You may always continue to use it under the terms of that
- version. You may also choose to use such Covered Code under the terms
- of any subsequent version of the License published by Netscape. No one
- other than Netscape has the right to modify the terms applicable to
- Covered Code created under this License.
- .
- 6.3. Derivative Works.
- If You create or use a modified version of this License (which you may
- only do in order to apply it to code which is not already Covered Code
- governed by this License), You must (a) rename Your license so that
- the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
- "MPL", "NPL" or any confusingly similar phrase do not appear in your
- license (except to note that your license differs from this License)
- and (b) otherwise make it clear that Your version of the license
- contains terms which differ from the Mozilla Public License and
- Netscape Public License. (Filling in the name of the Initial
- Developer, Original Code or Contributor in the notice described in
- Exhibit A shall not of themselves be deemed to be modifications of
- this License.)
- .
- 7. DISCLAIMER OF WARRANTY.
- .
- COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
- WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
- WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
- DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
- THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
- IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
- YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
- COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
- OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
- ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
- .
- 8. TERMINATION.
- .
- 8.1. This License and the rights granted hereunder will terminate
- automatically if You fail to comply with terms herein and fail to cure
- such breach within 30 days of becoming aware of the breach. All
- sublicenses to the Covered Code which are properly granted shall
- survive any termination of this License. Provisions which, by their
- nature, must remain in effect beyond the termination of this License
- shall survive.
- .
- 8.2. If You initiate litigation by asserting a patent infringement
- claim (excluding declatory judgment actions) against Initial Developer
- or a Contributor (the Initial Developer or Contributor against whom
- You file such action is referred to as "Participant") alleging that:
- .
- (a) such Participant's Contributor Version directly or indirectly
- infringes any patent, then any and all rights granted by such
- Participant to You under Sections 2.1 and/or 2.2 of this License
- shall, upon 60 days notice from Participant terminate prospectively,
- unless if within 60 days after receipt of notice You either: (i)
- agree in writing to pay Participant a mutually agreeable reasonable
- royalty for Your past and future use of Modifications made by such
- Participant, or (ii) withdraw Your litigation claim with respect to
- the Contributor Version against such Participant. If within 60 days
- of notice, a reasonable royalty and payment arrangement are not
- mutually agreed upon in writing by the parties or the litigation claim
- is not withdrawn, the rights granted by Participant to You under
- Sections 2.1 and/or 2.2 automatically terminate at the expiration of
- the 60 day notice period specified above.
- .
- (b) any software, hardware, or device, other than such Participant's
- Contributor Version, directly or indirectly infringes any patent, then
- any rights granted to You by such Participant under Sections 2.1(b)
- and 2.2(b) are revoked effective as of the date You first made, used,
- sold, distributed, or had made, Modifications made by that
- Participant.
- .
- 8.3. If You assert a patent infringement claim against Participant
- alleging that such Participant's Contributor Version directly or
- indirectly infringes any patent where such claim is resolved (such as
- by license or settlement) prior to the initiation of patent
- infringement litigation, then the reasonable value of the licenses
- granted by such Participant under Sections 2.1 or 2.2 shall be taken
- into account in determining the amount or value of any payment or
- license.
- .
- 8.4. In the event of termination under Sections 8.1 or 8.2 above,
- all end user license agreements (excluding distributors and resellers)
- which have been validly granted by You or any distributor hereunder
- prior to termination shall survive termination.
- .
- 9. LIMITATION OF LIABILITY.
- .
- UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
- (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
- DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
- OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
- ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
- CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
- WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
- COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
- INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
- LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
- RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
- PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
- EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
- THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
- .
- 10. U.S. GOVERNMENT END USERS.
- .
- The Covered Code is a "commercial item," as that term is defined in
- 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
- software" and "commercial computer software documentation," as such
- terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
- C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
- all U.S. Government End Users acquire Covered Code with only those
- rights set forth herein.
- .
- 11. MISCELLANEOUS.
- .
- This License represents the complete agreement concerning subject
- matter hereof. If any provision of this License is held to be
- unenforceable, such provision shall be reformed only to the extent
- necessary to make it enforceable. This License shall be governed by
- California law provisions (except to the extent applicable law, if
- any, provides otherwise), excluding its conflict-of-law provisions.
- With respect to disputes in which at least one party is a citizen of,
- or an entity chartered or registered to do business in the United
- States of America, any litigation relating to this License shall be
- subject to the jurisdiction of the Federal Courts of the Northern
- District of California, with venue lying in Santa Clara County,
- California, with the losing party responsible for costs, including
- without limitation, court costs and reasonable attorneys' fees and
- expenses. The application of the United Nations Convention on
- Contracts for the International Sale of Goods is expressly excluded.
- Any law or regulation which provides that the language of a contract
- shall be construed against the drafter shall not apply to this
- License.
- .
- 12. RESPONSIBILITY FOR CLAIMS.
- .
- As between Initial Developer and the Contributors, each party is
- responsible for claims and damages arising, directly or indirectly,
- out of its utilization of rights under this License and You agree to
- work with Initial Developer and Contributors to distribute such
- responsibility on an equitable basis. Nothing herein is intended or
- shall be deemed to constitute any admission of liability.
- .
- 13. MULTIPLE-LICENSED CODE.
- .
- Initial Developer may designate portions of the Covered Code as
- "Multiple-Licensed". "Multiple-Licensed" means that the Initial
- Developer permits you to utilize portions of the Covered Code under
- Your choice of the MPL or the alternative licenses, if any, specified
- by the Initial Developer in the file described in Exhibit A.
- .
- EXHIBIT A -Mozilla Public License.
- .
- ``The contents of this file are subject to the Mozilla Public License
- Version 1.1 (the "License"); you may not use this file except in
- compliance with the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
- .
- Software distributed under the License is distributed on an "AS IS"
- basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
- License for the specific language governing rights and limitations
- under the License.
- .
- The Original Code is ______________________________________.
- .
- The Initial Developer of the Original Code is ________________________.
- Portions created by ______________________ are Copyright (C) ______
- _______________________. All Rights Reserved.
- .
- Contributor(s): ______________________________________.
- .
- Alternatively, the contents of this file may be used under the terms
- of the _____ license (the "[___] License"), in which case the
- provisions of [______] License are applicable instead of those
- above. If you wish to allow use of your version of this file only
- under the terms of the [____] License and not to allow others to use
- your version of this file under the MPL, indicate your decision by
- deleting the provisions above and replace them with the notice and
- other provisions required by the [___] License. If you do not delete
- the provisions above, a recipient may use your version of this file
- under either the MPL or the [___] License."
- .
- [NOTE: The text of this Exhibit A may differ slightly from the text of
- the notices in the Source Code files of the Original Code. You should
- use the text of this Exhibit A rather than the text found in the
- Original Code Source Code for Your Modifications.]
-
-Files: substance-flamingo/*
-Copyright: 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
-License: BSD-3-clause
-
-Files: substance-swingx/*
-Copyright: 2005-2010 Kirill Grouchnikov, based on work by Sun Microsystems, Inc.
-License: LGPL-2.1+
- * Copyright 2005-2010 Kirill Grouchnikov, based on work by
- * Sun Microsystems, Inc. All rights reserved.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-Comment: images?
-
-Files: trident/*
-Copyright: 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved
-License: BSD-3-clause
-
-Files: trident/src/main/java/org/pushingpixels/trident/interpolator/Key*.java
-Copyright: 2006, Sun Microsystems, Inc
-License: BSD-3-clause
-
-License: BSD-3-clause
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- .
- o Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- .
- o Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
- .
- o Neither the name of Trident Kirill Grouchnikov nor the names of
- its contributors may be used to endorse or promote products derived
- from this software without specific prior written permission.
- .
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
- OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
- EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-License: Apache-2.0
- see /usr/share/common-licenses/Apache-2.0
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 7528c69..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/make -f
-# See debhelper(7) (uncomment to enable)
-# output every command that modifies files on the build system.
-#DH_VERBOSE = 1
-
-%:
- dh $@ --with maven_repo_helper --buildsystem=gradle
-
-override_dh_auto_build:
- dh_auto_build -- -x test check assemble
-
-# Get original sources directly using uscan
-get-orig-source:
- uscan --force-download --rename --repack
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index f2c4d4c..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,5 +0,0 @@
-# this matches /Insubstantial/insubstantial/archive/insubstantial_7.3.tar.gz
-
-version=3
-opts=dversionmangle=s/\+dfsg[1-9]//\
- https://github.com/Insubstantial/insubstantial/releases .*/insubstantial_(\d[\d\.]*)\.tar\.gz
diff --git a/flamingo/README.md b/flamingo/README.md
new file mode 100755
index 0000000..8a27293
--- /dev/null
+++ b/flamingo/README.md
@@ -0,0 +1,18 @@
+# Flamingo
+
+## Building
+
+### In Eclipse
+
+1. Create new Java Project (_**File > New > Java Project**_)
+1. Browse for the location of the _insubstantial/flamingo_ directory
+1. Click **Next**
+1. Go to the _**Libraries**_ tab and select the **Add JARs...** button
+1. Select all the jars in the _flamingo/lib_ and _flamingo/lib/test_ directories
+1. Click **Finish**
+1. After the project is created, ensure that the following directories are added to the build path if not already on the build path:
+ - _flamingo/src/main/java_
+ - _flamingo/src/main/resources_
+ - _flamingo/src/test/java_
+ - _flamingo/src/test/resources_
+1. Run the project as a **JUnit Test**; everything should <span style="color:green;">pass</span>
diff --git a/flamingo/build.gradle b/flamingo/build.gradle
new file mode 100755
index 0000000..cc2633a
--- /dev/null
+++ b/flamingo/build.gradle
@@ -0,0 +1,93 @@
+dependencies {
+ compile project(":trident")
+ testCompile group: 'com.jgoodies', name: 'forms', version: '1.2.0'
+ testCompile group: 'junit', name: 'junit', version: '4.3.1'
+ testCompile group: 'org.easytesting', name: 'fest-assert', version: '1.2'
+ testCompile group: 'org.easytesting', name: 'fest-reflect', version: '1.2'
+ testCompile group: 'org.easytesting', name: 'fest-swing', version: '1.2.1'
+ testCompile group: 'org.easytesting', name: 'fest-swing-junit', version: '1.2.1'
+ testCompile group: 'org.easytesting', name: 'fest-swing-junit-4.3.1', version: '1.2.1'
+}
+
+sourceSets {
+ main
+ test
+}
+
+test {
+ // if we are headless, don't run our tests
+ enabled = !Boolean.getBoolean("java.awt.headless")
+}
+
+jar {
+ manifest {
+ attributes(
+ "Flamingo-Version": version,
+ "Flamingo-VersionName": versionKey,
+ )
+ }
+}
+
+task testJar(type: Jar) {
+ classifier = 'tst'
+
+ from sourceSets.test.output
+
+ manifest {
+ attributes(
+ "Flamingo-Version": version,
+ "Flamingo-VersionName": versionKey,
+ )
+ }
+}
+
+uploadPublished {
+ repositories {
+ mavenDeployer {
+ configurePOM(pom)
+ }
+ }
+}
+
+install {
+ configurePOM(repositories.mavenInstaller.pom)
+}
+
+private def configurePOM(def pom) {
+ configureBasePom(pom)
+ pom.project {
+ name "flamingo"
+ description "A fork of @kirilcool's flamingo project"
+ url "http://insubstantial.github.com/insubstantial/flamingo/"
+ licenses {
+ license {
+ name 'The BSD License'
+ url 'http://www.opensource.org/licenses/bsd-license.php'
+ distribution 'repo'
+ }
+ }
+ }
+ // deal with a gradle bug where transitive=false is not passed into the generated POM
+ pom.whenConfigured {cpom ->
+ cpom.dependencies.each {dep ->
+ switch (dep.artifactId) {
+ case 'trident':
+ dep.classifier = 'swing'
+ break
+ }
+ }
+ }
+}
+
+task testRibbon(type: JavaExec) {
+ main = 'test.ribbon.BasicCheckRibbon'
+ debug = Boolean.valueOf(System.getProperty('debug', 'false'))
+ classpath = sourceSets.test.runtimeClasspath
+}
+
+task testRibbonRTL(type: JavaExec) {
+ main = 'test.ribbon.BasicCheckRibbon'
+ debug = Boolean.valueOf(System.getProperty('debug', 'false'))
+ classpath = sourceSets.test.runtimeClasspath
+ systemProperty "user.language", "iw"
+}
diff --git a/flamingo/lib/test/debug-1.0.jar b/flamingo/lib/test/debug-1.0.jar
new file mode 100644
index 0000000..646e94d
Binary files /dev/null and b/flamingo/lib/test/debug-1.0.jar differ
diff --git a/flamingo/settings.gradle b/flamingo/settings.gradle
new file mode 100755
index 0000000..14a4015
--- /dev/null
+++ b/flamingo/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'flamingo'
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbBarCallBack.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbBarCallBack.java
new file mode 100644
index 0000000..f072c26
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbBarCallBack.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2003-2010 Flamingo Kirill Grouchnikov
+ * and <a href="http://www.topologi.com">Topologi</a>.
+ * Contributed by <b>Rick Jelliffe</b> of <b>Topologi</b>
+ * in January 2006.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov Topologi nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.bcb;
+
+import java.io.InputStream;
+import java.util.List;
+
+import org.pushingpixels.flamingo.api.common.StringValuePair;
+
+/**
+ * The application callback that can be set on {@link JBreadcrumbBar}.
+ *
+ * @param <T>
+ * Type of data associated with each breadcrumb bar item.
+ */
+public abstract class BreadcrumbBarCallBack<T> {
+ /**
+ * Sets up the callback.
+ *
+ * @throws BreadcrumbBarException
+ * Runtime exception that wraps the cause. Is thrown only when
+ * {@link #setThrowsExceptions(boolean)} has been called with
+ * <code>true</code> parameter.
+ */
+ public void setup() throws BreadcrumbBarException {
+ }
+
+ /**
+ * If <code>true</code>, some of the operations will throw
+ * {@link BreadcrumbBarException}.
+ */
+ protected boolean throwsExceptions;
+
+ /**
+ * Sets the indication whether the operations of this breadcrumb bar will
+ * throw {@link BreadcrumbBarException}.
+ *
+ * @param throwsExceptions
+ * If <code>true</code>, the operations of this breadcrumb bar
+ * will throw {@link BreadcrumbBarException}.
+ */
+ public void setThrowsExceptions(boolean throwsExceptions) {
+ this.throwsExceptions = throwsExceptions;
+ }
+
+ /**
+ * Returns the choice element that corresponds to the specified path. If the
+ * path is empty, <code>null</code> should be returned. If path is
+ * <code>null</code>, the "root" elements should be returned
+ *
+ * @param path
+ * Breadcrumb bar path.
+ * @return The choice element that corresponds to the specified path
+ * @throws BreadcrumbBarException
+ * Runtime exception that wraps the cause. Is thrown only when
+ * {@link #setThrowsExceptions(boolean)} has been called with
+ * <code>true</code> parameter.
+ */
+ public List<StringValuePair<T>> getPathChoices(List<BreadcrumbItem<T>> path)
+ throws BreadcrumbBarException {
+ return null;
+ }
+
+ /**
+ * Returns the choice element that corresponds to the specified path. If the
+ * path is empty, <code>null</code> should be returned. If path is
+ * <code>null</code>, the "root" elements should be returned
+ *
+ * @param path
+ * Breadcrumb bar path.
+ * @return The choice element that corresponds to the specified path
+ * @throws BreadcrumbBarException
+ * Runtime exception that wraps the cause. Is thrown only when
+ * {@link #setThrowsExceptions(boolean)} has been called with
+ * <code>true</code> parameter.
+ */
+ public List<StringValuePair<T>> getLeafs(List<BreadcrumbItem<T>> path)
+ throws BreadcrumbBarException {
+ return null;
+ }
+
+ /**
+ * Returns the input stream with the leaf content. Some implementations may
+ * return <code>null</code> if this is not applicable.
+ *
+ * @param leaf
+ * Leaf.
+ * @return Input stream with the leaf content. May be <code>null</code> if
+ * this is not applicable.
+ * @throws BreadcrumbBarException
+ * Runtime exception that wraps the cause. Is thrown only when
+ * {@link #setThrowsExceptions(boolean)} has been called with
+ * <code>true</code> parameter.
+ */
+ public InputStream getLeafContent(T leaf) throws BreadcrumbBarException {
+ return null;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbBarException.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbBarException.java
new file mode 100644
index 0000000..4cc5f36
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbBarException.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2003-2010 Flamingo Kirill Grouchnikov
+ * and <a href="http://www.topologi.com">Topologi</a>.
+ * Contributed by <b>Rick Jelliffe</b> of <b>Topologi</b>
+ * in January 2006.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov Topologi nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.bcb;
+
+/**
+ * Generic runtime exception for the breadcrumb bar.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BreadcrumbBarException extends RuntimeException {
+ /**
+ * Creates the new exception instance.
+ *
+ * @param cause
+ * Exception cause.
+ */
+ public BreadcrumbBarException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbBarExceptionHandler.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbBarExceptionHandler.java
new file mode 100644
index 0000000..f682b12
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbBarExceptionHandler.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2003-2010 Flamingo Kirill Grouchnikov
+ * and <a href="http://www.topologi.com">Topologi</a>.
+ * Contributed by <b>Rick Jelliffe</b> of <b>Topologi</b>
+ * in January 2006.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov Topologi nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.bcb;
+
+/**
+ * Generic exception handler for the breadcrumb bar.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface BreadcrumbBarExceptionHandler {
+ /**
+ * Called when an exceptional condition occurs in the breadcrumb bar.
+ *
+ * @param t
+ * Throwable.
+ */
+ public void onException(Throwable t);
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbBarModel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbBarModel.java
new file mode 100644
index 0000000..8a14af5
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbBarModel.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2003-2010 Flamingo Kirill Grouchnikov
+ * and <a href="http://www.topologi.com">Topologi</a>.
+ * Contributed by <b>Rick Jelliffe</b> of <b>Topologi</b>
+ * in January 2006.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov Topologi nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.bcb;
+
+import java.util.*;
+
+import javax.swing.event.EventListenerList;
+
+/**
+ * Model for the breadcrumb bar component ({@link JBreadcrumbBar}).
+ *
+ * @param <T>
+ * Type of data associated with each breadcrumb bar item.
+ * @author Kirill Grouchnikov
+ */
+public class BreadcrumbBarModel<T> {
+ /**
+ * The list of breadcrumb items.
+ */
+ private LinkedList<BreadcrumbItem<T>> items;
+
+ /**
+ * Listener list.
+ */
+ protected EventListenerList listenerList;
+
+ /**
+ * Indication whether the model is in cumulative mode.
+ *
+ * @see #setCumulative(boolean)
+ */
+ protected boolean isCumulative;
+
+ /**
+ * Smallest index of path change since the last call to
+ * {@link #setCumulative(boolean)} with <code>true</code>.
+ */
+ protected int smallestCumulativeIndex;
+
+ /**
+ * Creates a new empty model.
+ */
+ public BreadcrumbBarModel() {
+ this.items = new LinkedList<BreadcrumbItem<T>>();
+ this.listenerList = new EventListenerList();
+ this.isCumulative = false;
+ this.smallestCumulativeIndex = -1;
+ }
+
+ /**
+ * Returns the index of the specified item.
+ *
+ * @param item
+ * Item.
+ * @return Index of the item if it is in the model or -1 if it is not.
+ */
+ public int indexOf(BreadcrumbItem<T> item) {
+ return this.items.indexOf(item);
+ }
+
+ /**
+ * Removes the last item in this model.
+ */
+ public void removeLast() {
+ this.items.removeLast();
+ this.firePathChanged(this.items.size());
+ }
+
+ /**
+ * Resets this model, removing all the items.
+ */
+ public void reset() {
+ this.items.clear();
+ this.firePathChanged(0);
+ }
+
+ /**
+ * Returns an unmodifiable list of the items in this model.
+ *
+ * @return Unmodifiable list of the items in this model.
+ */
+ public List<BreadcrumbItem<T>> getItems() {
+ return Collections.unmodifiableList(this.items);
+ }
+
+ /**
+ * Returns the number of items in this model.
+ *
+ * @return Number of items in this model.
+ */
+ public int getItemCount() {
+ return this.items.size();
+ }
+
+ /**
+ * Returns the model item at the specified index.
+ *
+ * @param index
+ * Item index.
+ * @return The model item at the specified index. Will return
+ * <code>null</code> if the index is negative or larger than the
+ * number of items.
+ */
+ public BreadcrumbItem<T> getItem(int index) {
+ if (index < 0)
+ return null;
+ if (index >= this.getItemCount())
+ return null;
+ return this.items.get(index);
+ }
+
+ /**
+ * Replaces the current item list with the specified list.
+ *
+ * @param items
+ * New contents of the model.
+ */
+ public void replace(List<BreadcrumbItem<T>> items) {
+ this.items.clear();
+ for (int i = 0; i < items.size(); i++) {
+ this.items.addLast(items.get(i));
+ }
+ this.firePathChanged(0);
+ }
+
+ /**
+ * Adds the specified item at the end of the path.
+ *
+ * @param item
+ * Item to add.
+ */
+ public void addLast(BreadcrumbItem<T> item) {
+ this.items.addLast(item);
+ this.firePathChanged(this.items.size() - 1);
+ }
+
+ /**
+ * Starts or ends the cumulative mode. In cumulative mode calls to
+ * {@link #addLast(BreadcrumbItem)}, {@link #removeLast()},
+ * {@link #replace(List)} and {@link #reset()} will not fire events on the
+ * listeners registered with
+ * {@link #addPathListener(BreadcrumbPathListener)}.
+ *
+ * @param isCumulative
+ * If <code>true</code>, the model enters cumulative mode. If
+ * <code>false</code>, the model exist cumulative mode and fires
+ * a path event on all registered listeners with the smallest
+ * index of all changes that have happened since the last time
+ * this method was called with <code>true</code>.
+ */
+ public void setCumulative(boolean isCumulative) {
+ boolean toFire = this.isCumulative && !isCumulative;
+ this.isCumulative = isCumulative;
+
+ if (toFire) {
+ this.firePathChanged(Math.max(0, this.smallestCumulativeIndex));
+ this.smallestCumulativeIndex = -1;
+ }
+ }
+
+ /**
+ * Adds the specified path listener to this model.
+ *
+ * @param l
+ * Path listener to add.
+ */
+ public void addPathListener(BreadcrumbPathListener l) {
+ this.listenerList.add(BreadcrumbPathListener.class, l);
+ }
+
+ /**
+ * Removes the specified path listener from this model.
+ *
+ * @param l
+ * Path listener to remove.
+ */
+ public void removePathListener(BreadcrumbPathListener l) {
+ this.listenerList.remove(BreadcrumbPathListener.class, l);
+ }
+
+ /**
+ * Fires a {@link BreadcrumbPathEvent}.
+ *
+ * @param indexOfFirstChange
+ * Index of the first item that has changed in the model.
+ */
+ protected void firePathChanged(int indexOfFirstChange) {
+ if (this.isCumulative) {
+ if (this.smallestCumulativeIndex == -1)
+ this.smallestCumulativeIndex = indexOfFirstChange;
+ else
+ this.smallestCumulativeIndex = Math.min(
+ this.smallestCumulativeIndex, indexOfFirstChange);
+ return;
+ }
+ BreadcrumbPathEvent event = new BreadcrumbPathEvent(this,
+ indexOfFirstChange);
+ // Guaranteed to return a non-null array
+ Object[] listeners = this.listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == BreadcrumbPathListener.class) {
+ ((BreadcrumbPathListener) listeners[i + 1])
+ .breadcrumbPathEvent(event);
+ }
+ }
+ }
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbItem.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbItem.java
new file mode 100644
index 0000000..f6f812a
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbItem.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2003-2010 Flamingo Kirill Grouchnikov
+ * and <a href="http://www.topologi.com">Topologi</a>.
+ * Contributed by <b>Rick Jelliffe</b> of <b>Topologi</b>
+ * in January 2006. in All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov Topologi nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.bcb;
+
+import javax.swing.Icon;
+
+/**
+ * A single item in the {@link JBreadcrumbBar} model.
+ *
+ * @param <T>
+ * Type of associated data.
+ */
+public final class BreadcrumbItem<T> {
+ /**
+ * Display key for this item.
+ */
+ protected String key;
+
+ /**
+ * Data value for this item.
+ */
+ protected T data;
+
+ /**
+ * The index of <code>this</code> item.
+ */
+ private int index = 0;
+
+ /**
+ * The optional icon.
+ */
+ private Icon icon;
+
+ /**
+ * Creates a new item.
+ *
+ * @param key
+ * Item key.
+ * @param data
+ * Item data.
+ */
+ public BreadcrumbItem(String key, T data) {
+ this.key = key;
+ this.data = data;
+ }
+
+ /**
+ * Creates a new item.
+ *
+ * @param s
+ * String that will be used for display purposes.
+ */
+ public BreadcrumbItem(String s) {
+ this(s, null);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public T getData() {
+ return data;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ /**
+ * Returns the index of <code>this</code> item.
+ *
+ * @return The index of <code>this</code> item.
+ */
+ public int getIndex() {
+ return this.index;
+ }
+
+ /**
+ * Sets the index of <code>this</code> item.
+ *
+ * @param index
+ * The new index of <code>this</code> item.
+ */
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getKey() + ":" + getData();
+ }
+
+ /**
+ * Returns the icon of <code>this</code> item.
+ *
+ * @return The icon of <code>this</code> item.
+ */
+ public Icon getIcon() {
+ return icon;
+ }
+
+ /**
+ * Sets the new icon on <code>this</code> item.
+ *
+ * @param icon
+ * The new icon for <code>this</code> item.
+ */
+ public void setIcon(Icon icon) {
+ this.icon = icon;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbPathEvent.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbPathEvent.java
new file mode 100644
index 0000000..1ec18f9
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbPathEvent.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2003-2010 Flamingo Kirill Grouchnikov
+ * and <a href="http://www.topologi.com">Topologi</a>.
+ * Contributed by <b>Rick Jelliffe</b> of <b>Topologi</b>
+ * in January 2006.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov Topologi nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.bcb;
+
+/**
+ * Event on the breadcrumb bar path.
+ */
+public class BreadcrumbPathEvent {
+ /**
+ * The object that fired <code>this</code> event.
+ */
+ private Object src;
+
+ /**
+ * The index of the first path item that has changed.
+ */
+ private int indexOfFirstChange;
+
+ /**
+ * Creates a new breadcrumb bar path event.
+ *
+ * @param src
+ * Event source.
+ * @param indexOfFirstChange
+ * The index of the first path item that has changed.
+ */
+ public BreadcrumbPathEvent(Object src, int indexOfFirstChange) {
+ this.src = src;
+ this.indexOfFirstChange = indexOfFirstChange;
+ }
+
+ /**
+ * Returns the index of the first path item that has changed.
+ *
+ * @return The index of the first path item that has changed.
+ */
+ public int getIndexOfFirstChange() {
+ return this.indexOfFirstChange;
+ }
+
+ /**
+ * Returns the source of <code>this</code> event.
+ *
+ * @return The source of <code>this</code> event.
+ */
+ public Object getSource() {
+ return this.src;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbPathListener.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbPathListener.java
new file mode 100644
index 0000000..7ecacbc
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/BreadcrumbPathListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2003-2010 Flamingo Kirill Grouchnikov
+ * and <a href="http://www.topologi.com">Topologi</a>.
+ * Contributed by <b>Rick Jelliffe</b> of <b>Topologi</b>
+ * in January 2006.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov Topologi nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.bcb;
+
+import java.util.EventListener;
+
+/**
+ * The application listener on breadcrumb path events.
+ */
+public interface BreadcrumbPathListener extends EventListener {
+ /**
+ * Called on breadcrumb bar events.
+ *
+ * @param event
+ * Breadcrumb bar event.
+ */
+ public void breadcrumbPathEvent(BreadcrumbPathEvent event);
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/JBreadcrumbBar.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/JBreadcrumbBar.java
new file mode 100644
index 0000000..3ac29c9
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/JBreadcrumbBar.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2003-2010 Flamingo Kirill Grouchnikov
+ * and <a href="http://www.topologi.com">Topologi</a>.
+ * Contributed by <b>Rick Jelliffe</b> of <b>Topologi</b>
+ * in January 2006.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov Topologi nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.bcb;
+
+import java.util.*;
+
+import javax.swing.*;
+
+import org.pushingpixels.flamingo.internal.ui.bcb.BasicBreadcrumbBarUI;
+import org.pushingpixels.flamingo.internal.ui.bcb.BreadcrumbBarUI;
+
+/**
+ * Breadcrumb bar. It is basically a way of lazily navigating around a tree, but
+ * just by manipulating the sections of a path.
+ *
+ * @param <T>
+ * Type of data associated with each breadcrumb bar item.
+ * @author Kirill Grouchnikov
+ */
+public class JBreadcrumbBar<T> extends JComponent {
+ /**
+ * Serial version ID.
+ */
+ private static final long serialVersionUID = 3258407339731400502L;
+
+ /**
+ * The breadcrumb bar model.
+ */
+ protected BreadcrumbBarModel<T> model;
+
+ /**
+ * Application callback. Used to retrieve choices for the activated
+ * selector.
+ */
+ protected BreadcrumbBarCallBack<T> callback;
+
+ /**
+ * List of registered exception handlers.
+ */
+ protected List<BreadcrumbBarExceptionHandler> exceptionHandlers;
+
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "BreadcrumbBarUI";
+
+ /**
+ * Base interface for elements in breadcrumb bar.
+ */
+ public interface BreadcrumbBarElement {
+ /**
+ * Returns the text presentation of <code>this</code> element.
+ *
+ * @return The text presentation of <code>this</code> element.
+ */
+ public String getText();
+
+ /**
+ * Returns the index of <code>this</code> element.
+ *
+ * @return The index of <code>this</code> element.
+ */
+ public int getIndex();
+ }
+
+ /**
+ * Creates a new breadcrumb bar.
+ *
+ * @param callback
+ * The application callback.
+ */
+ public JBreadcrumbBar(final BreadcrumbBarCallBack<T> callback) {
+ super();
+ this.model = new BreadcrumbBarModel<T>();
+ this.callback = callback;
+
+ if (this.callback != null)
+ this.callback.setup();
+
+ this.exceptionHandlers = new ArrayList<BreadcrumbBarExceptionHandler>();
+
+ this.updateUI();
+ }
+
+ /**
+ * Sets new path as the current path in <code>this</code> breadcrumb bar.
+ *
+ * @param newPath
+ * New path for <code>this</code> breadcrumb bar.
+ */
+ public void setPath(List<BreadcrumbItem<T>> newPath) {
+ this.getModel().replace(newPath);
+ }
+
+ /**
+ * Returns the application callback.
+ *
+ * @return The application callback.
+ */
+ public BreadcrumbBarCallBack<T> getCallback() {
+ return this.callback;
+ }
+
+ /**
+ * Sets the new UI delegate.
+ *
+ * @param ui
+ * New UI delegate.
+ */
+ public void setUI(BreadcrumbBarUI ui) {
+ super.setUI(ui);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((BreadcrumbBarUI) UIManager.getUI(this));
+ } else {
+ setUI(new BasicBreadcrumbBarUI());
+ }
+ }
+
+ /**
+ * Returns the UI object which implements the L&F for this component.
+ *
+ * @return a <code>BreadcrumbBarUI</code> object
+ * @see #setUI(org.pushingpixels.flamingo.internal.ui.bcb.BreadcrumbBarUI)
+ */
+ public BreadcrumbBarUI getUI() {
+ return (BreadcrumbBarUI) ui;
+ }
+
+ /**
+ * Returns the name of the UI class that implements the L&F for this
+ * component.
+ *
+ * @return the string "BreadcrumbBarUI"
+ * @see JComponent#getUIClassID
+ * @see UIDefaults#getUI(javax.swing.JComponent)
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /**
+ * Registers the specified exception handler.
+ *
+ * @param handler
+ * Exception handler.
+ */
+ public void addExceptionHandler(BreadcrumbBarExceptionHandler handler) {
+ this.exceptionHandlers.add(handler);
+ }
+
+ /**
+ * Unregisters the specified exception handler.
+ *
+ * @param handler
+ * Exception handler.
+ */
+ public void removeExceptionHandler(BreadcrumbBarExceptionHandler handler) {
+ this.exceptionHandlers.remove(handler);
+ }
+
+ /**
+ * Returns the list of currently registered exception handlers.
+ *
+ * @return List of currently registered exception handlers.
+ */
+ public List<BreadcrumbBarExceptionHandler> getExceptionHandlers() {
+ return Collections.unmodifiableList(this.exceptionHandlers);
+ }
+
+ /**
+ * Sets the indication whether the operations of this breadcrumb bar will
+ * throw {@link BreadcrumbBarException}.
+ *
+ * @param throwsExceptions
+ * If <code>true</code>, the operations of this breadcrumb bar
+ * will throw {@link BreadcrumbBarException}.
+ */
+ public void setThrowsExceptions(boolean throwsExceptions) {
+ if (this.callback != null) {
+ this.callback.setThrowsExceptions(throwsExceptions);
+ }
+ }
+
+ /**
+ * Returns the model of this breadcrumb bar.
+ *
+ * @return The model of this breadcrumb bar.
+ */
+ public BreadcrumbBarModel<T> getModel() {
+ return this.model;
+ }
+}
\ No newline at end of file
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/core/BreadcrumbFileSelector.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/core/BreadcrumbFileSelector.java
new file mode 100644
index 0000000..a8a3493
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/core/BreadcrumbFileSelector.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.bcb.core;
+
+import java.io.*;
+import java.util.*;
+
+import javax.swing.filechooser.FileSystemView;
+
+import org.pushingpixels.flamingo.api.bcb.*;
+import org.pushingpixels.flamingo.api.common.StringValuePair;
+
+/**
+ * Breadcrumb bar that allows browsing the local file system.
+ *
+ * @author Kirill Grouchnikov
+ * @author Brian Young
+ */
+public class BreadcrumbFileSelector extends JBreadcrumbBar<File> {
+ /**
+ * If <code>true</code>, the path selectors will use native icons.
+ */
+ protected boolean useNativeIcons;
+
+ /**
+ * Local file system specific implementation of the
+ * {@link BreadcrumbBarCallBack}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class DirCallback extends BreadcrumbBarCallBack<File> {
+ /**
+ * File system view.
+ */
+ protected FileSystemView fsv;
+
+ /**
+ * If <code>true</code>, the path selectors will use native icons.
+ */
+ protected boolean useNativeIcons;
+
+ /**
+ * Creates a new callback.
+ *
+ * @param useNativeIcons
+ * If <code>true</code>, the path selectors will use native
+ * icons.
+ */
+ public DirCallback(boolean useNativeIcons) {
+ this(FileSystemView.getFileSystemView(), useNativeIcons);
+ }
+
+ /**
+ * Creates a new callback.
+ *
+ * @param fileSystemView
+ * File system view to use.
+ * @param useNativeIcons
+ * If <code>true</code>, the path selectors will use native
+ * icons.
+ */
+ public DirCallback(FileSystemView fileSystemView, boolean useNativeIcons) {
+ this.fsv = fileSystemView;
+ this.useNativeIcons = useNativeIcons;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#setup()
+ */
+ @Override
+ public void setup() {
+ }
+
+ @Override
+ public List<StringValuePair<File>> getPathChoices(
+ List<BreadcrumbItem<File>> path) {
+ synchronized (fsv) {
+ if (path == null) {
+ LinkedList<StringValuePair<File>> bRoots = new LinkedList<StringValuePair<File>>();
+ for (File root : fsv.getRoots()) {
+ if (fsv.isHiddenFile(root))
+ continue;
+ String systemName = fsv.getSystemDisplayName(root);
+ if (systemName.length() == 0)
+ systemName = root.getAbsolutePath();
+ StringValuePair<File> rootPair = new StringValuePair<File>(
+ systemName, root);
+ if (useNativeIcons)
+ rootPair.set("icon", fsv.getSystemIcon(root));
+ bRoots.add(rootPair);
+ }
+ return bRoots;
+ }
+ if (path.size() == 0)
+ return null;
+ File lastInPath = path.get(path.size() - 1).getData();
+
+ if (!lastInPath.exists())
+ return new ArrayList<StringValuePair<File>>();
+ if (!lastInPath.isDirectory())
+ return null;
+ LinkedList<StringValuePair<File>> lResult = new LinkedList<StringValuePair<File>>();
+ for (File child : lastInPath.listFiles()) {
+ // ignore regular files and hidden directories
+ if (!child.isDirectory())
+ continue;
+ if (fsv.isHiddenFile(child))
+ continue;
+ String childFileName = fsv.getSystemDisplayName(child);
+ if ((childFileName == null) || childFileName.isEmpty())
+ childFileName = child.getName();
+ StringValuePair<File> pair = new StringValuePair<File>(
+ childFileName, child);
+ if (useNativeIcons)
+ pair.set("icon", fsv.getSystemIcon(child));
+ lResult.add(pair);
+ }
+ Collections.sort(lResult,
+ new Comparator<StringValuePair<File>>() {
+ @Override
+ public int compare(StringValuePair<File> o1,
+ StringValuePair<File> o2) {
+ String key1 = fsv.isFileSystemRoot(o1
+ .getValue()) ? o1.getValue()
+ .getAbsolutePath() : o1.getKey();
+ String key2 = fsv.isFileSystemRoot(o2
+ .getValue()) ? o2.getValue()
+ .getAbsolutePath() : o2.getKey();
+ return key1.toLowerCase().compareTo(
+ key2.toLowerCase());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return super.equals(obj);
+ }
+ });
+ return lResult;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#getLeafs(org.jvnet.flamingo
+ * .bcb.BreadcrumbItem<T>[])
+ */
+ @Override
+ public List<StringValuePair<File>> getLeafs(
+ List<BreadcrumbItem<File>> path) {
+ synchronized (fsv) {
+ if ((path == null) || (path.size() == 0))
+ return null;
+ File lastInPath = path.get(path.size() - 1).getData();
+ if (!lastInPath.exists())
+ return new ArrayList<StringValuePair<File>>();
+ if (!lastInPath.isDirectory())
+ return null;
+ LinkedList<StringValuePair<File>> lResult = new LinkedList<StringValuePair<File>>();
+ for (File child : lastInPath.listFiles()) {
+ // ignore directories and hidden directories
+ if (child.isDirectory())
+ continue;
+ if (fsv.isHiddenFile(child))
+ continue;
+ String childFileName = fsv.getSystemDisplayName(child);
+ if ((childFileName == null) || childFileName.isEmpty())
+ childFileName = child.getName();
+ StringValuePair<File> pair = new StringValuePair<File>(
+ childFileName, child);
+ if (useNativeIcons)
+ pair.set("icon", fsv.getSystemIcon(child));
+ lResult.add(pair);
+ }
+ Collections.sort(lResult,
+ new Comparator<StringValuePair<File>>() {
+ @Override
+ public int compare(StringValuePair<File> o1,
+ StringValuePair<File> o2) {
+ return o1.getKey().toLowerCase().compareTo(
+ o2.getKey().toLowerCase());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return super.equals(obj);
+ }
+ });
+ return lResult;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#getLeafContent(java.
+ * lang.Object)
+ */
+ @Override
+ public InputStream getLeafContent(File leaf) {
+ try {
+ return new FileInputStream(leaf);
+ } catch (FileNotFoundException fnfe) {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Creates a new breadcrumb bar file selector that uses native icons and the
+ * default file system view.
+ */
+ public BreadcrumbFileSelector() {
+ this(true);
+ }
+
+ /**
+ * Creates a new breadcrumb bar file selector that uses the default file
+ * system view.
+ *
+ * @param useNativeIcons
+ * If <code>true</code>, the path selectors will use native
+ * icons.
+ */
+ public BreadcrumbFileSelector(boolean useNativeIcons) {
+ this(FileSystemView.getFileSystemView(), useNativeIcons);
+ }
+
+ /**
+ * Creates a new breadcrumb bar file selector.
+ *
+ * @param fileSystemView
+ * File system view.
+ * @param useNativeIcons
+ * If <code>true</code>, the path selectors will use native
+ * icons.
+ */
+ public BreadcrumbFileSelector(FileSystemView fileSystemView,
+ boolean useNativeIcons) {
+ super(new DirCallback(fileSystemView, useNativeIcons));
+ this.useNativeIcons = useNativeIcons;
+ }
+
+ /**
+ * Sets indication whether the path selectors should use native icons.
+ *
+ * @param useNativeIcons
+ * If <code>true</code>, the path selectors will use native
+ * icons.
+ */
+ public void setUseNativeIcons(boolean useNativeIcons) {
+ this.useNativeIcons = useNativeIcons;
+ }
+
+ /**
+ * Sets the selected path based of the specified file. If this file is
+ * either <code>null</code> or not a directory, the home directory is
+ * selected.
+ *
+ * @param dir
+ * Points to a directory to be selected.
+ */
+ public void setPath(File dir) {
+ // System.out.println(dir.getAbsolutePath());
+
+ FileSystemView fsv = FileSystemView.getFileSystemView();
+
+ synchronized (fsv) {
+ if ((dir == null) || !dir.isDirectory()) {
+ dir = fsv.getHomeDirectory();
+ }
+
+ ArrayList<BreadcrumbItem<File>> path = new ArrayList<BreadcrumbItem<File>>();
+ File parent = dir;
+ BreadcrumbItem<File> bci = new BreadcrumbItem<File>(fsv
+ .getSystemDisplayName(dir), dir);
+ bci.setIcon(fsv.getSystemIcon(dir));
+ path.add(bci);
+ while (true) {
+ parent = fsv.getParentDirectory(parent);
+ if (parent == null)
+ break;
+ bci = new BreadcrumbItem<File>(
+ fsv.getSystemDisplayName(parent), parent);
+ bci.setIcon(fsv.getSystemIcon(parent));
+ path.add(bci);
+ }
+ Collections.reverse(path);
+ this.setPath(path);
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/core/BreadcrumbTreeAdapterSelector.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/core/BreadcrumbTreeAdapterSelector.java
new file mode 100644
index 0000000..5449afa
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/bcb/core/BreadcrumbTreeAdapterSelector.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.bcb.core;
+
+import java.awt.Component;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.tree.TreeModel;
+
+import org.pushingpixels.flamingo.api.bcb.*;
+import org.pushingpixels.flamingo.api.common.StringValuePair;
+
+/**
+ * Breadcrumb bar that allows wrapping an existing {@link JTree} or a
+ * {@link TreeModel}.
+ *
+ * <ul>
+ * <li>Use
+ * {@link BreadcrumbTreeAdapterSelector#BreadcrumbTreeAdapterSelector(JTree)} to
+ * wrap an existing tree that has a {@link JLabel} based renderer.</li>
+ * <li>Use a
+ * {@link BreadcrumbTreeAdapterSelector#BreadcrumbTreeAdapterSelector(JTree, TreeAdapter)}
+ * to wrap an existing tree and provide a custom breadcrumb bar path renderer.</li>
+ * <li>Use
+ * {@link BreadcrumbTreeAdapterSelector#BreadcrumbTreeAdapterSelector(TreeModel, TreeAdapter, boolean)}
+ * to wrap an existing tree model.</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BreadcrumbTreeAdapterSelector extends JBreadcrumbBar<Object> {
+ /**
+ * Tree adapter that allows plugging a custom rendering logic.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static abstract class TreeAdapter {
+ /**
+ * Returns the caption for the specified tree node. Note that the
+ * extending class <strong>must</strong> override this method in an
+ * EDT-safe fashion.
+ *
+ * @param node
+ * Tree node.
+ * @return The caption for the specified tree node.
+ */
+ public abstract String toString(final Object node);
+
+ /**
+ * Returns the icon for the specified tree node.
+ *
+ * @param node
+ * Tree node.
+ * @return The icon for the specified tree node.
+ */
+ public Icon getIcon(Object node) {
+ return null;
+ }
+ }
+
+ /**
+ * Tree-adapter specific implementation of the {@link BreadcrumbBarCallBack}
+ * .
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class TreeCallback extends BreadcrumbBarCallBack<Object> {
+ /**
+ * The corresponding tree model.
+ */
+ protected TreeModel treeModel;
+
+ /**
+ * The corresponding tree adapter. Can not be <code>null</code>.
+ */
+ protected TreeAdapter treeAdapter;
+
+ /**
+ * If <code>true</code>, the first selector shows the tree root node. If
+ * <code>false</code>, the first selector shows the tree root child
+ * nodes.
+ */
+ protected boolean isRootVisible;
+
+ /**
+ * Creates the callback.
+ *
+ * @param treeModel
+ * The corresponding tree model.
+ * @param treeAdapter
+ * The corresponding tree adapter. Can not be
+ * <code>null</code>.
+ * @param isRootVisible
+ * If <code>true</code>, the first selector shows the tree
+ * root node. If <code>false</code>, the first selector shows
+ * the tree root child nodes.
+ */
+ public TreeCallback(TreeModel treeModel, TreeAdapter treeAdapter,
+ boolean isRootVisible) {
+ this.treeModel = treeModel;
+ this.treeAdapter = treeAdapter;
+ this.isRootVisible = isRootVisible;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#getPathChoices(java.
+ * util.List)
+ */
+ @Override
+ public List<StringValuePair<Object>> getPathChoices(
+ List<BreadcrumbItem<Object>> path) {
+ if (path == null) {
+ Object root = this.treeModel.getRoot();
+ List<StringValuePair<Object>> bRoots = new LinkedList<StringValuePair<Object>>();
+ if (isRootVisible) {
+ StringValuePair<Object> rootPair = new StringValuePair<Object>(
+ this.treeAdapter.toString(root), root);
+ rootPair.set("icon", this.treeAdapter.getIcon(root));
+ bRoots.add(rootPair);
+ } else {
+ for (int i = 0; i < this.treeModel.getChildCount(root); i++) {
+ Object rootChild = this.treeModel.getChild(root, i);
+ StringValuePair<Object> rootPair = new StringValuePair<Object>(
+ this.treeAdapter.toString(rootChild), rootChild);
+ rootPair.set("icon", this.treeAdapter
+ .getIcon(rootChild));
+ bRoots.add(rootPair);
+ }
+ }
+ return bRoots;
+ }
+ if (path.size() == 0)
+ return null;
+ Object lastInPath = path.get(path.size() - 1).getData();
+
+ if (this.treeModel.isLeaf(lastInPath))
+ return null;
+ LinkedList<StringValuePair<Object>> lResult = new LinkedList<StringValuePair<Object>>();
+ for (int i = 0; i < this.treeModel.getChildCount(lastInPath); i++) {
+ Object child = this.treeModel.getChild(lastInPath, i);
+ if (this.treeModel.isLeaf(child))
+ continue;
+ StringValuePair<Object> pair = new StringValuePair<Object>(
+ this.treeAdapter.toString(child), child);
+ pair.set("icon", this.treeAdapter.getIcon(child));
+ lResult.add(pair);
+ }
+ return lResult;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#getLeafs(java.util.List)
+ */
+ @Override
+ public List<StringValuePair<Object>> getLeafs(
+ List<BreadcrumbItem<Object>> path) {
+ Object lastInPath = path.get(path.size() - 1).getData();
+ if (this.treeModel.isLeaf(lastInPath))
+ return null;
+ LinkedList<StringValuePair<Object>> lResult = new LinkedList<StringValuePair<Object>>();
+ for (int i = 0; i < this.treeModel.getChildCount(lastInPath); i++) {
+ Object child = this.treeModel.getChild(lastInPath, i);
+ if (!this.treeModel.isLeaf(child))
+ continue;
+ StringValuePair<Object> pair = new StringValuePair<Object>(
+ this.treeAdapter.toString(child), child);
+ pair.set("icon", this.treeAdapter.getIcon(child));
+ lResult.add(pair);
+ }
+ return lResult;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#getLeafContent(java.
+ * lang.Object)
+ */
+ @Override
+ public InputStream getLeafContent(Object leaf) {
+ return null;
+ }
+ }
+
+ /**
+ * Creates an adapter for the specified tree model.
+ *
+ * @param treeModel
+ * Tree model.
+ * @param treeAdapter
+ * Tree adapter. Can not be <code>null</code>.
+ * @param isRootVisible
+ * If <code>true</code>, the first selector shows the tree root
+ * node. If <code>false</code>, the first selector shows the tree
+ * root child nodes.
+ */
+ public BreadcrumbTreeAdapterSelector(TreeModel treeModel,
+ TreeAdapter treeAdapter, boolean isRootVisible) {
+ super(new TreeCallback(treeModel, treeAdapter, isRootVisible));
+ // SwingWorker<List<StringValuePair<Object>>, Void> worker = new
+ // SwingWorker<List<StringValuePair<Object>>, Void>() {
+ // @Override
+ // protected List<StringValuePair<Object>> doInBackground()
+ // throws Exception {
+ // return callback.getPathChoices(null);
+ // }
+ //
+ // @Override
+ // protected void done() {
+ // try {
+ // pushChoices(new BreadcrumbItemChoices<Object>(get()));
+ // } catch (Exception exc) {
+ // }
+ // }
+ // };
+ // worker.execute();
+ }
+
+ /**
+ * Creates an adapter for the specified tree.
+ *
+ * @param tree
+ * Tree.
+ * @param treeAdapter
+ * Tree adapter. Can not be <code>null</code>.
+ */
+ public BreadcrumbTreeAdapterSelector(JTree tree, TreeAdapter treeAdapter) {
+ this(tree.getModel(), treeAdapter, tree.isRootVisible());
+ }
+
+ /**
+ * Creates an adapter for the specified tree. Assumes that the tree renderer
+ * extends a {@link JLabel}. Otherwise, the path selectors will have no
+ * captions and no icons.
+ *
+ * @param tree
+ * Tree.
+ */
+ public BreadcrumbTreeAdapterSelector(final JTree tree) {
+ this(tree, new TreeAdapter() {
+ private JLabel getRenderer(Object node) {
+ Component renderer = tree.getCellRenderer()
+ .getTreeCellRendererComponent(tree, node, false, false,
+ tree.getModel().isLeaf(node), 0, false);
+ if (renderer instanceof JLabel)
+ return (JLabel) renderer;
+ return null;
+ }
+
+ @Override
+ public String toString(Object node) {
+ JLabel label = getRenderer(node);
+ if (label != null)
+ return label.getText();
+ return null;
+ }
+
+ @Override
+ public Icon getIcon(Object node) {
+ JLabel label = getRenderer(node);
+ if (label != null)
+ return label.getIcon();
+ return null;
+ }
+ });
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/AbstractCommandButton.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/AbstractCommandButton.java
new file mode 100644
index 0000000..0062fa9
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/AbstractCommandButton.java
@@ -0,0 +1,848 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.event.*;
+
+import javax.accessibility.AccessibleContext;
+import javax.swing.ButtonModel;
+import javax.swing.SwingConstants;
+import javax.swing.event.*;
+
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.model.ActionButtonModel;
+import org.pushingpixels.flamingo.internal.ui.common.CommandButtonUI;
+
+/**
+ * Base class for command buttons.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class AbstractCommandButton extends
+ RichToolTipManager.JTrackableComponent {
+ /**
+ * Associated icon.
+ *
+ * @see #setIcon(ResizableIcon)
+ * @see #getIcon()
+ */
+ protected ResizableIcon icon;
+
+ /**
+ * Associated disabled icon.
+ *
+ * @see #setDisabledIcon(ResizableIcon)
+ * @see #getDisabledIcon()
+ */
+ protected ResizableIcon disabledIcon;
+
+ /**
+ * The button text.
+ *
+ * @see #setText(String)
+ * @see #getText()
+ */
+ private String text;
+
+ /**
+ * The button action model.
+ *
+ * @see #getActionModel()
+ * @see #setActionModel(ActionButtonModel)
+ */
+ protected ActionButtonModel actionModel;
+
+ /**
+ * Additional text. This is shown for {@link CommandButtonDisplayState#TILE}
+ * .
+ *
+ * @see #setExtraText(String)
+ * @see #getExtraText()
+ */
+ protected String extraText;
+
+ /**
+ * Current display state of <code>this</code> button.
+ *
+ * @see #setDisplayState(CommandButtonDisplayState)
+ * @see #getDisplayState()
+ */
+ protected CommandButtonDisplayState displayState;
+
+ /**
+ * The dimension of the icon of the associated command button in the
+ * {@link CommandButtonDisplayState#FIT_TO_ICON} state.
+ *
+ * @see #getCustomDimension()
+ * @see #updateCustomDimension(int)
+ */
+ protected int customDimension;
+
+ /**
+ * Indication whether this button is flat.
+ *
+ * @see #setFlat(boolean)
+ * @see #isFlat()
+ */
+ protected boolean isFlat;
+
+ /**
+ * Horizontal alignment of the content.
+ *
+ * @see #setHorizontalAlignment(int)
+ * @see #getHorizontalAlignment()
+ */
+ private int horizontalAlignment;
+
+ /**
+ * Scale factor for horizontal gaps.
+ *
+ * @see #setHGapScaleFactor(double)
+ * @see #getHGapScaleFactor()
+ */
+ private double hgapScaleFactor;
+
+ /**
+ * Scale factor for vertical gaps.
+ *
+ * @see #setVGapScaleFactor(double)
+ * @see #getVGapScaleFactor()
+ */
+ private double vgapScaleFactor;
+
+ /**
+ * Rich tooltip for the action area.
+ *
+ * @see #setActionRichTooltip(RichTooltip)
+ * @see #getRichTooltip(MouseEvent)
+ */
+ private RichTooltip actionRichTooltip;
+
+ /**
+ * Location order kind for buttons placed in command button strips or for
+ * buttons that need the visuals of segmented strips.
+ *
+ * @see #setLocationOrderKind(CommandButtonLocationOrderKind)
+ * @see #getLocationOrderKind()
+ */
+ private CommandButtonLocationOrderKind locationOrderKind;
+
+ /**
+ * Action handler for the button.
+ */
+ protected ActionHandler actionHandler;
+
+ /**
+ * Key tip for the action area.
+ *
+ * @see #setActionKeyTip(String)
+ * @see #getActionKeyTip()
+ */
+ protected String actionKeyTip;
+
+ /**
+ * Enumerates the available values for the location order kind. This is used
+ * for buttons placed in command button strips or for buttons that need the
+ * visuals of segmented strips.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static enum CommandButtonLocationOrderKind {
+ /**
+ * Indicates that this button is the only button in the strip.
+ */
+ ONLY,
+
+ /**
+ * Indicates that this button is the first button in the strip.
+ */
+ FIRST,
+
+ /**
+ * Indicates that this button is in the middle of the strip.
+ */
+ MIDDLE,
+
+ /**
+ * Indicates that this button is the last button in the strip.
+ */
+ LAST
+ }
+
+ /**
+ * Creates a new command button.
+ *
+ * @param text
+ * Button title. May contain any number of words.
+ * @param icon
+ * Button icon.
+ */
+ public AbstractCommandButton(String text, ResizableIcon icon) {
+ this.icon = icon;
+ this.customDimension = -1;
+ this.displayState = CommandButtonDisplayState.FIT_TO_ICON;
+ this.horizontalAlignment = SwingConstants.CENTER;
+ this.actionHandler = new ActionHandler();
+ this.isFlat = true;
+ this.hgapScaleFactor = 1.0;
+ this.vgapScaleFactor = 1.0;
+ this.setText(text);
+ this.setOpaque(false);
+ }
+
+ /**
+ * Sets the new UI delegate.
+ *
+ * @param ui
+ * New UI delegate.
+ */
+ public void setUI(CommandButtonUI ui) {
+ super.setUI(ui);
+ }
+
+ /**
+ * Returns the UI delegate for this button.
+ *
+ * @return The UI delegate for this button.
+ */
+ public CommandButtonUI getUI() {
+ return (CommandButtonUI) ui;
+ }
+
+ /**
+ * Sets new display state for <code>this</code> button. Fires a
+ * <code>displayState</code> property change event.
+ *
+ * @param state
+ * New display state.
+ * @see #getDisplayState()
+ */
+ public void setDisplayState(CommandButtonDisplayState state) {
+ CommandButtonDisplayState old = this.displayState;
+ this.displayState = state;
+
+ this.firePropertyChange("displayState", old, this.displayState);
+ }
+
+ /**
+ * Returns the associated icon.
+ *
+ * @return The associated icon.
+ * @see #getDisabledIcon()
+ * @see #setIcon(ResizableIcon)
+ */
+ public ResizableIcon getIcon() {
+ return icon;
+ }
+
+ /**
+ * Sets new icon for this button. Fires an <code>icon</code> property change
+ * event.
+ *
+ * @param defaultIcon
+ * New default icon for this button.
+ * @see #setDisabledIcon(ResizableIcon)
+ * @see #getIcon()
+ */
+ public void setIcon(ResizableIcon defaultIcon) {
+ ResizableIcon oldValue = this.icon;
+ this.icon = defaultIcon;
+
+ firePropertyChange("icon", oldValue, defaultIcon);
+ if (defaultIcon != oldValue) {
+ if (defaultIcon == null || oldValue == null
+ || defaultIcon.getIconWidth() != oldValue.getIconWidth()
+ || defaultIcon.getIconHeight() != oldValue.getIconHeight()) {
+ revalidate();
+ }
+ repaint();
+ }
+ }
+
+ /**
+ * Sets the disabled icon for this button.
+ *
+ * @param disabledIcon
+ * Disabled icon for this button.
+ * @see #setIcon(ResizableIcon)
+ * @see #getDisabledIcon()
+ */
+ public void setDisabledIcon(ResizableIcon disabledIcon) {
+ this.disabledIcon = disabledIcon;
+ }
+
+ /**
+ * Returns the associated disabled icon.
+ *
+ * @return The associated disabled icon.
+ * @see #setDisabledIcon(ResizableIcon)
+ * @see #getIcon()
+ */
+ public ResizableIcon getDisabledIcon() {
+ return disabledIcon;
+ }
+
+ /**
+ * Return the current display state of <code>this</code> button.
+ *
+ * @return The current display state of <code>this</code> button.
+ * @see #setDisplayState(CommandButtonDisplayState)
+ */
+ public CommandButtonDisplayState getDisplayState() {
+ return displayState;
+ }
+
+ /**
+ * Returns the extra text of this button.
+ *
+ * @return Extra text of this button.
+ * @see #setExtraText(String)
+ */
+ public String getExtraText() {
+ return this.extraText;
+ }
+
+ /**
+ * Sets the extra text for this button. Fires an <code>extraText</code>
+ * property change event.
+ *
+ * @param extraText
+ * Extra text for this button.
+ * @see #getExtraText()
+ */
+ public void setExtraText(String extraText) {
+ String oldValue = this.extraText;
+ this.extraText = extraText;
+ firePropertyChange("extraText", oldValue, extraText);
+
+ if (accessibleContext != null) {
+ accessibleContext.firePropertyChange(
+ AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
+ oldValue, extraText);
+ }
+ if (extraText == null || oldValue == null
+ || !extraText.equals(oldValue)) {
+ revalidate();
+ repaint();
+ }
+ }
+
+ /**
+ * Returns the text of this button.
+ *
+ * @return The text of this button.
+ * @see #setText(String)
+ */
+ public String getText() {
+ return this.text;
+ }
+
+ /**
+ * Sets the new text for this button. Fires a <code>text</code> property
+ * change event.
+ *
+ * @param text
+ * The new text for this button.
+ * @see #getText()
+ */
+ public void setText(String text) {
+ String oldValue = this.text;
+ this.text = text;
+ firePropertyChange("text", oldValue, text);
+
+ if (accessibleContext != null) {
+ accessibleContext.firePropertyChange(
+ AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
+ oldValue, text);
+ }
+ if (text == null || oldValue == null || !text.equals(oldValue)) {
+ revalidate();
+ repaint();
+ }
+ }
+
+ /**
+ * Updates the dimension of the icon of the associated command button in the
+ * {@link CommandButtonDisplayState#FIT_TO_ICON} state. Fires a
+ * <code>customDimension</code> property change event.
+ *
+ * @param dimension
+ * New dimension of the icon of the associated command button in
+ * the {@link CommandButtonDisplayState#FIT_TO_ICON} state.
+ * @see #getCustomDimension()
+ */
+ public void updateCustomDimension(int dimension) {
+ if (this.customDimension != dimension) {
+ int old = this.customDimension;
+ this.customDimension = dimension;
+ this.firePropertyChange("customDimension", old,
+ this.customDimension);
+ }
+ }
+
+ /**
+ * Returns the dimension of the icon of the associated command button in the
+ * {@link CommandButtonDisplayState#FIT_TO_ICON} state.
+ *
+ * @return The dimension of the icon of the associated command button in the
+ * {@link CommandButtonDisplayState#FIT_TO_ICON} state.
+ * @see #updateCustomDimension(int)
+ */
+ public int getCustomDimension() {
+ return this.customDimension;
+ }
+
+ /**
+ * Returns indication whether this button has flat appearance.
+ *
+ * @return <code>true</code> if this button has flat appearance,
+ * <code>false</code> otherwise.
+ * @see #setFlat(boolean)
+ */
+ public boolean isFlat() {
+ return this.isFlat;
+ }
+
+ /**
+ * Sets the flat appearance of this button. Fires a <code>flat</code>
+ * property change event.
+ *
+ * @param isFlat
+ * If <code>true</code>, this button will have flat appearance,
+ * otherwise this button will not have flat appearance.
+ * @see #isFlat()
+ */
+ public void setFlat(boolean isFlat) {
+ boolean old = this.isFlat;
+ this.isFlat = isFlat;
+ if (old != this.isFlat) {
+ this.firePropertyChange("flat", old, this.isFlat);
+ }
+
+ if (old != isFlat) {
+ repaint();
+ }
+ }
+
+ /**
+ * Returns the action model for this button.
+ *
+ * @return The action model for this button.
+ * @see #setActionModel(ActionButtonModel)
+ */
+ public ActionButtonModel getActionModel() {
+ return this.actionModel;
+ }
+
+ /**
+ * Sets the new action model for this button. Fires an
+ * <code>actionModel</code> property change event.
+ *
+ * @param newModel
+ * The new action model for this button.
+ * @see #getActionModel()
+ */
+ public void setActionModel(ActionButtonModel newModel) {
+ ButtonModel oldModel = getActionModel();
+
+ if (oldModel != null) {
+ oldModel.removeChangeListener(this.actionHandler);
+ oldModel.removeActionListener(this.actionHandler);
+ }
+
+ actionModel = newModel;
+
+ if (newModel != null) {
+ newModel.addChangeListener(this.actionHandler);
+ newModel.addActionListener(this.actionHandler);
+ }
+
+ firePropertyChange("actionModel", oldModel, newModel);
+ if (newModel != oldModel) {
+ revalidate();
+ repaint();
+ }
+ }
+
+ /**
+ * Adds the specified action listener to this button.
+ *
+ * @param l
+ * Action listener to add.
+ * @see #removeActionListener(ActionListener)
+ */
+ public void addActionListener(ActionListener l) {
+ this.listenerList.add(ActionListener.class, l);
+ }
+
+ /**
+ * Removes the specified action listener from this button.
+ *
+ * @param l
+ * Action listener to remove.
+ * @see #addActionListener(ActionListener)
+ */
+ public void removeActionListener(ActionListener l) {
+ this.listenerList.remove(ActionListener.class, l);
+ }
+
+ /**
+ * Adds the specified change listener to this button.
+ *
+ * @param l
+ * Change listener to add.
+ * @see #removeChangeListener(ChangeListener)
+ */
+ public void addChangeListener(ChangeListener l) {
+ this.listenerList.add(ChangeListener.class, l);
+ }
+
+ /**
+ * Removes the specified change listener from this button.
+ *
+ * @param l
+ * Change listener to remove.
+ * @see #addChangeListener(ChangeListener)
+ */
+ public void removeChangeListener(ChangeListener l) {
+ this.listenerList.remove(ChangeListener.class, l);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#setEnabled(boolean)
+ */
+ @Override
+ public void setEnabled(boolean b) {
+ if (!b && actionModel.isRollover()) {
+ actionModel.setRollover(false);
+ }
+ super.setEnabled(b);
+ actionModel.setEnabled(b);
+ }
+
+ /**
+ * Default action handler for this button.
+ *
+ * @author Kirill Grouchnikov
+ */
+ class ActionHandler implements ActionListener, ChangeListener {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ fireStateChanged();
+ repaint();
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ fireActionPerformed(event);
+ }
+ }
+
+ /**
+ * Notifies all listeners that have registered interest for notification on
+ * this event type. The event instance is lazily created.
+ *
+ * @see EventListenerList
+ */
+ protected void fireStateChanged() {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ ChangeEvent ce = new ChangeEvent(this);
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ChangeListener.class) {
+ // Lazily create the event:
+ ((ChangeListener) listeners[i + 1]).stateChanged(ce);
+ }
+ }
+ }
+
+ /**
+ * Notifies all listeners that have registered interest for notification on
+ * this event type. The event instance is lazily created using the
+ * <code>event</code> parameter.
+ *
+ * @param event
+ * the <code>ActionEvent</code> object
+ * @see EventListenerList
+ */
+ protected void fireActionPerformed(ActionEvent event) {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ ActionEvent e = null;
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ActionListener.class) {
+ // Lazily create the event:
+ if (e == null) {
+ String actionCommand = event.getActionCommand();
+ e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
+ actionCommand, event.getWhen(), event
+ .getModifiers());
+ }
+ ((ActionListener) listeners[i + 1]).actionPerformed(e);
+ }
+ }
+ }
+
+ /**
+ * Sets new horizontal alignment for the content of this button. Fires a
+ * <code>horizontalAlignment</code> property change event.
+ *
+ * @param alignment
+ * New horizontal alignment for the content of this button.
+ * @see #getHorizontalAlignment()
+ */
+ public void setHorizontalAlignment(int alignment) {
+ if (alignment == this.horizontalAlignment)
+ return;
+ int oldValue = this.horizontalAlignment;
+ this.horizontalAlignment = alignment;
+ firePropertyChange("horizontalAlignment", oldValue,
+ this.horizontalAlignment);
+ repaint();
+ }
+
+ /**
+ * Returns the horizontal alignment for the content of this button.
+ *
+ * @return The horizontal alignment for the content of this button.
+ * @see #setHorizontalAlignment(int)
+ */
+ public int getHorizontalAlignment() {
+ return this.horizontalAlignment;
+ }
+
+ /**
+ * Sets new horizontal gap scale factor for the content of this button.
+ * Fires an <code>hgapScaleFactor</code> property change event.
+ *
+ * @param hgapScaleFactor
+ * New horizontal gap scale factor for the content of this
+ * button.
+ * @see #getHGapScaleFactor()
+ * @see #setVGapScaleFactor(double)
+ * @see #setGapScaleFactor(double)
+ */
+ public void setHGapScaleFactor(double hgapScaleFactor) {
+ if (hgapScaleFactor == this.hgapScaleFactor)
+ return;
+ double oldValue = this.hgapScaleFactor;
+ this.hgapScaleFactor = hgapScaleFactor;
+ firePropertyChange("hgapScaleFactor", oldValue, this.hgapScaleFactor);
+ if (this.hgapScaleFactor != oldValue) {
+ revalidate();
+ repaint();
+ }
+ }
+
+ /**
+ * Sets new vertical gap scale factor for the content of this button. Fires
+ * a <code>vgapScaleFactor</code> property change event.
+ *
+ * @param vgapScaleFactor
+ * New vertical gap scale factor for the content of this button.
+ * @see #getVGapScaleFactor()
+ * @see #setHGapScaleFactor(double)
+ * @see #setGapScaleFactor(double)
+ */
+ public void setVGapScaleFactor(double vgapScaleFactor) {
+ if (vgapScaleFactor == this.vgapScaleFactor)
+ return;
+ double oldValue = this.vgapScaleFactor;
+ this.vgapScaleFactor = vgapScaleFactor;
+ firePropertyChange("vgapScaleFactor", oldValue, this.vgapScaleFactor);
+ if (this.vgapScaleFactor != oldValue) {
+ revalidate();
+ repaint();
+ }
+ }
+
+ /**
+ * Sets new gap scale factor for the content of this button.
+ *
+ * @param gapScaleFactor
+ * New gap scale factor for the content of this button.
+ * @see #getHGapScaleFactor()
+ * @see #getVGapScaleFactor()
+ */
+ public void setGapScaleFactor(double gapScaleFactor) {
+ setHGapScaleFactor(gapScaleFactor);
+ setVGapScaleFactor(gapScaleFactor);
+ }
+
+ /**
+ * Returns the horizontal gap scale factor for the content of this button.
+ *
+ * @return The horizontal gap scale factor for the content of this button.
+ * @see #setHGapScaleFactor(double)
+ * @see #setGapScaleFactor(double)
+ * @see #getVGapScaleFactor()
+ */
+ public double getHGapScaleFactor() {
+ return this.hgapScaleFactor;
+ }
+
+ /**
+ * Returns the vertical gap scale factor for the content of this button.
+ *
+ * @return The vertical gap scale factor for the content of this button.
+ * @see #setVGapScaleFactor(double)
+ * @see #setGapScaleFactor(double)
+ * @see #getHGapScaleFactor()
+ */
+ public double getVGapScaleFactor() {
+ return this.vgapScaleFactor;
+ }
+
+ /**
+ * Programmatically perform an action "click". This does the same thing as
+ * if the user had pressed and released the action area of the button.
+ */
+ public void doActionClick() {
+ Dimension size = getSize();
+ ButtonModel actionModel = this.getActionModel();
+ actionModel.setArmed(true);
+ actionModel.setPressed(true);
+ paintImmediately(new Rectangle(0, 0, size.width, size.height));
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ie) {
+ }
+ actionModel.setPressed(false);
+ actionModel.setArmed(false);
+ }
+
+ boolean hasRichTooltips() {
+ return (this.actionRichTooltip != null);
+ }
+
+ /**
+ * Sets the rich tooltip for the action area of this button.
+ *
+ * @param richTooltip
+ * Rich tooltip for the action area of this button.
+ * @see #getRichTooltip(MouseEvent)
+ */
+ public void setActionRichTooltip(RichTooltip richTooltip) {
+ this.actionRichTooltip = richTooltip;
+ RichToolTipManager richToolTipManager = RichToolTipManager
+ .sharedInstance();
+ if (this.hasRichTooltips()) {
+ richToolTipManager.registerComponent(this);
+ } else {
+ richToolTipManager.unregisterComponent(this);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.jvnet.flamingo.common.RichToolTipManager.JTrackableComponent#
+ * getRichTooltip(java.awt.event.MouseEvent)
+ */
+ @Override
+ public RichTooltip getRichTooltip(MouseEvent mouseEvent) {
+ return this.actionRichTooltip;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#setToolTipText(java.lang.String)
+ */
+ @Override
+ public void setToolTipText(String text) {
+ throw new UnsupportedOperationException("Use rich tooltip APIs");
+ }
+
+ /**
+ * Returns the location order kind for buttons placed in command button
+ * strips or for buttons that need the visuals of segmented strips.
+ *
+ * @return The location order kind for buttons placed in command button
+ * strips or for buttons that need the visuals of segmented strips.
+ * @see #setLocationOrderKind(CommandButtonLocationOrderKind)
+ */
+ public CommandButtonLocationOrderKind getLocationOrderKind() {
+ return this.locationOrderKind;
+ }
+
+ /**
+ * Sets the location order kind for buttons placed in command button strips
+ * or for buttons that need the visuals of segmented strips. Fires a
+ * <code>locationOrderKind</code> property change event.
+ *
+ * @param locationOrderKind
+ * The location order kind for buttons placed in command button
+ * strips or for buttons that need the visuals of segmented
+ * strips.
+ * @see #getLocationOrderKind()
+ */
+ public void setLocationOrderKind(
+ CommandButtonLocationOrderKind locationOrderKind) {
+ CommandButtonLocationOrderKind old = this.locationOrderKind;
+ if (old != locationOrderKind) {
+ this.locationOrderKind = locationOrderKind;
+ this.firePropertyChange("locationOrderKind", old,
+ this.locationOrderKind);
+ }
+ }
+
+ /**
+ * Returns the key tip for the action area of this button.
+ *
+ * @return The key tip for the action area of this button.
+ * @see #setActionKeyTip(String)
+ */
+ public String getActionKeyTip() {
+ return this.actionKeyTip;
+ }
+
+ /**
+ * Sets the key tip for the action area of this button. Fires an
+ * <code>actionKeyTip</code> property change event.
+ *
+ * @param actionKeyTip
+ * The key tip for the action area of this button.
+ * @see #getActionKeyTip()
+ */
+ public void setActionKeyTip(String actionKeyTip) {
+ String old = this.actionKeyTip;
+ this.actionKeyTip = actionKeyTip;
+ this.firePropertyChange("actionKeyTip", old, this.actionKeyTip);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/AbstractFileViewPanel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/AbstractFileViewPanel.java
new file mode 100644
index 0000000..97e5e85
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/AbstractFileViewPanel.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.awt.Dimension;
+import java.io.InputStream;
+import java.util.*;
+
+import javax.swing.SwingUtilities;
+import javax.swing.SwingWorker;
+
+import org.pushingpixels.flamingo.api.common.icon.EmptyResizableIcon;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+
+/**
+ * Panel that hosts file-related command buttons with progress indication and
+ * cancellation capabilities.
+ *
+ * @author Kirill Grouchnikov
+ * @param <T>
+ * Type tag.
+ */
+public abstract class AbstractFileViewPanel<T> extends JCommandButtonPanel {
+ /**
+ * Maps from file name to the buttons.
+ */
+ protected Map<String, JCommandButton> buttonMap;
+
+ /**
+ * Progress listener to report back on loaded images.
+ */
+ protected ProgressListener progressListener;
+
+ /**
+ * Contains the buttons with completely loaded images.
+ */
+ protected Set<JCommandButton> loadedSet;
+
+ /**
+ * The main worker that loads the images off EDT.
+ */
+ private SwingWorker<Void, Leaf> mainWorker;
+
+ /**
+ * Information on the specific file. Depending on the actual type of the
+ * file repository, the property map will have different keys.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class Leaf {
+ /**
+ * Leaf name.
+ */
+ protected String leafName;
+
+ /**
+ * Stream with the contents of the leaf file.
+ */
+ protected InputStream leafStream;
+
+ /**
+ * Leaf property map.
+ */
+ protected Map<String, Object> leafProps;
+
+ /**
+ * Creates a new leaf.
+ *
+ * @param leafName
+ * Leaf name.
+ * @param leafStream
+ * Stream with the contents of the leaf file.
+ */
+ public Leaf(String leafName, InputStream leafStream) {
+ this.leafName = leafName;
+ this.leafStream = leafStream;
+ this.leafProps = new HashMap<String, Object>();
+ }
+
+ /**
+ * Returns the leaf name.
+ *
+ * @return Leaf name.
+ */
+ public String getLeafName() {
+ return leafName;
+ }
+
+ /**
+ * Returns the stream with the contents of the leaf file.
+ *
+ * @return Stream with the contents of the leaf file.
+ */
+ public InputStream getLeafStream() {
+ return leafStream;
+ }
+
+ /**
+ * Returns the leaf property with the specified name.
+ *
+ * @param propName
+ * Property name.
+ * @return Leaf property with the specified name.
+ */
+ public Object getLeafProp(String propName) {
+ return this.leafProps.get(propName);
+ }
+
+ /**
+ * Sets the leaf property with the specified name.
+ *
+ * @param propName
+ * Property name.
+ * @param propValue
+ * Property value.
+ */
+ public void setLeafProp(String propName, Object propValue) {
+ this.leafProps.put(propName, propValue);
+ }
+
+ /**
+ * Returns the map of all the properties of this leaf.
+ *
+ * @return Unmodifiable view of the map of all the properties of this
+ * leaf.
+ */
+ public Map<String, Object> getLeafProps() {
+ return Collections.unmodifiableMap(this.leafProps);
+ }
+ }
+
+ /**
+ * Creates a new panel.
+ *
+ * @param startingDimension
+ * Initial dimension for icons.
+ * @param progressListener
+ * Progress listener to report back on loaded icons.
+ */
+ public AbstractFileViewPanel(int startingDimension,
+ ProgressListener progressListener) {
+ super(startingDimension);
+ this.buttonMap = new HashMap<String, JCommandButton>();
+ this.progressListener = progressListener;
+ this.loadedSet = new HashSet<JCommandButton>();
+
+ this.setToShowGroupLabels(false);
+ }
+
+ /**
+ * Creates a new panel.
+ *
+ * @param startingState
+ * Initial state for icons.
+ * @param progressListener
+ * Progress listener to report back on loaded icons.
+ */
+ public AbstractFileViewPanel(CommandButtonDisplayState startingState,
+ ProgressListener progressListener) {
+ super(startingState);
+ this.buttonMap = new HashMap<String, JCommandButton>();
+ this.progressListener = progressListener;
+ this.loadedSet = new HashSet<JCommandButton>();
+
+ this.setToShowGroupLabels(false);
+ }
+
+ /**
+ * Sets the current entries to show. The current contents of the panel are
+ * discarded. For each matching entry determined by the
+ * {@link #toShowFile(StringValuePair)} call, a new {@link JCommandButton}
+ * hosting an the matching implementation of {@link ResizableIcon} is added
+ * to the panel.
+ *
+ * @param leafs
+ * Information on the entries to show in the panel.
+ */
+ public void setFolder(final java.util.List<StringValuePair<T>> leafs) {
+ this.removeAllGroups();
+ this.addButtonGroup("");
+ this.buttonMap.clear();
+ int fileCount = 0;
+
+ final Map<String, JCommandButton> newButtons = new HashMap<String, JCommandButton>();
+ for (StringValuePair<T> leaf : leafs) {
+ String name = leaf.getKey();
+ if (!toShowFile(leaf))
+ continue;
+
+ int initialSize = currDimension;
+ if (initialSize < 0)
+ initialSize = currState.getPreferredIconSize();
+ JCommandButton button = new JCommandButton(name,
+ new EmptyResizableIcon(initialSize));
+ button.setHorizontalAlignment(SwingUtilities.LEFT);
+ button.setDisplayState(this.currState);
+ if (this.currState == CommandButtonDisplayState.FIT_TO_ICON)
+ button.updateCustomDimension(currDimension);
+
+ this.addButtonToLastGroup(button);
+
+ newButtons.put(name, button);
+ buttonMap.put(name, button);
+ fileCount++;
+ }
+ this.doLayout();
+ this.repaint();
+
+ final int totalCount = fileCount;
+ this.mainWorker = new SwingWorker<Void, Leaf>() {
+ @Override
+ protected Void doInBackground() throws Exception {
+ if ((totalCount > 0) && (progressListener != null)) {
+ progressListener.onProgress(new ProgressEvent(
+ AbstractFileViewPanel.this, 0, totalCount, 0));
+ }
+ for (final StringValuePair<T> leafPair : leafs) {
+ if (isCancelled())
+ break;
+ final String name = leafPair.getKey();
+ if (!toShowFile(leafPair))
+ continue;
+ InputStream stream = getLeafContent(leafPair.getValue());
+ Leaf leaf = new Leaf(name, stream);
+ leaf.setLeafProp("source", leafPair.getValue());
+ for (Map.Entry<String, Object> propEntry : leafPair
+ .getProps().entrySet()) {
+ leaf.setLeafProp(propEntry.getKey(), propEntry
+ .getValue());
+ }
+ publish(leaf);
+ }
+ return null;
+ }
+
+ @Override
+ protected void process(List<Leaf> leaves) {
+ for (final Leaf leaf : leaves) {
+ final String name = leaf.getLeafName();
+ InputStream stream = leaf.getLeafStream();
+ Dimension dim = new Dimension(currDimension, currDimension);
+ final ResizableIcon icon = getResizableIcon(leaf, stream,
+ currState, dim);
+ if (icon == null)
+ continue;
+ final JCommandButton commandButton = newButtons.get(name);
+ commandButton.setIcon(icon);
+
+ if (icon instanceof AsynchronousLoading) {
+ ((AsynchronousLoading) icon)
+ .addAsynchronousLoadListener(new AsynchronousLoadListener() {
+ @Override
+ public void completed(boolean success) {
+ synchronized (AbstractFileViewPanel.this) {
+ if (loadedSet
+ .contains(commandButton))
+ return;
+ loadedSet.add(commandButton);
+ // loadedCount++;
+ if (progressListener != null) {
+ progressListener
+ .onProgress(new ProgressEvent(
+ AbstractFileViewPanel.this,
+ 0, totalCount,
+ loadedSet
+ .size()));
+ if (loadedSet.size() == totalCount) {
+ progressListener
+ .onProgress(new ProgressEvent(
+ AbstractFileViewPanel.this,
+ 0,
+ totalCount,
+ totalCount));
+ }
+ }
+ }
+ }
+ });
+ }
+
+ configureCommandButton(leaf, commandButton, icon);
+
+ commandButton.setDisplayState(currState);
+ if (currState == CommandButtonDisplayState.FIT_TO_ICON)
+ commandButton.updateCustomDimension(currDimension);
+ }
+ }
+ };
+ mainWorker.execute();
+ }
+
+ /**
+ * Returns the number of loaded icons.
+ *
+ * @return The number of loaded icons.
+ */
+ public int getLoadedIconCount() {
+ return this.loadedSet.size();
+ }
+
+ /**
+ * Cancels the pending processing.
+ */
+ public void cancelMainWorker() {
+ if (this.mainWorker == null)
+ return;
+ if (this.mainWorker.isDone() || this.mainWorker.isCancelled())
+ return;
+ this.mainWorker.cancel(false);
+ }
+
+ /**
+ * Returns the button map.
+ *
+ * @return Unmodifiable view on the button map.
+ */
+ public Map<String, JCommandButton> getButtonMap() {
+ return Collections.unmodifiableMap(buttonMap);
+ }
+
+ /**
+ * Returns indication whether the specified file should be shown on this
+ * panel.
+ *
+ * @param pair
+ * Information on the file.
+ * @return <code>true</code> if the specified file should be shown on this
+ * panel, <code>false</code> otherwise.
+ */
+ protected abstract boolean toShowFile(StringValuePair<T> pair);
+
+ /**
+ * Returns the icon for the specified parameters.
+ *
+ * @param leaf
+ * Information on the file.
+ * @param stream
+ * Input stream with the file contents.
+ * @param state
+ * Icon state.
+ * @param dimension
+ * Icon dimension.
+ * @return File icon.
+ */
+ protected abstract ResizableIcon getResizableIcon(Leaf leaf,
+ InputStream stream, CommandButtonDisplayState state,
+ Dimension dimension);
+
+ /**
+ * Configures the specified command button. Can be used to wire additional
+ * behavior, such as tooltips or action listeners if the specific view panel
+ * implementation requires it.
+ *
+ * @param leaf
+ * Information on the file "behind" the button.
+ * @param button
+ * Button to configure.
+ * @param icon
+ * Button icon.
+ */
+ protected abstract void configureCommandButton(Leaf leaf,
+ JCommandButton button, ResizableIcon icon);
+
+ /**
+ * Returns the input stream with the file contents.
+ *
+ * @param leaf
+ * Leaf (file behind a command button on this panel).
+ * @return Input stream with the file contents.
+ */
+ protected abstract InputStream getLeafContent(T leaf);
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/AsynchronousLoadListener.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/AsynchronousLoadListener.java
new file mode 100644
index 0000000..156c455
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/AsynchronousLoadListener.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.util.EventListener;
+
+/**
+ * This interface is used for asynchronously-loaded contents. When the image
+ * is loaded, the component that contains this image ({@link JCommandButton}
+ * for example) is notified to repaint itself.
+ *
+ * @author Kirill Grouchnikov.
+ */
+public interface AsynchronousLoadListener extends EventListener {
+ /**
+ * Indicates that the asynchronous load has been completed.
+ *
+ * @param success
+ * If <code>true</code>, the load has been completed
+ * successfully.
+ */
+ public void completed(boolean success);
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/AsynchronousLoading.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/AsynchronousLoading.java
new file mode 100644
index 0000000..0abedd0
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/AsynchronousLoading.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+/**
+ * This interface is used for asynchronously-loaded contents. When the image
+ * is loaded, the component that contains this image ({@link JCommandButton}
+ * for example) is notified to repaint itself.
+ *
+ * @author Kirill Grouchnikov.
+ */
+public interface AsynchronousLoading {
+ /**
+ * Adds listener on the asynchronous loading events.
+ *
+ * @param l
+ * Listener to add.
+ */
+ public void addAsynchronousLoadListener(AsynchronousLoadListener l);
+
+ /**
+ * Removes listener on the asynchronous loading events.
+ *
+ * @param l
+ * Listener to remove.
+ */
+ public void removeAsynchronousLoadListener(AsynchronousLoadListener l);
+
+ /**
+ * Returns indication whether the content is still loading.
+ *
+ * @return <code>true</code> if the content is still loading,
+ * <code>false</code> otherwise.
+ */
+ public boolean isLoading();
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/CommandButtonDisplayState.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/CommandButtonDisplayState.java
new file mode 100644
index 0000000..9dbc309
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/CommandButtonDisplayState.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import org.pushingpixels.flamingo.internal.ui.common.*;
+
+/**
+ * Display state for command buttons. This class provides a number of core
+ * display states, and it is possible to create additional custom states by
+ * using the protected constructor and implementing the relevant abstract
+ * methods.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class CommandButtonDisplayState {
+ /**
+ * Fit to icon state.
+ */
+ public static final CommandButtonDisplayState FIT_TO_ICON = new CommandButtonDisplayState(
+ "Fit to icon", -1) {
+ @Override
+ public CommandButtonLayoutManager createLayoutManager(
+ AbstractCommandButton commandButton) {
+ return new CommandButtonLayoutManagerCustom(commandButton);
+ }
+ };
+
+ /**
+ * Big state.
+ */
+ public static final CommandButtonDisplayState BIG = new CommandButtonDisplayState(
+ "Big", 32) {
+ @Override
+ public CommandButtonLayoutManager createLayoutManager(
+ AbstractCommandButton commandButton) {
+ return new CommandButtonLayoutManagerBig(commandButton);
+ }
+ };
+
+ /**
+ * Tile state.
+ */
+ public static final CommandButtonDisplayState TILE = new CommandButtonDisplayState(
+ "Tile", 32) {
+ @Override
+ public CommandButtonLayoutManager createLayoutManager(
+ AbstractCommandButton arg0) {
+ return new CommandButtonLayoutManagerTile();
+ }
+ };
+
+ /**
+ * Medium state.
+ */
+ public static final CommandButtonDisplayState MEDIUM = new CommandButtonDisplayState(
+ "Medium", 16) {
+ @Override
+ public CommandButtonLayoutManager createLayoutManager(
+ AbstractCommandButton arg0) {
+ return new CommandButtonLayoutManagerMedium();
+ }
+ };
+
+ /**
+ * Small state.
+ */
+ public static final CommandButtonDisplayState SMALL = new CommandButtonDisplayState(
+ "Small", 16) {
+ @Override
+ public CommandButtonLayoutManager createLayoutManager(
+ AbstractCommandButton arg0) {
+ return new CommandButtonLayoutManagerSmall();
+ }
+ };
+
+ /**
+ * Preferred icon size for this state.
+ *
+ * @see #CommandButtonDisplayState(String, int)
+ * @see #getPreferredIconSize()
+ */
+ int preferredIconSize;
+
+ /**
+ * Display name for this state.
+ *
+ * @see #CommandButtonDisplayState(String, int)
+ * @see #getDisplayName()
+ */
+ String displayName;
+
+ /**
+ * Creates a new element state.
+ *
+ * @param displayName
+ * Display name.
+ * @param preferredIconSize
+ * Preferred icon size.
+ */
+ protected CommandButtonDisplayState(String displayName,
+ int preferredIconSize) {
+ this.displayName = displayName;
+ this.preferredIconSize = preferredIconSize;
+ }
+
+ /**
+ * Returns the display name for this state.
+ *
+ * @return The display name for this state.
+ * @see #CommandButtonDisplayState(String, int)
+ */
+ public String getDisplayName() {
+ return this.displayName;
+ }
+
+ /**
+ * Returns the preferred icon size for this state.
+ *
+ * @return The preferred icon size for this state.
+ * @see #CommandButtonDisplayState(String, int)
+ */
+ public int getPreferredIconSize() {
+ return this.preferredIconSize;
+ }
+
+ /**
+ * Creates a layout manager for the specified button.
+ *
+ * @param commandButton
+ * Command button.
+ * @return A layout manager for the specified button.
+ */
+ public abstract CommandButtonLayoutManager createLayoutManager(
+ AbstractCommandButton commandButton);
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return this.getDisplayName();
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/CommandButtonLayoutManager.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/CommandButtonLayoutManager.java
new file mode 100644
index 0000000..197c7f0
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/CommandButtonLayoutManager.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.awt.*;
+import java.beans.PropertyChangeListener;
+import java.util.List;
+
+/**
+ * Definition of a layout manager for {@link AbstractCommandButton}s.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface CommandButtonLayoutManager extends PropertyChangeListener {
+ /**
+ * Enumerates the available values for separator orientations.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public enum CommandButtonSeparatorOrientation {
+ /**
+ * Vertical separator orientation.
+ */
+ VERTICAL,
+
+ /**
+ * Horizontal separator orientation.
+ */
+ HORIZONTAL
+ }
+
+ /**
+ * Layout information on a single line of text.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public class TextLayoutInfo {
+ /**
+ * Text itself.
+ */
+ public String text;
+
+ /**
+ * The text rectangle.
+ */
+ public Rectangle textRect;
+ }
+
+ /**
+ * Layout information on different visual parts of a single command button.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public class CommandButtonLayoutInfo {
+ /**
+ * The action area. A mouse click in this area will trigger all
+ * listeners associated with the command button action model
+ * {@link AbstractCommandButton#addActionListener(java.awt.event.ActionListener)}
+ */
+ public Rectangle actionClickArea;
+
+ /**
+ * The popup area. A mouse click in this area will trigger the listener
+ * associated with the command button popup model
+ * {@link JCommandButton#setPopupCallback(org.pushingpixels.flamingo.api.common.popup.PopupPanelCallback)}
+ */
+ public Rectangle popupClickArea;
+
+ /**
+ * The separator area. If it's not empty, the command button will show a
+ * separator between {@link #actionClickArea} and
+ * {@link #popupClickArea} on mouse rollover - depending on the current
+ * look-and-feel.
+ */
+ public Rectangle separatorArea;
+
+ public CommandButtonSeparatorOrientation separatorOrientation;
+
+ /**
+ * Rectangle for the command button icon.
+ */
+ public Rectangle iconRect;
+
+ /**
+ * Layout information for the command button text (that can span
+ * multiple lines).
+ */
+ public List<TextLayoutInfo> textLayoutInfoList;
+
+ /**
+ * Layout information for the command button extra text (that can span
+ * multiple lines).
+ */
+ public List<TextLayoutInfo> extraTextLayoutInfoList;
+
+ /**
+ * Rectangle for the icon associated with the {@link #popupClickArea}.
+ * This icon is usually a single or double arrow indicating that the
+ * command button has a popup area.
+ */
+ public Rectangle popupActionRect;
+
+ /**
+ * Indication whether the command button text (rectangles in
+ * {@link #textLayoutInfoList}) belongs in the action area.
+ */
+ public boolean isTextInActionArea;
+ }
+
+ /**
+ * Returns the preferred size of the specified command button.
+ *
+ * @param commandButton
+ * Command button.
+ * @return The preferred size of the specified command button.
+ */
+ public Dimension getPreferredSize(AbstractCommandButton commandButton);
+
+ /**
+ * Returns the preferred icon size of command buttons which use this layout
+ * manager.
+ *
+ * @return The preferred icon size of command buttons which use this layout
+ * manager.
+ */
+ public int getPreferredIconSize();
+
+ /**
+ * Returns the anchor center point of the key tip of the specified command
+ * button.
+ *
+ * @param commandButton
+ * Command button.
+ * @return The anchor center point of the key tip of the specified command
+ * button.
+ */
+ public Point getKeyTipAnchorCenterPoint(AbstractCommandButton commandButton);
+
+ /**
+ * Returns the layout information for the specified command button.
+ *
+ * @param commandButton
+ * Command button.
+ * @param g
+ * Graphics context.
+ * @return The layout information for the specified command button.
+ */
+ public CommandButtonLayoutInfo getLayoutInfo(
+ AbstractCommandButton commandButton, Graphics g);
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/CommandToggleButtonGroup.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/CommandToggleButtonGroup.java
new file mode 100644
index 0000000..1161bcd
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/CommandToggleButtonGroup.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.Serializable;
+import java.util.*;
+
+import javax.swing.ButtonGroup;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+/**
+ * Group of command toggle buttons. Unlike the {@link ButtonGroup}, this class
+ * operates on buttons and not on button models.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class CommandToggleButtonGroup implements Serializable {
+ /**
+ * Contains all group buttons.
+ */
+ protected Vector<JCommandToggleButton> buttons;
+
+ /**
+ * Map of registered model change listeners.
+ */
+ protected Map<JCommandToggleButton, ChangeListener> modelChangeListeners;
+
+ /**
+ * Property change support to track the registered property change
+ * listeners.
+ */
+ private PropertyChangeSupport changeSupport;
+
+ /**
+ * Name of the property change event fired when the group selection is
+ * changed.
+ */
+ public static final String SELECTED_PROPERTY = "selected";
+
+ /**
+ * The currently selected button. Can be <code>null</code>.
+ */
+ protected JCommandToggleButton selection;
+
+ /**
+ * If <code>false</code>, the selection cannot be cleared. By default the
+ * button group allows clearing the selection in {@link #clearSelection()}
+ * or {@link #setSelected(JCommandToggleButton, boolean)} (passing the
+ * currently selected button and <code>false</code>).
+ */
+ protected boolean allowsClearingSelection;
+
+ /**
+ * Creates a new button group.
+ */
+ public CommandToggleButtonGroup() {
+ this.buttons = new Vector<JCommandToggleButton>();
+ this.modelChangeListeners = new HashMap<JCommandToggleButton, ChangeListener>();
+ this.allowsClearingSelection = true;
+ }
+
+ /**
+ * Sets the new value for clearing selection. If <code>true</code> is
+ * passed, the selection can be cleared in {@link #clearSelection()} or
+ * {@link #setSelected(JCommandToggleButton, boolean)} (passing the
+ * currently selected button and <code>false</code>).
+ *
+ * @param allowsClearingSelection
+ * The new value for clearing selection.
+ */
+ public void setAllowsClearingSelection(boolean allowsClearingSelection) {
+ this.allowsClearingSelection = allowsClearingSelection;
+ }
+
+ /**
+ * Returns the current value for clearing selection. <code>true</code> is
+ * returned when selection can be cleared in {@link #clearSelection()} or
+ * {@link #setSelected(JCommandToggleButton, boolean)} (passing the
+ * currently selected button and <code>false</code>).
+ *
+ * @return The current value for clearing selection.
+ */
+ public boolean isAllowsClearingSelection() {
+ return allowsClearingSelection;
+ }
+
+ /**
+ * Adds the specified button to the group. If the button is selected, and
+ * the group has a selected button, the newly added button is marked as
+ * unselected.
+ *
+ * @param b
+ * The button to be added.
+ */
+ public void add(final JCommandToggleButton b) {
+ if (b == null) {
+ return;
+ }
+ buttons.addElement(b);
+
+ boolean wasSelectionNull = (this.selection == null);
+ if (b.getActionModel().isSelected()) {
+ if (wasSelectionNull) {
+ selection = b;
+ } else {
+ b.getActionModel().setSelected(false);
+ }
+ }
+ ChangeListener cl = new ChangeListener() {
+ boolean wasSelected = b.getActionModel().isSelected();
+
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ boolean isSelected = b.getActionModel().isSelected();
+ if (wasSelected != isSelected)
+ setSelected(b, isSelected);
+ wasSelected = isSelected;
+ }
+ };
+
+ b.getActionModel().addChangeListener(cl);
+ this.modelChangeListeners.put(b, cl);
+
+ if (wasSelectionNull) {
+ this.firePropertyChange(SELECTED_PROPERTY, null, b);
+ }
+ }
+
+ /**
+ * Removes the specified button from the group.
+ *
+ * @param b
+ * The button to be removed
+ */
+ public void remove(JCommandToggleButton b) {
+ if (b == null) {
+ return;
+ }
+ buttons.removeElement(b);
+ boolean wasSelected = (b == selection);
+ if (wasSelected) {
+ selection = null;
+ }
+ b.getActionModel().removeChangeListener(
+ this.modelChangeListeners.get(b));
+ this.modelChangeListeners.remove(b);
+
+ if (wasSelected) {
+ this.firePropertyChange(SELECTED_PROPERTY, b, null);
+ }
+ }
+
+ /**
+ * Changes the selected status of the specified button.
+ *
+ * @param button
+ * Button.
+ * @param isSelected
+ * Selection indication.
+ */
+ public void setSelected(JCommandToggleButton button, boolean isSelected) {
+ if (isSelected && button != null && button != selection) {
+ JCommandToggleButton oldSelection = selection;
+ selection = button;
+ if (oldSelection != null) {
+ oldSelection.getActionModel().setSelected(false);
+ }
+ button.getActionModel().setSelected(true);
+
+ this.firePropertyChange(SELECTED_PROPERTY, oldSelection, button);
+ }
+
+ if (!isSelected && (button != null) && (button == selection)) {
+ if (this.allowsClearingSelection) {
+ selection = null;
+ button.getActionModel().setSelected(false);
+
+ this.firePropertyChange(SELECTED_PROPERTY, button, null);
+ } else {
+ // set the button back to selected
+ button.getActionModel().setSelected(true);
+ }
+ }
+ }
+
+ /**
+ * Returns the selected button of this group.
+ *
+ * @return The selected button of this group. The result can be
+ * <code>null</code>.
+ */
+ public JCommandToggleButton getSelected() {
+ return this.selection;
+ }
+
+ /**
+ * Clears the selection of this button group.
+ */
+ public void clearSelection() {
+ if (this.allowsClearingSelection && (this.selection != null)) {
+ this.selection.getActionModel().setSelected(false);
+ }
+ }
+
+ /**
+ * Adds the specified property change listener on this button group.
+ *
+ * @param listener
+ * Listener to add.
+ */
+ public synchronized void addPropertyChangeListener(
+ PropertyChangeListener listener) {
+ if (listener == null) {
+ return;
+ }
+ if (changeSupport == null) {
+ changeSupport = new PropertyChangeSupport(this);
+ }
+ changeSupport.addPropertyChangeListener(listener);
+ }
+
+ /**
+ * Removes the specified property change listener from this button group.
+ *
+ * @param listener
+ * Listener to remove.
+ */
+ public synchronized void removePropertyChangeListener(
+ PropertyChangeListener listener) {
+ if (listener == null || changeSupport == null) {
+ return;
+ }
+ changeSupport.removePropertyChangeListener(listener);
+ }
+
+ /**
+ * Fires a property change event on all registered listeners.
+ *
+ * @param propertyName
+ * Name of the changed property.
+ * @param oldValue
+ * Old property value.
+ * @param newValue
+ * New property value.
+ */
+ protected void firePropertyChange(String propertyName, Object oldValue,
+ Object newValue) {
+ PropertyChangeSupport changeSupport = this.changeSupport;
+ if (changeSupport == null || oldValue == newValue) {
+ return;
+ }
+ changeSupport.firePropertyChange(propertyName, oldValue, newValue);
+ }
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/HorizontalAlignment.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/HorizontalAlignment.java
new file mode 100644
index 0000000..40117a9
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/HorizontalAlignment.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+/**
+ * Enumerates the available values for horizontal alignment.
+ *
+ * @author Kirill Grouchnikov
+ */
+public enum HorizontalAlignment {
+ /**
+ * If the preferred width is less than the available width, the relevant
+ * component is placed at the leading position in its parent (left for LTR
+ * and right for RTL).
+ */
+ LEADING,
+
+ /**
+ * If the preferred width is less than the available width, the relevant
+ * component is horizontally centerd in its parent.
+ */
+ CENTER,
+
+ /**
+ * If the preferred width is less than the available width, the relevant
+ * component is placed at the trailing position in its parent (right for LTR
+ * and left for RTL).
+ */
+ TRAILING,
+
+ /**
+ * The component is placed to fill all available width from its parent.
+ */
+ FILL
+}
\ No newline at end of file
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandButton.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandButton.java
new file mode 100644
index 0000000..73b7c31
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandButton.java
@@ -0,0 +1,871 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+import javax.swing.event.*;
+
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.model.ActionRepeatableButtonModel;
+import org.pushingpixels.flamingo.api.common.model.PopupButtonModel;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelCallback;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonUI;
+import org.pushingpixels.flamingo.internal.ui.common.CommandButtonUI;
+
+/**
+ * Command button.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JCommandButton extends AbstractCommandButton {
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "CommandButtonUI";
+
+ /**
+ * Associated popup callback. May be <code>null</code>.
+ *
+ * @see #setPopupCallback(PopupPanelCallback)
+ * @see #getPopupCallback()
+ */
+ protected PopupPanelCallback popupCallback;
+
+ /**
+ * The command button kind of this button.
+ *
+ * @see #setCommandButtonKind(CommandButtonKind)
+ * @see #getCommandButtonKind()
+ */
+ protected CommandButtonKind commandButtonKind;
+
+ /**
+ * The popup orientation kind of this button.
+ *
+ * @see #setPopupOrientationKind(CommandButtonPopupOrientationKind)
+ * @see #getPopupOrientationKind()
+ */
+ protected CommandButtonPopupOrientationKind popupOrientationKind;
+
+ /**
+ * Indicates the auto-repeat action mode. When the button is not in the
+ * auto-repeat action mode, the registered action listeners are activated
+ * when the mouse is released (just as with the base {@link AbstractButton}
+ * ). When the button is in auto-repeat mode, the registered action
+ * listeners are activated when the mouse is pressed. In addition, if the
+ * mouse is still pressed after {@link #getAutoRepeatInitialInterval()}, the
+ * action listeners will be activated every
+ * {@link #getAutoRepeatSubsequentInterval()} until the button is disabled
+ * or the mouse is released.
+ *
+ * @see #autoRepeatInitialInterval
+ * @see #autoRepeatSubsequentInterval
+ * @see #setAutoRepeatAction(boolean)
+ * @see #isAutoRepeatAction()
+ */
+ protected boolean isAutoRepeatAction;
+
+ /**
+ * The initial interval for invoking the registered action listeners in the
+ * auto-repeat action mode.
+ *
+ * @see #isAutoRepeatAction
+ * @see #autoRepeatSubsequentInterval
+ * @see #getAutoRepeatInitialInterval()
+ * @see #setAutoRepeatActionIntervals(int, int)
+ */
+ protected int autoRepeatInitialInterval;
+
+ /**
+ * The subsequent interval for invoking the registered action listeners in
+ * the auto-repeat action mode.
+ *
+ * @see #isAutoRepeatAction
+ * @see #autoRepeatInitialInterval
+ * @see #getAutoRepeatSubsequentInterval()
+ * @see #setAutoRepeatActionIntervals(int, int)
+ */
+ protected int autoRepeatSubsequentInterval;
+
+ /**
+ * Indicates that rollover should result in firing the action. Used in
+ * conjunction with the {@link #isAutoRepeatAction} can model quick pan
+ * buttons such as breadcrumb bar scrollers.
+ *
+ * @see #setFireActionOnRollover(boolean)
+ * @see #isFireActionOnRollover()
+ */
+ protected boolean isFireActionOnRollover;
+
+ /**
+ * Popup model of this button.
+ *
+ * @see #setPopupModel(PopupButtonModel)
+ * @see #getPopupModel()
+ */
+ protected PopupButtonModel popupModel;
+
+ /**
+ * Default popup handler for this button.
+ */
+ protected PopupHandler popupHandler;
+
+ /**
+ * Rich tooltip for the popup area of this button.
+ *
+ * @see #setPopupRichTooltip(RichTooltip)
+ * @see #getRichTooltip(MouseEvent)
+ */
+ private RichTooltip popupRichTooltip;
+
+ /**
+ * Key tip for the popup area of this button.
+ *
+ * @see #setPopupKeyTip(String)
+ * @see #getPopupKeyTip()
+ */
+ protected String popupKeyTip;
+
+ /**
+ * Enumerates the available command button kinds.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static enum CommandButtonKind {
+ /**
+ * Command button that has only action area.
+ */
+ ACTION_ONLY(true, false),
+
+ /**
+ * Command button that has only popup area.
+ */
+ POPUP_ONLY(false, true),
+
+ /**
+ * Command button that has both action and popup areas, with the main
+ * text click activating the action.
+ */
+ ACTION_AND_POPUP_MAIN_ACTION(true, true),
+
+ /**
+ * Command button that has both action and popup areas, with the main
+ * text click activating the popup.
+ */
+ ACTION_AND_POPUP_MAIN_POPUP(true, true);
+
+ /**
+ * <code>true</code> if the command button kind has an action.
+ */
+ private boolean hasAction;
+
+ /**
+ * <code>true</code> if the command button kind has a popup.
+ */
+ private boolean hasPopup;
+
+ /**
+ * Constructs a new command button kind.
+ *
+ * @param hasAction
+ * Indicates whether the command button kind has an action.
+ * @param hasPopup
+ * Indicates whether the command button kind has a popup.
+ */
+ private CommandButtonKind(boolean hasAction, boolean hasPopup) {
+ this.hasAction = hasAction;
+ this.hasPopup = hasPopup;
+ }
+
+ /**
+ * Returns indication whether this command button kind has an action.
+ *
+ * @return <code>true</code> if the command button kind has an action,
+ * <code>false</code> otherwise.
+ */
+ public boolean hasAction() {
+ return hasAction;
+ }
+
+ /**
+ * Returns indication whether this command button kind has a popup.
+ *
+ * @return <code>true</code> if the command button kind has a popup,
+ * <code>false</code> otherwise.
+ */
+ public boolean hasPopup() {
+ return hasPopup;
+ }
+ }
+
+ /**
+ * Orientation kind for the popup.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static enum CommandButtonPopupOrientationKind {
+ /**
+ * Indicates that the popup should be displayed below the button.
+ */
+ DOWNWARD,
+
+ /**
+ * Indicates that the popup should be displayed to the side of the
+ * button.
+ */
+ SIDEWARD
+ }
+
+ /**
+ * Extension of the default button model that supports the
+ * {@link PopupButtonModel} interface.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class DefaultPopupButtonModel extends DefaultButtonModel
+ implements PopupButtonModel {
+ /**
+ * Timer for the auto-repeat action mode.
+ */
+ protected Timer autoRepeatTimer;
+
+ /**
+ * Identifies the "popup showing" bit in the bitmask, which indicates
+ * that the visibility status of the associated popup.
+ */
+ public final static int POPUP_SHOWING = 1 << 8;
+
+ /**
+ * Creates a new default popup button model.
+ */
+ public DefaultPopupButtonModel() {
+ super();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.PopupButtonModel#addPopupActionListener
+ * (org.jvnet.flamingo.common.PopupActionListener)
+ */
+ @Override
+ public void addPopupActionListener(PopupActionListener l) {
+ listenerList.add(PopupActionListener.class, l);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.PopupButtonModel#removePopupActionListener
+ * (org.jvnet.flamingo.common.PopupActionListener)
+ */
+ @Override
+ public void removePopupActionListener(PopupActionListener l) {
+ listenerList.remove(PopupActionListener.class, l);
+ }
+
+ /**
+ * Notifies all listeners that have registered interest for notification
+ * on this event type.
+ *
+ * @param e
+ * the <code>ActionEvent</code> to deliver to listeners
+ * @see EventListenerList
+ */
+ protected void firePopupActionPerformed(ActionEvent e) {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == PopupActionListener.class) {
+ ((PopupActionListener) listeners[i + 1]).actionPerformed(e);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.DefaultButtonModel#setPressed(boolean)
+ */
+ @Override
+ public void setPressed(boolean b) {
+ if ((isPressed() == b) || !isEnabled()) {
+ return;
+ }
+
+ if (b) {
+ stateMask |= PRESSED;
+ } else {
+ stateMask &= ~PRESSED;
+ }
+
+ if (isPressed() && isArmed()) {
+ // fire the popup action on button press and not on button
+ // release - like the comboboxes
+ int modifiers = 0;
+ AWTEvent currentEvent = EventQueue.getCurrentEvent();
+ if (currentEvent instanceof InputEvent) {
+ modifiers = ((InputEvent) currentEvent).getModifiers();
+ } else if (currentEvent instanceof ActionEvent) {
+ modifiers = ((ActionEvent) currentEvent).getModifiers();
+ }
+ firePopupActionPerformed(new ActionEvent(this,
+ ActionEvent.ACTION_PERFORMED, getActionCommand(),
+ EventQueue.getMostRecentEventTime(), modifiers));
+ }
+
+ fireStateChanged();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.PopupButtonModel#isPopupShowing()
+ */
+ @Override
+ public boolean isPopupShowing() {
+ return (stateMask & POPUP_SHOWING) != 0;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.PopupButtonModel#setPopupShowing(boolean)
+ */
+ @Override
+ public void setPopupShowing(boolean b) {
+ // System.out.println(this.isPopupShowing() + "-->" + b);
+ if (this.isPopupShowing() == b) {
+ return;
+ }
+
+ if (b) {
+ stateMask |= POPUP_SHOWING;
+ } else {
+ stateMask &= ~POPUP_SHOWING;
+ }
+
+ fireStateChanged();
+ }
+ }
+
+ /**
+ * Creates a new command button with empty text
+ *
+ * @param icon
+ * Button icon.
+ */
+ public JCommandButton(ResizableIcon icon) {
+ this(null, icon);
+ }
+
+ /**
+ * Creates a new command button without an icon.
+ *
+ * @param title
+ * Button title. May contain any number of words.
+ */
+ public JCommandButton(String title) {
+ this(title, null);
+ }
+
+ /**
+ * Creates a new command button.
+ *
+ * @param title
+ * Button title. May contain any number of words.
+ * @param icon
+ * Button icon.
+ */
+ public JCommandButton(String title, ResizableIcon icon) {
+ super(title, icon);
+
+ this.setActionModel(new ActionRepeatableButtonModel(this));
+
+ // important - handler creation must be done before setting
+ // the popup model so that it can be registered to track the
+ // changes
+ this.popupHandler = new PopupHandler();
+ this.setPopupModel(new DefaultPopupButtonModel());
+
+ this.commandButtonKind = CommandButtonKind.ACTION_ONLY;
+ this.popupOrientationKind = CommandButtonPopupOrientationKind.DOWNWARD;
+ // this.displayState = CommandButtonDisplayState.CUSTOM;
+ this.isAutoRepeatAction = false;
+ this.autoRepeatInitialInterval = 500;
+ this.autoRepeatSubsequentInterval = 100;
+
+ this.updateUI();
+ }
+
+ /**
+ * Returns the command button kind of this button.
+ *
+ * @return Command button kind of this button.
+ * @see #setCommandButtonKind(CommandButtonKind)
+ */
+ public CommandButtonKind getCommandButtonKind() {
+ return this.commandButtonKind;
+ }
+
+ /**
+ * Sets the kind for this button. Fires a <code>commandButtonKind</code>
+ * property change event.
+ *
+ * @param commandButtonKind
+ * The new button kind.
+ * @see #getCommandButtonKind()
+ */
+ public void setCommandButtonKind(CommandButtonKind commandButtonKind) {
+ CommandButtonKind old = this.commandButtonKind;
+ this.commandButtonKind = commandButtonKind;
+ if (old != this.commandButtonKind) {
+ firePropertyChange("commandButtonKind", old, this.commandButtonKind);
+ }
+ }
+
+ /**
+ * Returns the popup orientation kind of this button.
+ *
+ * @return Popup orientation kind of this button.
+ * @see #setPopupOrientationKind(CommandButtonPopupOrientationKind)
+ */
+ public CommandButtonPopupOrientationKind getPopupOrientationKind() {
+ return this.popupOrientationKind;
+ }
+
+ /**
+ * Sets the popup orientation for this button. Fires a
+ * <code>popupOrientationKind</code> property change event.
+ *
+ * @param popupOrientationKind
+ * The new popup orientation kind.
+ * @see #getPopupOrientationKind()
+ */
+ public void setPopupOrientationKind(
+ CommandButtonPopupOrientationKind popupOrientationKind) {
+ CommandButtonPopupOrientationKind old = this.popupOrientationKind;
+ this.popupOrientationKind = popupOrientationKind;
+ if (old != this.popupOrientationKind) {
+ firePropertyChange("popupOrientationKind", old,
+ this.popupOrientationKind);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((CommandButtonUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicCommandButtonUI.createUI(this));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /**
+ * Returns the associated popup callback.
+ *
+ * @return The associated popup callback.
+ * @see #setPopupCallback(PopupPanelCallback)
+ */
+ public PopupPanelCallback getPopupCallback() {
+ return this.popupCallback;
+ }
+
+ /**
+ * Sets new popup callback for <code>this</code> button.
+ *
+ * @param popupCallback
+ * New popup callback for <code>this</code> button.
+ * @see #getPopupCallback()
+ */
+ public void setPopupCallback(PopupPanelCallback popupCallback) {
+ this.popupCallback = popupCallback;
+ }
+
+ /**
+ * Sets the auto-repeat action indication.
+ *
+ * @param isAutoRepeatAction
+ * If <code>true</code>, pressing the button will activate
+ * auto-repeat action mode. When the button is not in the
+ * auto-repeat action mode, the registered action listeners are
+ * activated when the mouse is released (just as with the base
+ * {@link AbstractButton}). When the button is in auto-repeat
+ * mode, the registered action listeners are activated when the
+ * mouse is pressed. In addition, is the mouse is still pressed
+ * after {@link #getAutoRepeatInitialInterval()}, the action
+ * listeners will be activated every
+ * {@link #getAutoRepeatSubsequentInterval()} until the button is
+ * disabled or the mouse is released.
+ * @see #setAutoRepeatActionIntervals(int, int)
+ * @see #isAutoRepeatAction()
+ */
+ public void setAutoRepeatAction(boolean isAutoRepeatAction) {
+ this.isAutoRepeatAction = isAutoRepeatAction;
+ }
+
+ /**
+ * Sets the intervals for the auto-repeat action mode.
+ *
+ * @param initial
+ * The initial interval for invoking the registered action
+ * listeners in the auto-repeat action mode.
+ * @param subsequent
+ * The subsequent interval for invoking the registered action
+ * listeners in the auto-repeat action mode.
+ * @see #setAutoRepeatAction(boolean)
+ * @see #isAutoRepeatAction()
+ * @see #getAutoRepeatInitialInterval()
+ * @see #getAutoRepeatSubsequentInterval()
+ */
+ public void setAutoRepeatActionIntervals(int initial, int subsequent) {
+ this.autoRepeatInitialInterval = initial;
+ this.autoRepeatSubsequentInterval = subsequent;
+ }
+
+ /**
+ * Returns indication whether the button is in auto-repeat action mode.
+ *
+ * @return <code>true</code> if the button is in auto-repeat action mode,
+ * <code>false</code> otherwise.
+ * @see #setAutoRepeatAction(boolean)
+ * @see #setAutoRepeatActionIntervals(int, int)
+ * @see #getAutoRepeatInitialInterval()
+ * @see #getAutoRepeatSubsequentInterval()
+ */
+ public boolean isAutoRepeatAction() {
+ return this.isAutoRepeatAction;
+ }
+
+ /**
+ * Returns the initial interval for invoking the registered action listeners
+ * in the auto-repeat action mode.
+ *
+ * @return The initial interval for invoking the registered action listeners
+ * in the auto-repeat action mode.
+ * @see #setAutoRepeatActionIntervals(int, int)
+ * @see #setAutoRepeatAction(boolean)
+ * @see #isAutoRepeatAction()
+ * @see #getAutoRepeatSubsequentInterval()
+ */
+ public int getAutoRepeatInitialInterval() {
+ return autoRepeatInitialInterval;
+ }
+
+ /**
+ * Returns the subsequent interval for invoking the registered action
+ * listeners in the auto-repeat action mode.
+ *
+ * @return The subsequent interval for invoking the registered action
+ * listeners in the auto-repeat action mode.
+ * @see #setAutoRepeatActionIntervals(int, int)
+ * @see #setAutoRepeatAction(boolean)
+ * @see #isAutoRepeatAction()
+ * @see #getAutoRepeatInitialInterval()
+ */
+ public int getAutoRepeatSubsequentInterval() {
+ return autoRepeatSubsequentInterval;
+ }
+
+ /**
+ * Sets action-on-rollover mode. When this mode is on, button will fire
+ * action events when it gets rollover (instead of press). Combine with
+ * {@link #setAutoRepeatAction(boolean)} passing <code>true</code> to get
+ * auto-repeat action fired on rollover (useful for quicker manipulation of
+ * scroller buttons, for example).
+ *
+ * @param isFireActionOnRollover
+ * If <code>true</code>, the button is moved into the
+ * action-on-rollover mode.
+ * @see #isFireActionOnRollover()
+ */
+ public void setFireActionOnRollover(boolean isFireActionOnRollover) {
+ this.isFireActionOnRollover = isFireActionOnRollover;
+ }
+
+ /**
+ * Returns indication whether this button is in action-on-rollover mode.
+ *
+ * @return <code>true</code> if this button is in action-on-rollover mode,
+ * <code>false</code> otherwise.
+ * @see #setFireActionOnRollover(boolean)
+ */
+ public boolean isFireActionOnRollover() {
+ return this.isFireActionOnRollover;
+ }
+
+ /**
+ * Returns the popup model of this button.
+ *
+ * @return The popup model of this button.
+ * @see #setPopupModel(PopupButtonModel)
+ */
+ public PopupButtonModel getPopupModel() {
+ return this.popupModel;
+ }
+
+ /**
+ * Sets the new popup model for this button. Fires a <code>popupModel</code>
+ * property change event.
+ *
+ * @param newModel
+ * The new popup model for this button.
+ * @see #getPopupModel()
+ */
+ public void setPopupModel(PopupButtonModel newModel) {
+
+ PopupButtonModel oldModel = getPopupModel();
+
+ if (oldModel != null) {
+ oldModel.removeChangeListener(this.popupHandler);
+ oldModel.removeActionListener(this.popupHandler);
+ }
+
+ this.popupModel = newModel;
+
+ if (newModel != null) {
+ newModel.addChangeListener(this.popupHandler);
+ newModel.addActionListener(this.popupHandler);
+ }
+
+ firePropertyChange("popupModel", oldModel, newModel);
+ if (newModel != oldModel) {
+ revalidate();
+ repaint();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.AbstractCommandButton#setEnabled(boolean)
+ */
+ @Override
+ public void setEnabled(boolean b) {
+ if (!b && popupModel.isRollover()) {
+ popupModel.setRollover(false);
+ }
+ super.setEnabled(b);
+ popupModel.setEnabled(b);
+ }
+
+ /**
+ * Default popup handler.
+ *
+ * @author Kirill Grouchnikov
+ */
+ class PopupHandler implements PopupActionListener, ChangeListener {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ fireStateChanged();
+ repaint();
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ firePopupActionPerformed(event);
+ }
+ }
+
+ /**
+ * Notifies all listeners that have registered interest for notification on
+ * this event type. The event instance is lazily created using the
+ * <code>event</code> parameter.
+ *
+ * @param event
+ * the <code>ActionEvent</code> object
+ * @see EventListenerList
+ */
+ protected void firePopupActionPerformed(ActionEvent event) {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ ActionEvent e = null;
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == PopupActionListener.class) {
+ // Lazily create the event:
+ if (e == null) {
+ String actionCommand = event.getActionCommand();
+ e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
+ actionCommand, event.getWhen(), event
+ .getModifiers());
+ }
+ ((PopupActionListener) listeners[i + 1]).actionPerformed(e);
+ }
+ }
+ }
+
+ @Override
+ boolean hasRichTooltips() {
+ return super.hasRichTooltips() || (this.popupRichTooltip != null);
+ }
+
+ /**
+ * Sets the rich tooltip for the popup area of this button.
+ *
+ * @param richTooltip
+ * Rich tooltip for the popup area of this button.
+ * @see #getRichTooltip(MouseEvent)
+ * @see #setActionRichTooltip(RichTooltip)
+ */
+ public void setPopupRichTooltip(RichTooltip richTooltip) {
+ this.popupRichTooltip = richTooltip;
+ RichToolTipManager richToolTipManager = RichToolTipManager
+ .sharedInstance();
+ if (this.hasRichTooltips()) {
+ richToolTipManager.registerComponent(this);
+ } else {
+ richToolTipManager.unregisterComponent(this);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.AbstractCommandButton#getRichTooltip(java.awt
+ * .event.MouseEvent)
+ */
+ @Override
+ public RichTooltip getRichTooltip(MouseEvent event) {
+ CommandButtonUI ui = this.getUI();
+ if (ui.getLayoutInfo().actionClickArea.contains(event.getPoint()))
+ return super.getRichTooltip(event);
+ if (ui.getLayoutInfo().popupClickArea.contains(event.getPoint()))
+ return this.popupRichTooltip;
+ return null;
+ }
+
+ /**
+ * Returns the key tip for the popup area of this button.
+ *
+ * @return The key tip for the popup area of this button.
+ * @see #setPopupKeyTip(String)
+ * @see #getActionKeyTip()
+ */
+ public String getPopupKeyTip() {
+ return this.popupKeyTip;
+ }
+
+ /**
+ * Sets the key tip for the popup area of this button. Fires a
+ * <code>popupKeyTip</code> property change event.
+ *
+ * @param popupKeyTip
+ * The key tip for the popup area of this button.
+ * @see #getPopupKeyTip()
+ * @see #setActionKeyTip(String)
+ */
+ public void setPopupKeyTip(String popupKeyTip) {
+ if (!canHaveBothKeyTips() && (popupKeyTip != null)
+ && (this.actionKeyTip != null)) {
+ throw new IllegalArgumentException(
+ "Action *and* popup keytips are not supported at the same time");
+ }
+
+ String old = this.popupKeyTip;
+ this.popupKeyTip = popupKeyTip;
+ this.firePropertyChange("popupKeyTip", old, this.popupKeyTip);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.AbstractCommandButton#setActionKeyTip(java.
+ * lang.String)
+ */
+ @Override
+ public void setActionKeyTip(String actionKeyTip) {
+ if (!canHaveBothKeyTips() && (popupKeyTip != null)
+ && (this.actionKeyTip != null)) {
+ throw new IllegalArgumentException(
+ "Action *and* popup keytips are not supported at the same time");
+ }
+
+ super.setActionKeyTip(actionKeyTip);
+ }
+
+ /**
+ * Returns indication whether key tips can be installed on both action and
+ * popup areas of this button. This method is for internal use only.
+ *
+ * @return <code>true</code> if key tips can be installed on both action and
+ * popup areas of this button, <code>false</code> otherwise.
+ */
+ boolean canHaveBothKeyTips() {
+ return false;
+ }
+
+ /**
+ * Programmatically perform a "click" on the popup area. This does the same
+ * thing as if the user had pressed and released the popup area of the
+ * button.
+ */
+ public void doPopupClick() {
+ Dimension size = getSize();
+ PopupButtonModel popupModel = this.getPopupModel();
+ popupModel.setArmed(true);
+ popupModel.setPressed(true);
+ paintImmediately(new Rectangle(0, 0, size.width, size.height));
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ie) {
+ }
+ popupModel.setPressed(false);
+ popupModel.setArmed(false);
+ popupModel.setPopupShowing(true);
+ paintImmediately(new Rectangle(0, 0, size.width, size.height));
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandButtonPanel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandButtonPanel.java
new file mode 100644
index 0000000..6d210fd
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandButtonPanel.java
@@ -0,0 +1,835 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonPanelUI;
+import org.pushingpixels.flamingo.internal.ui.common.CommandButtonPanelUI;
+
+/**
+ * Panel that hosts command buttons. Provides support for button groups, single
+ * selection mode (for toggle command buttons), same icon state / dimension and
+ * column-fill / row-fill layout.
+ *
+ * <p>
+ * Under the default {@link LayoutKind#ROW_FILL}, the buttons are laid out in
+ * rows, never exceeding the available horizontal space. A vertical scroll bar
+ * will kick in once there is not enough vertical space to show all the buttons.
+ * The schematic below shows a row-fill command button panel:
+ * </p>
+ *
+ * <pre>
+ * +-----------------------------+-+
+ * | | |
+ * | +----+ +----+ +----+ +----+ | |
+ * | | 01 | | 02 | | 03 | | 04 | | |
+ * | +----+ +----+ +----+ +----+ | |
+ * | | |
+ * | +----+ +----+ +----+ +----+ | |
+ * | | 05 | | 06 | | 07 | | 07 | | |
+ * | +----+ +----+ +----+ +----+ | |
+ * | | |
+ * | +----+ +----+ +----+ +----+ | |
+ * | | 09 | | 10 | | 11 | | 12 | | |
+ * | +----+ +----+ +----+ +----+ | |
+ * | | |
+ * | +----+ +----+ +----+ +----+ | |
+ * | | 13 | | 14 | | 15 | | 16 | | |
+ * +-----------------------------+-+
+ * </pre>
+ *
+ * <p>
+ * Each row hosts four buttons, and the vertical scroll bar allows scrolling the
+ * content down.
+ * </p>
+ *
+ * <p>
+ * Under the {@link LayoutKind#COLUMN_FILL}, the buttons are laid out in
+ * columns, never exceeding the available vertical space. A horizontal scroll
+ * bar will kick in once there is not enough horizontal space to show all the
+ * buttons. The schematic below shows a column-fill command button panel:
+ * </p>
+ *
+ * <pre>
+ * +---------------------------------+
+ * | |
+ * | +----+ +----+ +----+ +----+ +---|
+ * | | 01 | | 04 | | 07 | | 10 | | 13|
+ * | +----+ +----+ +----+ +----+ +---|
+ * | |
+ * | +----+ +----+ +----+ +----+ +---|
+ * | | 02 | | 05 | | 08 | | 11 | | 14|
+ * | +----+ +----+ +----+ +----+ +---|
+ * | |
+ * | +----+ +----+ +----+ +----+ +---|
+ * | | 03 | | 06 | | 09 | | 12 | | 15|
+ * | +----+ +----+ +----+ +----+ +---|
+ * | |
+ * +---------------------------------+
+ * +---------------------------------+
+ * </pre>
+ *
+ * <p>
+ * Each column hosts three buttons, and the horizontal scroll bar allows
+ * scrolling the content down.
+ * </p>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JCommandButtonPanel extends JPanel implements Scrollable {
+ /**
+ * @see #getUIClassID
+ */
+ public static final String uiClassID = "CommandButtonPanelUI";
+
+ /**
+ * List of titles for all button groups.
+ *
+ * @see #getGroupCount()
+ * @see #getGroupTitleAt(int)
+ */
+ protected List<String> groupTitles;
+
+ /**
+ * List of all button groups.
+ *
+ * @see #getGroupCount()
+ * @see #getGroupButtons(int)
+ */
+ protected List<List<AbstractCommandButton>> buttons;
+
+ /**
+ * Maximum number of columns for this panel. Relevant only when the layout
+ * kind is {@link LayoutKind#ROW_FILL}.
+ *
+ * @see #getMaxButtonColumns()
+ * @see #setMaxButtonColumns(int)
+ */
+ protected int maxButtonColumns;
+
+ /**
+ * Maximum number of rows for this panel. Relevant only when the layout kind
+ * is {@link LayoutKind#COLUMN_FILL}.
+ *
+ * @see #getMaxButtonRows()
+ * @see #setMaxButtonRows(int)
+ */
+ protected int maxButtonRows;
+
+ /**
+ * Indicates the selection mode for the {@link JCommandToggleButton} in this
+ * panel.
+ *
+ * @see #setSingleSelectionMode(boolean)
+ */
+ protected boolean isSingleSelectionMode;
+
+ /**
+ * If <code>true</code>, the panel will show group labels.
+ *
+ * @see #setToShowGroupLabels(boolean)
+ * @see #isToShowGroupLabels()
+ */
+ protected boolean toShowGroupLabels;
+
+ /**
+ * The button group for the single selection mode.
+ */
+ protected CommandToggleButtonGroup buttonGroup;
+
+ /**
+ * Current icon dimension.
+ */
+ protected int currDimension;
+
+ /**
+ * Current icon state.
+ */
+ protected CommandButtonDisplayState currState;
+
+ /**
+ * Layout kind of this button panel.
+ *
+ * @see #getLayoutKind()
+ * @see #setLayoutKind(LayoutKind)
+ */
+ protected LayoutKind layoutKind;
+
+ /**
+ * Enumerates the available layout kinds.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public enum LayoutKind {
+ /**
+ * The buttons are layed out in rows respecting the available width.
+ */
+ ROW_FILL,
+
+ /**
+ * The buttons are layed out in columns respecting the available height.
+ */
+ COLUMN_FILL
+ }
+
+ /**
+ * Creates a new panel.
+ */
+ protected JCommandButtonPanel() {
+ this.buttons = new ArrayList<List<AbstractCommandButton>>();
+ this.groupTitles = new ArrayList<String>();
+ this.maxButtonColumns = -1;
+ this.maxButtonRows = -1;
+ this.isSingleSelectionMode = false;
+ this.toShowGroupLabels = true;
+ this.setLayoutKind(LayoutKind.ROW_FILL);
+ }
+
+ /**
+ * Creates a new panel.
+ *
+ * @param startingDimension
+ * Initial dimension for buttons.
+ */
+ public JCommandButtonPanel(int startingDimension) {
+ this();
+ this.currDimension = startingDimension;
+ this.currState = CommandButtonDisplayState.FIT_TO_ICON;
+ this.updateUI();
+ }
+
+ /**
+ * Creates a new panel.
+ *
+ * @param startingState
+ * Initial state for buttons.
+ */
+ public JCommandButtonPanel(CommandButtonDisplayState startingState) {
+ this();
+ this.currDimension = -1;
+ this.currState = startingState;
+ this.updateUI();
+ }
+
+ /**
+ * Adds a new button group at the specified index.
+ *
+ * @param buttonGroupName
+ * Button group name.
+ * @param groupIndex
+ * Button group index.
+ * @see #addButtonGroup(String)
+ * @see #removeButtonGroup(String)
+ * @see #removeAllGroups()
+ */
+ public void addButtonGroup(String buttonGroupName, int groupIndex) {
+ this.groupTitles.add(groupIndex, buttonGroupName);
+ List<AbstractCommandButton> list = new ArrayList<AbstractCommandButton>();
+ this.buttons.add(groupIndex, list);
+ this.fireStateChanged();
+ }
+
+ /**
+ * Adds a new button group after all the existing button groups.
+ *
+ * @param buttonGroupName
+ * Button group name.
+ * @see #addButtonGroup(String, int)
+ * @see #removeButtonGroup(String)
+ * @see #removeAllGroups()
+ */
+ public void addButtonGroup(String buttonGroupName) {
+ this.addButtonGroup(buttonGroupName, this.groupTitles.size());
+ }
+
+ /**
+ * Removes the specified button group.
+ *
+ * @param buttonGroupName
+ * Name of the button group to remove.
+ * @see #addButtonGroup(String)
+ * @see #addButtonGroup(String, int)
+ * @see #removeAllGroups()
+ */
+ public void removeButtonGroup(String buttonGroupName) {
+ int groupIndex = this.groupTitles.indexOf(buttonGroupName);
+ if (groupIndex < 0)
+ return;
+ this.groupTitles.remove(groupIndex);
+ List<AbstractCommandButton> list = this.buttons.get(groupIndex);
+ if (list != null) {
+ for (AbstractCommandButton button : list) {
+ this.remove(button);
+ if (this.isSingleSelectionMode
+ && (button instanceof JCommandToggleButton)) {
+ this.buttonGroup.remove((JCommandToggleButton) button);
+ }
+ }
+ }
+ this.buttons.remove(groupIndex);
+ this.fireStateChanged();
+ }
+
+ /**
+ * Adds a new button to the specified button group.
+ *
+ * @param commandButton
+ * Button to add.
+ * @return Returns the index of the button on the specified group, or -1 if
+ * no such group exists.
+ * @see #addButtonToGroup(String, AbstractCommandButton)
+ * @see #addButtonToGroup(String, int, AbstractCommandButton)
+ * @see #removeButtonFromGroup(String, int)
+ */
+ public int addButtonToLastGroup(AbstractCommandButton commandButton) {
+ if (this.groupTitles.size() == 0)
+ return -1;
+ int groupIndex = this.groupTitles.size() - 1;
+ commandButton.setDisplayState(this.currState);
+ return this.addButtonToGroup(this.groupTitles.get(groupIndex),
+ this.buttons.get(groupIndex).size(), commandButton);
+ }
+
+ /**
+ * Adds a new button to the specified button group.
+ *
+ * @param buttonGroupName
+ * Name of the button group.
+ * @param commandButton
+ * Button to add.
+ * @return Returns the index of the button on the specified group, or -1 if
+ * no such group exists.
+ * @see #addButtonToGroup(String, int, AbstractCommandButton)
+ * @see #addButtonToLastGroup(AbstractCommandButton)
+ * @see #removeButtonFromGroup(String, int)
+ */
+ public int addButtonToGroup(String buttonGroupName,
+ AbstractCommandButton commandButton) {
+ int groupIndex = this.groupTitles.indexOf(buttonGroupName);
+ if (groupIndex < 0)
+ return -1;
+ commandButton.setDisplayState(this.currState);
+ return this.addButtonToGroup(buttonGroupName, this.buttons.get(
+ groupIndex).size(), commandButton);
+ }
+
+ /**
+ * Adds a new button to the specified button group.
+ *
+ * @param buttonGroupName
+ * Name of the button group.
+ * @param indexInGroup
+ * Index of the button in group.
+ * @param commandButton
+ * Button to add.
+ * @return Returns the index of the button on the specified group, or -1 if
+ * no such group exists.
+ * @see #addButtonToGroup(String, int, AbstractCommandButton)
+ * @see #addButtonToLastGroup(AbstractCommandButton)
+ * @see #removeButtonFromGroup(String, int)
+ */
+ public int addButtonToGroup(String buttonGroupName, int indexInGroup,
+ AbstractCommandButton commandButton) {
+ int groupIndex = this.groupTitles.indexOf(buttonGroupName);
+ if (groupIndex < 0)
+ return -1;
+ // commandButton.setState(ElementState.ORIG, true);
+ this.add(commandButton);
+ this.buttons.get(groupIndex).add(indexInGroup, commandButton);
+ if (this.isSingleSelectionMode
+ && (commandButton instanceof JCommandToggleButton)) {
+ this.buttonGroup.add((JCommandToggleButton) commandButton);
+ }
+ this.fireStateChanged();
+ return indexInGroup;
+ }
+
+ /**
+ * Removes the button at the specified index from the specified button
+ * group.
+ *
+ * @param buttonGroupName
+ * Name of the button group.
+ * @param indexInGroup
+ * Index of the button to remove.
+ * @see #addButtonToGroup(String, AbstractCommandButton)
+ * @see #addButtonToGroup(String, int, AbstractCommandButton)
+ * @see #addButtonToLastGroup(AbstractCommandButton)
+ */
+ public void removeButtonFromGroup(String buttonGroupName, int indexInGroup) {
+ int groupIndex = this.groupTitles.indexOf(buttonGroupName);
+ if (groupIndex < 0)
+ return;
+
+ AbstractCommandButton removed = this.buttons.get(groupIndex).remove(
+ indexInGroup);
+ this.remove(removed);
+ if (this.isSingleSelectionMode
+ && (removed instanceof JCommandToggleButton)) {
+ this.buttonGroup.remove((JCommandToggleButton) removed);
+ }
+ this.fireStateChanged();
+ }
+
+ /**
+ * Removes all the button groups and buttons from this panel.
+ *
+ * @see #addButtonGroup(String, int)
+ * @see #addButtonGroup(String)
+ * @see #removeButtonGroup(String)
+ * @see #removeButtonFromGroup(String, int)
+ */
+ public void removeAllGroups() {
+ for (List<AbstractCommandButton> ljcb : this.buttons) {
+ for (AbstractCommandButton jcb : ljcb) {
+ if (this.isSingleSelectionMode
+ && (jcb instanceof JCommandToggleButton)) {
+ this.buttonGroup.remove((JCommandToggleButton) jcb);
+ }
+ this.remove(jcb);
+ }
+ }
+ this.buttons.clear();
+ this.groupTitles.clear();
+ this.fireStateChanged();
+ }
+
+ /**
+ * Returns the number of button groups in this panel.
+ *
+ * @return Number of button groups in this panel.
+ */
+ public int getGroupCount() {
+ if (this.groupTitles == null)
+ return 0;
+ return this.groupTitles.size();
+ }
+
+ /**
+ * Returns the number of buttons in this panel.
+ *
+ * @return Number of buttons in this panel.
+ */
+ public int getButtonCount() {
+ int result = 0;
+ for (List<AbstractCommandButton> ljcb : this.buttons) {
+ result += ljcb.size();
+ }
+ return result;
+ }
+
+ /**
+ * Returns the title of the button group at the specified index.
+ *
+ * @param index
+ * Button group index.
+ * @return Title of the button group at the specified index.
+ */
+ public String getGroupTitleAt(int index) {
+ return this.groupTitles.get(index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((CommandButtonPanelUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicCommandButtonPanelUI.createUI(this));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /**
+ * Sets the maximum button columns for this panel. When this panel is shown
+ * and the layout kind is {@link LayoutKind#ROW_FILL}, it will have no more
+ * than this number of buttons in each row. Fires a
+ * <code>maxButtonColumns</code> property change event.
+ *
+ * @param maxButtonColumns
+ * Maximum button columns for this panel.
+ * @see #getMaxButtonColumns()
+ * @see #setMaxButtonRows(int)
+ */
+ public void setMaxButtonColumns(int maxButtonColumns) {
+ if (maxButtonColumns != this.maxButtonColumns) {
+ int oldValue = this.maxButtonColumns;
+ this.maxButtonColumns = maxButtonColumns;
+ this.firePropertyChange("maxButtonColumns", oldValue,
+ this.maxButtonColumns);
+ }
+ }
+
+ /**
+ * Returns the maximum button columns for this panel. The return value is
+ * relevant only when the layout kind is {@link LayoutKind#ROW_FILL}.
+ *
+ * @return Maximum button columns for this panel.
+ * @see #setMaxButtonColumns(int)
+ * @see #getMaxButtonRows()
+ */
+ public int getMaxButtonColumns() {
+ return this.maxButtonColumns;
+ }
+
+ /**
+ * Sets the maximum button rows for this panel. When this panel is shown and
+ * the layout kind is {@link LayoutKind#COLUMN_FILL}, it will have no more
+ * than this number of buttons in each column. Fires a
+ * <code>maxButtonRows</code> property change event.
+ *
+ * @param maxButtonRows
+ * Maximum button rows for this panel.
+ * @see #getMaxButtonRows()
+ * @see #setMaxButtonColumns(int)
+ */
+ public void setMaxButtonRows(int maxButtonRows) {
+ if (maxButtonRows != this.maxButtonRows) {
+ int oldValue = this.maxButtonRows;
+ this.maxButtonRows = maxButtonRows;
+ this.firePropertyChange("maxButtonRows", oldValue,
+ this.maxButtonRows);
+ }
+ }
+
+ /**
+ * Returns the maximum button rows for this panel. The return value is
+ * relevant only when the layout kind is {@link LayoutKind#COLUMN_FILL}.
+ *
+ * @return Maximum button rows for this panel.
+ * @see #setMaxButtonRows(int)
+ * @see #getMaxButtonColumns()
+ */
+ public int getMaxButtonRows() {
+ return this.maxButtonRows;
+ }
+
+ /**
+ * Returns the list of all buttons in the specified button group.
+ *
+ * @param groupIndex
+ * Group index.
+ * @return Unmodifiable view on the list of all buttons in the specified
+ * button group.
+ * @see #getGroupCount()
+ */
+ public List<AbstractCommandButton> getGroupButtons(int groupIndex) {
+ return Collections.unmodifiableList(this.buttons.get(groupIndex));
+ }
+
+ /**
+ * Sets the selection mode for this panel. If <code>true</code> is passed as
+ * the parameter, all {@link JCommandToggleButton} in this panel are set to
+ * belong to the same button group.
+ *
+ * @param isSingleSelectionMode
+ * If <code>true</code>,all {@link JCommandToggleButton} in this
+ * panel are set to belong to the same button group.
+ * @see #getSelectedButton()
+ */
+ public void setSingleSelectionMode(boolean isSingleSelectionMode) {
+ if (this.isSingleSelectionMode == isSingleSelectionMode)
+ return;
+
+ this.isSingleSelectionMode = isSingleSelectionMode;
+ if (this.isSingleSelectionMode) {
+ this.buttonGroup = new CommandToggleButtonGroup();
+ for (List<AbstractCommandButton> ljrb : this.buttons) {
+ for (AbstractCommandButton jrb : ljrb) {
+ if (jrb instanceof JCommandToggleButton) {
+ this.buttonGroup.add((JCommandToggleButton) jrb);
+ }
+ }
+ }
+ } else {
+ for (List<AbstractCommandButton> ljrb : this.buttons) {
+ for (AbstractCommandButton jrb : ljrb) {
+ if (jrb instanceof JCommandToggleButton) {
+ this.buttonGroup.remove((JCommandToggleButton) jrb);
+ }
+ }
+ }
+ this.buttonGroup = null;
+ }
+ }
+
+ /**
+ * Sets indication whether button group labels should be shown. Fires a
+ * <code>toShowGroupLabels</code> property change event.
+ *
+ * @param toShowGroupLabels
+ * If <code>true</code>, this panel will show the labels of the
+ * button groups.
+ * @see #isToShowGroupLabels()
+ */
+ public void setToShowGroupLabels(boolean toShowGroupLabels) {
+ if ((layoutKind == LayoutKind.COLUMN_FILL) && toShowGroupLabels) {
+ throw new IllegalArgumentException(
+ "Column fill layout is not supported when group labels are shown");
+ }
+ if (this.toShowGroupLabels != toShowGroupLabels) {
+ boolean oldValue = this.toShowGroupLabels;
+ this.toShowGroupLabels = toShowGroupLabels;
+ this.firePropertyChange("toShowGroupLabels", oldValue,
+ this.toShowGroupLabels);
+ }
+ }
+
+ /**
+ * Returns indication whether button group labels should be shown.
+ *
+ * @return If <code>true</code>, this panel shows the labels of the button
+ * groups, and <code>false</code> otherwise.
+ * @see #setToShowGroupLabels(boolean)
+ */
+ public boolean isToShowGroupLabels() {
+ return this.toShowGroupLabels;
+ }
+
+ /**
+ * Sets the new dimension for the icons in this panel. The state for all the
+ * icons is set to {@link CommandButtonDisplayState#FIT_TO_ICON}.
+ *
+ * @param dimension
+ * New dimension for the icons in this panel.
+ * @see #setIconState(CommandButtonDisplayState)
+ */
+ public void setIconDimension(int dimension) {
+ this.currDimension = dimension;
+ this.currState = CommandButtonDisplayState.FIT_TO_ICON;
+ for (List<AbstractCommandButton> buttonList : this.buttons) {
+ for (AbstractCommandButton button : buttonList) {
+ button.updateCustomDimension(dimension);
+ }
+ }
+ this.revalidate();
+ this.doLayout();
+ this.repaint();
+ }
+
+ /**
+ * Sets the new state for the icons in this panel. The dimension for all the
+ * icons is set to -1; this method should only be called with a state that
+ * has an associated default size (like
+ * {@link CommandButtonDisplayState#BIG},
+ * {@link CommandButtonDisplayState#TILE},
+ * {@link CommandButtonDisplayState#MEDIUM} and
+ * {@link CommandButtonDisplayState#SMALL}).
+ *
+ * @param state
+ * New state for the icons in this panel.
+ * @see #setIconDimension(int)
+ */
+ public void setIconState(CommandButtonDisplayState state) {
+ this.currDimension = -1;
+ this.currState = state;
+ for (List<AbstractCommandButton> ljrb : this.buttons) {
+ for (AbstractCommandButton jrb : ljrb) {
+ jrb.setDisplayState(state);
+ jrb.revalidate();
+ jrb.doLayout();
+ }
+ }
+ this.revalidate();
+ this.doLayout();
+ this.repaint();
+ }
+
+ /**
+ * Returns the selected button of this panel. Only relevant for single
+ * selection mode (set by {@link #setSingleSelectionMode(boolean)}),
+ * returning <code>null</code> otherwise.
+ *
+ * @return The selected button of this panel.
+ * @see #setSingleSelectionMode(boolean)
+ */
+ public JCommandToggleButton getSelectedButton() {
+ if (this.isSingleSelectionMode) {
+ for (List<AbstractCommandButton> ljrb : this.buttons) {
+ for (AbstractCommandButton jrb : ljrb) {
+ if (jrb instanceof JCommandToggleButton) {
+ JCommandToggleButton jctb = (JCommandToggleButton) jrb;
+ if (jctb.getActionModel().isSelected())
+ return jctb;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the layout kind of this panel.
+ *
+ * @return Layout kind of this panel.
+ * @see #setLayoutKind(LayoutKind)
+ */
+ public LayoutKind getLayoutKind() {
+ return layoutKind;
+ }
+
+ /**
+ * Sets the new layout kind for this panel. Fires a <code>layoutKind</code>
+ * property change event.
+ *
+ * @param layoutKind
+ * New layout kind for this panel.
+ * @see #getLayoutKind()
+ */
+ public void setLayoutKind(LayoutKind layoutKind) {
+ if (layoutKind == null)
+ throw new IllegalArgumentException("Layout kind cannot be null");
+ if ((layoutKind == LayoutKind.COLUMN_FILL)
+ && this.isToShowGroupLabels()) {
+ throw new IllegalArgumentException(
+ "Column fill layout is not supported when group labels are shown");
+ }
+ if (layoutKind != this.layoutKind) {
+ LayoutKind old = this.layoutKind;
+ this.layoutKind = layoutKind;
+ this.firePropertyChange("layoutKind", old, this.layoutKind);
+ }
+ }
+
+ /**
+ * Adds the specified change listener to this button panel.
+ *
+ * @param l
+ * Change listener to add.
+ * @see #removeChangeListener(ChangeListener)
+ */
+ public void addChangeListener(ChangeListener l) {
+ this.listenerList.add(ChangeListener.class, l);
+ }
+
+ /**
+ * Removes the specified change listener from this button panel.
+ *
+ * @param l
+ * Change listener to remove.
+ * @see #addChangeListener(ChangeListener)
+ */
+ public void removeChangeListener(ChangeListener l) {
+ this.listenerList.remove(ChangeListener.class, l);
+ }
+
+ /**
+ * Notifies all registered listener that the state of this command button
+ * panel has changed.
+ */
+ protected void fireStateChanged() {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ ChangeEvent event = new ChangeEvent(this);
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ChangeListener.class) {
+ ((ChangeListener) listeners[i + 1]).stateChanged(event);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Scrollable#getPreferredScrollableViewportSize()
+ */
+ @Override
+ public Dimension getPreferredScrollableViewportSize() {
+ return this.getPreferredSize();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.Scrollable#getScrollableBlockIncrement(java.awt.Rectangle,
+ * int, int)
+ */
+ @Override
+ public int getScrollableBlockIncrement(Rectangle visibleRect,
+ int orientation, int direction) {
+ return 30;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Scrollable#getScrollableTracksViewportHeight()
+ */
+ @Override
+ public boolean getScrollableTracksViewportHeight() {
+ return (this.layoutKind == LayoutKind.COLUMN_FILL);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Scrollable#getScrollableTracksViewportWidth()
+ */
+ @Override
+ public boolean getScrollableTracksViewportWidth() {
+ return (this.layoutKind == LayoutKind.ROW_FILL);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.Scrollable#getScrollableUnitIncrement(java.awt.Rectangle,
+ * int, int)
+ */
+ @Override
+ public int getScrollableUnitIncrement(Rectangle visibleRect,
+ int orientation, int direction) {
+ return 10;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandButtonStrip.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandButtonStrip.java
new file mode 100644
index 0000000..2c2b0dc
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandButtonStrip.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.awt.Component;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonStripUI;
+import org.pushingpixels.flamingo.internal.ui.common.CommandButtonStripUI;
+
+/**
+ * Button strip component. Provides visual appearance of a strip. The buttons in
+ * the strip are either drawn horizontally with no horizontal space between them
+ * or drawn vertically with no vertical space between them.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JCommandButtonStrip extends JComponent {
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "CommandButtonStripUI";
+
+ /**
+ * Element state for the buttons in this button strip. Default state is
+ * {@link CommandButtonDisplayState#SMALL}.
+ */
+ protected CommandButtonDisplayState displayState;
+
+ /**
+ * Scale factor for horizontal gaps.
+ *
+ * @see #setVGapScaleFactor(double)
+ */
+ protected double hgapScaleFactor;
+
+ /**
+ * Scale factor for vertical gaps.
+ *
+ * @see #setVGapScaleFactor(double)
+ */
+ protected double vgapScaleFactor;
+
+ /**
+ * Button strip orientation.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public enum StripOrientation {
+ /**
+ * Horizontal strip orientation.
+ */
+ HORIZONTAL,
+
+ /**
+ * Vertical strip orientation.
+ */
+ VERTICAL
+ }
+
+ /**
+ * Orientation of <code>this</code> strip.
+ *
+ * @see #getOrientation()
+ */
+ private StripOrientation orientation;
+
+ /**
+ * Creates an empty horizontally-oriented strip.
+ */
+ public JCommandButtonStrip() {
+ this(StripOrientation.HORIZONTAL);
+ }
+
+ /**
+ * Creates an empty strip.
+ *
+ * @param orientation
+ * Orientation for this strip.
+ */
+ public JCommandButtonStrip(StripOrientation orientation) {
+ this.orientation = orientation;
+ this.displayState = CommandButtonDisplayState.SMALL;
+ switch (orientation) {
+ case HORIZONTAL:
+ this.hgapScaleFactor = 0.75;
+ this.vgapScaleFactor = 1.0;
+ break;
+ case VERTICAL:
+ this.hgapScaleFactor = 1.0;
+ this.vgapScaleFactor = 0.75;
+ break;
+ }
+ this.setOpaque(false);
+ updateUI();
+ }
+
+ /**
+ * Sets the display state for the buttons in this button strip. This method
+ * must be called <em>before</em> adding the first command button. The
+ * default state is {@link CommandButtonDisplayState#SMALL}.
+ *
+ * @param elementState
+ * New element state for the buttons in this button strip.
+ */
+ public void setDisplayState(CommandButtonDisplayState elementState) {
+ if (this.getComponentCount() > 0) {
+ throw new IllegalStateException(
+ "Can't call this method after buttons have been already added");
+ }
+ this.displayState = elementState;
+ }
+
+ /**
+ * Sets the horizontal gap scale factor for the buttons in this button
+ * strip. This method must be called <em>before</em> adding the first
+ * command button.
+ *
+ * <p>
+ * The default horizontal gap scale factor for horizontally oriented strips
+ * is 0.75. The default horizontal gap scale factor for vertically oriented
+ * strips is 1.0.
+ * </p>
+ *
+ * @param hgapScaleFactor
+ * New horizontal gap scale factor for the buttons in this button
+ * strip.
+ * @see #setVGapScaleFactor(double)
+ */
+ public void setHGapScaleFactor(double hgapScaleFactor) {
+ if (this.getComponentCount() > 0) {
+ throw new IllegalStateException(
+ "Can't call this method after buttons have been already added");
+ }
+ this.hgapScaleFactor = hgapScaleFactor;
+ }
+
+ /**
+ * Sets the vertical gap scale factor for the buttons in this button strip.
+ * This method must be called <em>before</em> adding the first command
+ * button.
+ *
+ * <p>
+ * The default vertical gap scale factor for vertically oriented strips is
+ * 0.75. The default vertical gap scale factor for horizontally oriented
+ * strips is 1.0.
+ * </p>
+ *
+ * @param vgapScaleFactor
+ * New vertical gap scale factor for the buttons in this button
+ * strip.
+ * @see #setHGapScaleFactor(double)
+ */
+ public void setVGapScaleFactor(double vgapScaleFactor) {
+ if (this.getComponentCount() > 0) {
+ throw new IllegalStateException(
+ "Can't call this method after buttons have been already added");
+ }
+ this.vgapScaleFactor = vgapScaleFactor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Container#add(java.awt.Component, java.lang.Object, int)
+ */
+ @Override
+ public void add(Component comp, Object constraints, int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Container#add(java.awt.Component, java.lang.Object)
+ */
+ @Override
+ public void add(Component comp, Object constraints) {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Container#add(java.awt.Component, int)
+ */
+ @Override
+ public Component add(Component comp, int index) {
+ if (!(comp instanceof AbstractCommandButton))
+ throw new UnsupportedOperationException();
+ this.configureCommandButton((AbstractCommandButton) comp);
+ return super.add(comp, index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Container#add(java.awt.Component)
+ */
+ @Override
+ public Component add(Component comp) {
+ if (!(comp instanceof AbstractCommandButton))
+ throw new UnsupportedOperationException();
+ try {
+ this.configureCommandButton((AbstractCommandButton) comp);
+ Component result = super.add(comp);
+ return result;
+ } finally {
+ this.fireStateChanged();
+ }
+ }
+
+ /**
+ * Configures the specified command button.
+ *
+ * @param button
+ * Command button to configure.
+ */
+ private void configureCommandButton(AbstractCommandButton button) {
+ button.setDisplayState(this.displayState);
+ button.setHGapScaleFactor(this.hgapScaleFactor);
+ button.setVGapScaleFactor(this.vgapScaleFactor);
+ button.setFlat(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Container#add(java.lang.String, java.awt.Component)
+ */
+ @Override
+ public Component add(String name, Component comp) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Sets the new UI delegate.
+ *
+ * @param ui
+ * New UI delegate.
+ */
+ public void setUI(CommandButtonStripUI ui) {
+ super.setUI(ui);
+ }
+
+ /**
+ * Resets the UI property to a value from the current look and feel.
+ *
+ * @see JComponent#updateUI
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((CommandButtonStripUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicCommandButtonStripUI.createUI(this));
+ }
+ }
+
+ /**
+ * Returns the UI object which implements the L&F for this component.
+ *
+ * @return a <code>ButtonStripUI</code> object
+ * @see #setUI(org.pushingpixels.flamingo.internal.ui.common.CommandButtonStripUI)
+ */
+ public CommandButtonStripUI getUI() {
+ return (CommandButtonStripUI) ui;
+ }
+
+ /**
+ * Returns the name of the UI class that implements the L&F for this
+ * component.
+ *
+ * @return the string "ButtonStripUI"
+ * @see JComponent#getUIClassID
+ * @see UIDefaults#getUI(javax.swing.JComponent)
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /**
+ * Returns the number of buttons in <code>this</code> strip.
+ *
+ * @return Number of buttons in <code>this</code> strip.
+ * @see #getButton(int)
+ */
+ public int getButtonCount() {
+ return this.getComponentCount();
+ }
+
+ /**
+ * Returns the specified button component of <code>this</code> strip.
+ *
+ * @param index
+ * Button index.
+ * @return The matching button.
+ * @see #getButtonCount()
+ */
+ public AbstractCommandButton getButton(int index) {
+ return (AbstractCommandButton) this.getComponent(index);
+ }
+
+ /**
+ * Checks whether the specified button is the first button in
+ * <code>this</code> strip.
+ *
+ * @param button
+ * Button to check.
+ * @return <code>true</code> if the specified button is the first button in
+ * <code>this</code> strip, <code>false</code> otherwise.
+ * @see #isLast(AbstractCommandButton)
+ */
+ public boolean isFirst(AbstractCommandButton button) {
+ return (button == this.getButton(0));
+ }
+
+ /**
+ * Checks whether the specified button is the last button in
+ * <code>this</code> strip.
+ *
+ * @param button
+ * Button to check.
+ * @return <code>true</code> if the specified button is the last button in
+ * <code>this</code> strip, <code>false</code> otherwise.
+ * @see #isFirst(AbstractCommandButton)
+ */
+ public boolean isLast(AbstractCommandButton button) {
+ return (button == this.getButton(this.getButtonCount() - 1));
+ }
+
+ /**
+ * Returns the orientation of <code>this</code> strip.
+ *
+ * @return Orientation of <code>this</code> strip.
+ */
+ public StripOrientation getOrientation() {
+ return orientation;
+ }
+
+ /**
+ * Adds the specified change listener to track changes to this command
+ * button strip.
+ *
+ * @param l
+ * Change listener to add.
+ * @see #removeChangeListener(ChangeListener)
+ */
+ public void addChangeListener(ChangeListener l) {
+ this.listenerList.add(ChangeListener.class, l);
+ }
+
+ /**
+ * Removes the specified change listener from tracking changes to this
+ * command button strip.
+ *
+ * @param l
+ * Change listener to remove.
+ * @see #addChangeListener(ChangeListener)
+ */
+ public void removeChangeListener(ChangeListener l) {
+ this.listenerList.remove(ChangeListener.class, l);
+ }
+
+ /**
+ * Notifies all registered listener that the state of this command button
+ * strip has changed.
+ */
+ protected void fireStateChanged() {
+ // Guaranteed to return a non-null array
+ Object[] listeners = this.listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ ChangeEvent event = new ChangeEvent(this);
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ChangeListener.class) {
+ ((ChangeListener) listeners[i + 1]).stateChanged(event);
+ }
+ }
+ }
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandMenuButton.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandMenuButton.java
new file mode 100644
index 0000000..6e6f528
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandMenuButton.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.UIManager;
+
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.popup.JCommandPopupMenu;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandMenuButtonUI;
+import org.pushingpixels.flamingo.internal.ui.common.CommandButtonUI;
+
+/**
+ * A command button that can be placed in {@link JCommandPopupMenu}s and in the
+ * primary / secondary panels of the ribbon application menu.
+ *
+ * @author Kirill Grouchnikov
+ * @see JCommandPopupMenu#addMenuButton(JCommandMenuButton)
+ */
+public class JCommandMenuButton extends JCommandButton {
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "CommandMenuButtonUI";
+
+ /**
+ * Creates a new command menu button.
+ *
+ * @param title
+ * Command menu button title.
+ * @param icon
+ * Command menu button icon.
+ */
+ public JCommandMenuButton(String title, ResizableIcon icon) {
+ super(title, icon);
+ setPopupOrientationKind(CommandButtonPopupOrientationKind.SIDEWARD);
+ }
+
+ /**
+ * Adds a rollover action listener that will be called when the rollover
+ * state of this button becomes active.
+ *
+ * @param l
+ * The rollover action listener to add.
+ * @see #removeRolloverActionListener(RolloverActionListener)
+ */
+ public void addRolloverActionListener(RolloverActionListener l) {
+ this.listenerList.add(RolloverActionListener.class, l);
+ }
+
+ /**
+ * Removes the specified rollover action listener.
+ *
+ * @param l
+ * The listener to remove.
+ * @see #addRolloverActionListener(RolloverActionListener)
+ */
+ public void removeRolloverActionListener(RolloverActionListener l) {
+ this.listenerList.remove(RolloverActionListener.class, l);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((CommandButtonUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicCommandMenuButtonUI.createUI(this));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.JCommandButton#canHaveBothKeyTips()
+ */
+ @Override
+ boolean canHaveBothKeyTips() {
+ return true;
+ }
+
+ /**
+ * Programmatically perform a "rollover" on the action area. This does the
+ * same thing as if the user had moved the mouse over the action area of the
+ * button.
+ */
+ public void doActionRollover() {
+ ActionEvent ae = new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
+ this.getActionModel().getActionCommand());
+ // Guaranteed to return a non-null array
+ RolloverActionListener[] listeners = this
+ .getListeners(RolloverActionListener.class);
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 1; i >= 0; i--) {
+ (listeners[i]).actionPerformed(ae);
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandToggleButton.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandToggleButton.java
new file mode 100644
index 0000000..58bff29
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandToggleButton.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import javax.swing.JComponent;
+import javax.swing.UIManager;
+
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.model.ActionToggleButtonModel;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandToggleButtonUI;
+import org.pushingpixels.flamingo.internal.ui.common.CommandButtonUI;
+
+/**
+ * Command button.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JCommandToggleButton extends AbstractCommandButton {
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "CommandToggleButtonUI";
+
+ /**
+ * Creates a new command toggle button with empty text
+ *
+ * @param icon
+ * Button icon.
+ */
+ public JCommandToggleButton(ResizableIcon icon) {
+ this(null, icon);
+ }
+
+ /**
+ * Creates a new command toggle button without an icon.
+ *
+ * @param title
+ * Button title. May contain any number of words.
+ */
+ public JCommandToggleButton(String title) {
+ this(title, null);
+ }
+
+ /**
+ * Creates a new command toggle button.
+ *
+ * @param title
+ * Button title. May contain any number of words.
+ * @param icon
+ * Button icon.
+ */
+ public JCommandToggleButton(String title, ResizableIcon icon) {
+ super(title, icon);
+ this.setActionModel(new ActionToggleButtonModel(false));
+ this.updateUI();
+ }
+
+ /**
+ * Resets the UI property to a value from the current look and feel.
+ *
+ * @see JComponent#updateUI
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((CommandButtonUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicCommandToggleButtonUI.createUI(this));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ @Override
+ public String toString() {
+ return "Command toggle button[" + this.getText() + "]";
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandToggleMenuButton.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandToggleMenuButton.java
new file mode 100644
index 0000000..2417842
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JCommandToggleMenuButton.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import javax.swing.UIManager;
+
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.popup.JCommandPopupMenu;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandToggleMenuButtonUI;
+import org.pushingpixels.flamingo.internal.ui.common.CommandButtonUI;
+
+/**
+ * A command toggle button that can be placed in {@link JCommandPopupMenu}.
+ *
+ * @author Kirill Grouchnikov
+ * @see JCommandPopupMenu#addMenuButton(JCommandToggleMenuButton)
+ */
+public class JCommandToggleMenuButton extends JCommandToggleButton {
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "CommandToggleMenuButtonUI";
+
+ /**
+ * Creates a new command toggle menu button.
+ *
+ * @param title
+ * Command toggle menu button title.
+ * @param icon
+ * Command toggle menu button icon.
+ */
+ public JCommandToggleMenuButton(String title, ResizableIcon icon) {
+ super(title, icon);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((CommandButtonUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicCommandToggleMenuButtonUI.createUI(this));
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JScrollablePanel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JScrollablePanel.java
new file mode 100644
index 0000000..05f2a5f
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/JScrollablePanel.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.pushingpixels.flamingo.internal.ui.common.BasicScrollablePanelUI;
+import org.pushingpixels.flamingo.internal.ui.common.ScrollablePanelUI;
+
+/**
+ * ScrollablePanel allows to have scrolling buttons on each side.
+ */
+public class JScrollablePanel<T extends JComponent> extends JPanel {
+ /**
+ * @see #getUIClassID
+ */
+ public static final String uiClassID = "ScrollablePanelUI";
+
+ private T view;
+
+ private ScrollType scrollType;
+
+ private boolean isScrollOnRollover;
+
+ public enum ScrollType {
+ VERTICALLY, HORIZONTALLY
+ }
+
+ public JScrollablePanel(T c, final ScrollType scrollType) {
+ super();
+
+ this.view = c;
+ this.scrollType = scrollType;
+ this.isScrollOnRollover = true;
+
+ this.updateUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#getUI()
+ */
+ @Override
+ public ScrollablePanelUI getUI() {
+ return (ScrollablePanelUI) ui;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((ScrollablePanelUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicScrollablePanelUI.createUI(this));
+ }
+ }
+
+ public void setScrollOnRollover(boolean toScrollOnRollover) {
+ boolean old = this.isScrollOnRollover;
+ this.isScrollOnRollover = toScrollOnRollover;
+
+ if (old != this.isScrollOnRollover) {
+ this.firePropertyChange("scrollOnRollover", old,
+ this.isScrollOnRollover);
+ }
+ }
+
+ public void scrollToIfNecessary(int startPosition, int span) {
+ this.getUI().scrollToIfNecessary(startPosition, span);
+ }
+
+ public T getView() {
+ return view;
+ }
+
+ public void addChangeListener(ChangeListener l) {
+ listenerList.add(ChangeListener.class, l);
+ }
+
+ public void removeChangeListener(ChangeListener l) {
+ listenerList.remove(ChangeListener.class, l);
+ }
+
+ protected void fireStateChanged() {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ ChangeEvent changeEvent = new ChangeEvent(this);
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ChangeListener.class) {
+ ((ChangeListener) listeners[i + 1]).stateChanged(changeEvent);
+ }
+ }
+ }
+
+ @Override
+ public void doLayout() {
+ super.doLayout();
+ this.fireStateChanged();
+ }
+
+ public ScrollType getScrollType() {
+ return scrollType;
+ }
+
+ public boolean isScrollOnRollover() {
+ return this.isScrollOnRollover;
+ }
+
+ public boolean isShowingScrollButtons() {
+ return this.getUI().isShowingScrollButtons();
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/KeyValuePair.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/KeyValuePair.java
new file mode 100644
index 0000000..bfdad68
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/KeyValuePair.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.util.*;
+
+/**
+ * Generic key-value pair with optional property map.
+ *
+ * @author Kirill Grouchnikov
+ * @param <S>
+ * Key class.
+ * @param <T>
+ * Value class.
+ */
+public class KeyValuePair<S, T> {
+ /**
+ * Pair key.
+ */
+ protected S key;
+
+ /**
+ * Pair value.
+ */
+ protected T value;
+
+ /**
+ * Property map.
+ */
+ protected Map<String, Object> propMap;
+
+ /**
+ * Creates a new pair.
+ *
+ * @param key
+ * Pair key.
+ * @param value
+ * Pair value.
+ */
+ public KeyValuePair(S key, T value) {
+ this.key = key;
+ this.value = value;
+ this.propMap = new HashMap<String, Object>();
+ }
+
+ /**
+ * Returns the pair value.
+ *
+ * @return Pair value.
+ */
+ public T getValue() {
+ return value;
+ }
+
+ /**
+ * Returns the pair key.
+ *
+ * @return Pair key.
+ */
+ public S getKey() {
+ return key;
+ }
+
+ /**
+ * Returns the property attached to the specified key.
+ *
+ * @param propKey
+ * Property key.
+ * @return Attached property.
+ */
+ public Object get(String propKey) {
+ return this.propMap.get(propKey);
+ }
+
+ /**
+ * Sets the property specified by the key and value.
+ *
+ * @param propKey
+ * Property key.
+ * @param propValue
+ * Property value.
+ */
+ public void set(String propKey, Object propValue) {
+ this.propMap.put(propKey, propValue);
+ }
+
+ /**
+ * Returns all attached properties.
+ *
+ * @return All attached properties.
+ */
+ public Map<String, Object> getProps() {
+ return Collections.unmodifiableMap(this.propMap);
+ }
+}
\ No newline at end of file
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/PopupActionListener.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/PopupActionListener.java
new file mode 100644
index 0000000..5ed2c6d
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/PopupActionListener.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.awt.event.ActionListener;
+
+/**
+ * Popup action listener. Is used to associate application logic with the popup
+ * area of {@link JCommandButton} component.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface PopupActionListener extends ActionListener {
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/ProgressEvent.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/ProgressEvent.java
new file mode 100644
index 0000000..1b67481
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/ProgressEvent.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.util.EventObject;
+
+/**
+ * This event is used to notify interested parties that progress has been made
+ * in the event source.
+ *
+ * @author Kirill Grouchnikov
+ * @see ProgressListener
+ */
+public class ProgressEvent extends EventObject {
+ /**
+ * Minimum value of the available progress range.
+ */
+ private int minimum;
+
+ /**
+ * Maximum value of the available progress range.
+ */
+ private int maximum;
+
+ /**
+ * Current value of the progress.
+ */
+ private int progress;
+
+ /**
+ * Creates a new progress event.
+ *
+ * @param source
+ * Event source.
+ * @param min
+ * Minimum value of the available progress range.
+ * @param max
+ * Maximum value of the available progress range.
+ * @param progress
+ * Current value of the progress.
+ */
+ public ProgressEvent(Object source, int min, int max, int progress) {
+ super(source);
+ this.maximum = max;
+ this.minimum = min;
+ this.progress = progress;
+ }
+
+ /**
+ * Returns the maximum value of the available progress range.
+ *
+ * @return The maximum value of the available progress range.
+ */
+ public int getMaximum() {
+ return this.maximum;
+ }
+
+ /**
+ * Returns the minimum value of the available progress range.
+ *
+ * @return The minimum value of the available progress range.
+ */
+ public int getMinimum() {
+ return this.minimum;
+ }
+
+ /**
+ * Returns the current value of the progress.
+ *
+ * @return The current value of the progress.
+ */
+ public int getProgress() {
+ return this.progress;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/ProgressListener.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/ProgressListener.java
new file mode 100644
index 0000000..c553bb0
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/ProgressListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.util.EventListener;
+
+/**
+ * Contract for parties interested to listen on progress events.
+ *
+ * @author Kirill Grouchnikov
+ * @see ProgressEvent
+ */
+public interface ProgressListener extends EventListener {
+ /**
+ * Fired when progress has been made in the source process.
+ *
+ * @param evt
+ * Progress event.
+ */
+ void onProgress(ProgressEvent evt);
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/RichToolTipManager.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/RichToolTipManager.java
new file mode 100644
index 0000000..e05f3bf
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/RichToolTipManager.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.List;
+
+import javax.swing.*;
+
+import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager;
+import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
+import org.pushingpixels.flamingo.internal.ui.common.JRichTooltipPanel;
+
+public class RichToolTipManager extends MouseAdapter implements
+ MouseMotionListener {
+ private Timer initialDelayTimer;
+
+ private Timer dismissTimer;
+
+ private RichTooltip richTooltip;
+
+ private JTrackableComponent insideComponent;
+
+ private MouseEvent mouseEvent;
+
+ final static RichToolTipManager sharedInstance = new RichToolTipManager();
+
+ private Popup tipWindow;
+
+ private JRichTooltipPanel tip;
+
+ private boolean tipShowing = false;
+
+ private static final String TRACKED_FOR_RICH_TOOLTIP = "flamingo.internal.trackedForRichTooltip";
+
+ public static abstract class JTrackableComponent extends JComponent {
+ public abstract RichTooltip getRichTooltip(MouseEvent mouseEvent);
+ }
+
+ RichToolTipManager() {
+ initialDelayTimer = new Timer(750, new InitialDelayTimerAction());
+ initialDelayTimer.setRepeats(false);
+ dismissTimer = new Timer(20000, new DismissTimerAction());
+ dismissTimer.setRepeats(false);
+ }
+
+ /**
+ * Specifies the initial delay value.
+ *
+ * @param milliseconds
+ * the number of milliseconds to delay (after the cursor has
+ * paused) before displaying the tooltip
+ * @see #getInitialDelay
+ */
+ public void setInitialDelay(int milliseconds) {
+ initialDelayTimer.setInitialDelay(milliseconds);
+ }
+
+ /**
+ * Returns the initial delay value.
+ *
+ * @return an integer representing the initial delay value, in milliseconds
+ * @see #setInitialDelay(int)
+ */
+ public int getInitialDelay() {
+ return initialDelayTimer.getInitialDelay();
+ }
+
+ /**
+ * Specifies the dismissal delay value.
+ *
+ * @param milliseconds
+ * the number of milliseconds to delay before taking away the
+ * tooltip
+ * @see #getDismissDelay
+ */
+ public void setDismissDelay(int milliseconds) {
+ dismissTimer.setInitialDelay(milliseconds);
+ }
+
+ /**
+ * Returns the dismissal delay value.
+ *
+ * @return an integer representing the dismissal delay value, in
+ * milliseconds
+ * @see #setDismissDelay(int)
+ */
+ public int getDismissDelay() {
+ return dismissTimer.getInitialDelay();
+ }
+
+ void showTipWindow(MouseEvent mouseEvent) {
+ if (insideComponent == null || !insideComponent.isShowing())
+ return;
+ Dimension size;
+ Point screenLocation = insideComponent.getLocationOnScreen();
+ Point location = new Point();
+ GraphicsConfiguration gc;
+ gc = insideComponent.getGraphicsConfiguration();
+ Rectangle sBounds = gc.getBounds();
+ Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
+ // Take into account screen insets, decrease viewport
+ sBounds.x += screenInsets.left;
+ sBounds.y += screenInsets.top;
+ sBounds.width -= (screenInsets.left + screenInsets.right);
+ sBounds.height -= (screenInsets.top + screenInsets.bottom);
+
+ // Just to be paranoid
+ hideTipWindow();
+
+ tip = new JRichTooltipPanel(insideComponent.getRichTooltip(mouseEvent));
+ tip
+ .applyComponentOrientation(insideComponent
+ .getComponentOrientation());
+ size = tip.getPreferredSize();
+
+ AbstractRibbonBand<?> ribbonBand = (AbstractRibbonBand<?>) SwingUtilities
+ .getAncestorOfClass(AbstractRibbonBand.class, insideComponent);
+ boolean ltr = tip.getComponentOrientation().isLeftToRight();
+ boolean isInRibbonBand = (ribbonBand != null);
+ if (isInRibbonBand) {
+ // display directly below or above ribbon band
+ location.x = ltr ? screenLocation.x : screenLocation.x
+ + insideComponent.getWidth() - size.width;
+ Point bandLocationOnScreen = ribbonBand.getLocationOnScreen();
+ location.y = bandLocationOnScreen.y + ribbonBand.getHeight() + 4;
+ if ((location.y + size.height) > (sBounds.y + sBounds.height)) {
+ location.y = bandLocationOnScreen.y - size.height;
+ }
+ } else {
+ // display directly below or above it
+ location.x = ltr ? screenLocation.x : screenLocation.x
+ + insideComponent.getWidth() - size.width;
+ location.y = screenLocation.y + insideComponent.getHeight();
+ if ((location.y + size.height) > (sBounds.y + sBounds.height)) {
+ location.y = screenLocation.y - size.height;
+ }
+ }
+
+ // Tweak the X location to not overflow the screen
+ if (location.x < sBounds.x) {
+ location.x = sBounds.x;
+ } else if (location.x - sBounds.x + size.width > sBounds.width) {
+ location.x = sBounds.x + Math.max(0, sBounds.width - size.width);
+ }
+
+ PopupFactory popupFactory = PopupFactory.getSharedInstance();
+ tipWindow = popupFactory.getPopup(insideComponent, tip, location.x,
+ location.y);
+ tipWindow.show();
+
+ dismissTimer.start();
+ tipShowing = true;
+ }
+
+ void hideTipWindow() {
+ if (tipWindow != null) {
+ tipWindow.hide();
+ tipWindow = null;
+ tipShowing = false;
+ tip = null;
+ dismissTimer.stop();
+ }
+ }
+
+ /**
+ * Returns a shared <code>ToolTipManager</code> instance.
+ *
+ * @return a shared <code>ToolTipManager</code> object
+ */
+ public static RichToolTipManager sharedInstance() {
+ return sharedInstance;
+ }
+
+ /**
+ * Registers a component for tooltip management.
+ * <p>
+ * This will register key bindings to show and hide the tooltip text only if
+ * <code>component</code> has focus bindings. This is done so that
+ * components that are not normally focus traversable, such as
+ * <code>JLabel</code>, are not made focus traversable as a result of
+ * invoking this method.
+ *
+ * @param comp
+ * a <code>JComponent</code> object to add
+ * @see JComponent#isFocusTraversable
+ */
+ public void registerComponent(JTrackableComponent comp) {
+ if (Boolean.TRUE.equals(comp
+ .getClientProperty(TRACKED_FOR_RICH_TOOLTIP)))
+ return;
+ comp.addMouseListener(this);
+ // commandButton.addMouseMotionListener(moveBeforeEnterListener);
+ comp.putClientProperty(TRACKED_FOR_RICH_TOOLTIP, Boolean.TRUE);
+ }
+
+ /**
+ * Removes a component from tooltip control.
+ *
+ * @param comp
+ * a <code>JComponent</code> object to remove
+ */
+ public void unregisterComponent(JTrackableComponent comp) {
+ comp.removeMouseListener(this);
+ comp.putClientProperty(TRACKED_FOR_RICH_TOOLTIP, null);
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent event) {
+ initiateToolTip(event);
+ }
+
+ private void initiateToolTip(MouseEvent event) {
+ JTrackableComponent component = (JTrackableComponent) event.getSource();
+ // component.removeMouseMotionListener(moveBeforeEnterListener);
+
+ Point location = event.getPoint();
+ // ensure tooltip shows only in proper place
+ if (location.x < 0 || location.x >= component.getWidth()
+ || location.y < 0 || location.y >= component.getHeight()) {
+ return;
+ }
+
+ // do not show tooltips on components in popup panels that are not
+ // in the last shown one
+ List<PopupPanelManager.PopupInfo> popups = PopupPanelManager
+ .defaultManager().getShownPath();
+ if (popups.size() > 0) {
+ JPopupPanel popupPanel = popups.get(popups.size() - 1)
+ .getPopupPanel();
+ boolean ignore = true;
+ Component c = component;
+ while (c != null) {
+ if (c == popupPanel) {
+ ignore = false;
+ break;
+ }
+ c = c.getParent();
+ }
+ if (ignore)
+ return;
+ }
+
+ if (insideComponent != null) {
+ initialDelayTimer.stop();
+ }
+ // A component in an unactive internal frame is sent two
+ // mouseEntered events, make sure we don't end up adding
+ // ourselves an extra time.
+ component.removeMouseMotionListener(this);
+ component.addMouseMotionListener(this);
+
+ insideComponent = component;
+ mouseEvent = event;
+ initialDelayTimer.start();
+ }
+
+ @Override
+ public void mouseExited(MouseEvent event) {
+ initialDelayTimer.stop();
+ if (insideComponent != null) {
+ insideComponent.removeMouseMotionListener(this);
+ }
+ insideComponent = null;
+ richTooltip = null;
+ mouseEvent = null;
+ hideTipWindow();
+ }
+
+ @Override
+ public void mousePressed(MouseEvent event) {
+ hideTipWindow();
+ initialDelayTimer.stop();
+ insideComponent = null;
+ mouseEvent = null;
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent event) {
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent event) {
+ if (tipShowing) {
+ checkForTipChange(event);
+ } else {
+ // Lazily lookup the values from within insideTimerAction
+ insideComponent = (JTrackableComponent) event.getSource();
+ mouseEvent = event;
+ richTooltip = null;
+ initialDelayTimer.restart();
+ }
+ }
+
+ private void checkForTipChange(MouseEvent event) {
+ JTrackableComponent component = (JTrackableComponent) event.getSource();
+ RichTooltip newTooltip = component.getRichTooltip(event);
+
+ // is it different?
+ boolean isDifferent = (richTooltip != newTooltip);
+ if (isDifferent) {
+ hideTipWindow();
+ if (newTooltip != null) {
+ richTooltip = newTooltip;
+ initialDelayTimer.restart();
+ }
+ }
+ }
+
+ protected class InitialDelayTimerAction implements ActionListener {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (insideComponent != null && insideComponent.isShowing()) {
+ // Lazy lookup
+ if (richTooltip == null && mouseEvent != null) {
+ richTooltip = insideComponent.getRichTooltip(mouseEvent);
+ }
+ if (richTooltip != null) {
+ boolean showRichTooltip = true;
+ // check that no visible popup is originating in this
+ // component
+ for (PopupPanelManager.PopupInfo pi : PopupPanelManager
+ .defaultManager().getShownPath()) {
+ if (pi.getPopupOriginator() == insideComponent) {
+ showRichTooltip = false;
+ break;
+ }
+ }
+
+ if (showRichTooltip) {
+ showTipWindow(mouseEvent);
+ }
+ } else {
+ insideComponent = null;
+ richTooltip = null;
+ mouseEvent = null;
+ hideTipWindow();
+ }
+ }
+ }
+ }
+
+ protected class DismissTimerAction implements ActionListener {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ hideTipWindow();
+ initialDelayTimer.stop();
+ insideComponent = null;
+ mouseEvent = null;
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/RichTooltip.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/RichTooltip.java
new file mode 100644
index 0000000..c3d132b
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/RichTooltip.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.awt.Image;
+import java.util.*;
+
+/**
+ * Rich tooltip for command buttons.
+ *
+ * <p>
+ * In its most basic form, the rich tooltip has a title and one (possible
+ * multiline) description text:
+ * </p>
+ *
+ * <pre>
+ * +--------------------------------+
+ * | Title |
+ * | Some description text |
+ * +--------------------------------+
+ * </pre>
+ *
+ * <p>
+ * The {@link #addDescriptionSection(String)} can be used to add multiple
+ * sections to the description:
+ * </p>
+ *
+ * <pre>
+ * +--------------------------------+
+ * | Title |
+ * | First multiline |
+ * | description section |
+ * | |
+ * | Second multiline |
+ * | description section |
+ * | |
+ * | Third multiline |
+ * | description section |
+ * +--------------------------------+
+ * </pre>
+ *
+ * <p>
+ * The {@link #setMainImage(Image)} can be used to place an image below the
+ * title and to the left of the description sections:
+ * </p>
+ *
+ * <pre>
+ * +--------------------------------+
+ * | Title |
+ * | ******* First multiline |
+ * | *image* description section |
+ * | ******* |
+ * | Second multiline |
+ * | description section |
+ * +--------------------------------+
+ * </pre>
+ *
+ * <p>
+ * The {@link #addFooterSection(String)} can be used to add (possibly) multiple
+ * footer sections that will be shown below a horizontal separator:
+ * </p>
+ *
+ * <pre>
+ * +--------------------------------+
+ * | Title |
+ * | First multiline |
+ * | description section |
+ * | |
+ * | Second multiline |
+ * | description section |
+ * |--------------------------------|
+ * | A multiline footer section |
+ * | placed below a separator |
+ * +--------------------------------+
+ * </pre>
+ *
+ * <p>
+ * The {@link #setFooterImage(Image)} can be used to place an image to the left
+ * of the footer sections:
+ * </p>
+ *
+ * <pre>
+ * +--------------------------------+
+ * | Title |
+ * | First multiline |
+ * | description section |
+ * | |
+ * | Second multiline |
+ * | description section |
+ * |--------------------------------|
+ * | ******* A multiline |
+ * | *image* footer section |
+ * | ******* |
+ * +--------------------------------+
+ * </pre>
+ *
+ * <p>
+ * Here is a fully fledged rich tooltip that shows all these APIs in action:
+ * </p>
+ *
+ * <pre>
+ * +--------------------------------+
+ * | Title |
+ * | ******* First multiline |
+ * | *image* description section |
+ * | ******* |
+ * | Second multiline |
+ * | description section |
+ * |--------------------------------|
+ * | ******* First multiline |
+ * | *image* footer section |
+ * | ******* |
+ * | Second multiline |
+ * | footer section |
+ * +--------------------------------+
+ * </pre>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RichTooltip {
+ /**
+ * The main title of this tooltip.
+ *
+ * @see #RichTooltip(String, String)
+ * @see #setTitle(String)
+ * @see #getTitle()
+ */
+ protected String title;
+
+ /**
+ * The main image of this tooltip. Can be <code>null</code>.
+ *
+ * @see #getMainImage()
+ * @see #setMainImage(Image)
+ */
+ protected Image mainImage;
+
+ /**
+ * The description sections of this tooltip.
+ *
+ * @see #RichTooltip(String, String)
+ * @see #addDescriptionSection(String)
+ * @see #getDescriptionSections()
+ */
+ protected List<String> descriptionSections;
+
+ /**
+ * The footer image of this tooltip. Can be <code>null</code>.
+ *
+ * @see #getFooterImage()
+ * @see #setFooterImage(Image)
+ */
+ protected Image footerImage;
+
+ /**
+ * The footer sections of this tooltip. Can be empty.
+ *
+ * @see #addFooterSection(String)
+ * @see #getFooterSections()
+ */
+ protected List<String> footerSections;
+
+ /**
+ * Creates an empty tooltip.
+ */
+ public RichTooltip() {
+ }
+
+ /**
+ * Creates a tooltip with the specified title and description section.
+ *
+ * @param title
+ * Tooltip title.
+ * @param descriptionSection
+ * Tooltip main description section.
+ */
+ public RichTooltip(String title, String descriptionSection) {
+ this.setTitle(title);
+ this.addDescriptionSection(descriptionSection);
+ }
+
+ /**
+ * Sets the title for this tooltip.
+ *
+ * @param title
+ * The new tooltip title.
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ /**
+ * Sets the main image for this tooltip.
+ *
+ * @param image
+ * The main image for this tooltip.
+ * @see #getMainImage()
+ * @see #addDescriptionSection(String)
+ */
+ public void setMainImage(Image image) {
+ this.mainImage = image;
+ }
+
+ /**
+ * Adds the specified description section to this tooltip.
+ *
+ * @param section
+ * The description section to add.
+ * @see #getDescriptionSections()
+ * @see #setMainImage(Image)
+ * @see #setTitle(String)
+ */
+ public void addDescriptionSection(String section) {
+ if (this.descriptionSections == null) {
+ this.descriptionSections = new LinkedList<String>();
+ }
+ this.descriptionSections.add(section);
+ }
+
+ /**
+ * Sets the footer image for this tooltip.
+ *
+ * @param image
+ * The footer image for this tooltip.
+ * @see #getFooterImage()
+ * @see #addFooterSection(String)
+ */
+ public void setFooterImage(Image image) {
+ this.footerImage = image;
+ }
+
+ /**
+ * Adds the specified footer section to this tooltip.
+ *
+ * @param section
+ * The footer section to add.
+ * @see #getFooterSections()
+ * @see #setFooterImage(Image)
+ */
+ public void addFooterSection(String section) {
+ if (this.footerSections == null) {
+ this.footerSections = new LinkedList<String>();
+ }
+ this.footerSections.add(section);
+ }
+
+ /**
+ * Returns the main title of this tooltip.
+ *
+ * @return The main title of this tooltip.
+ * @see #RichTooltip(String, String)
+ * @see #setTitle(String)
+ */
+ public String getTitle() {
+ return this.title;
+ }
+
+ /**
+ * Returns the main image of this tooltip. Can return <code>null</code>.
+ *
+ * @return The main image of this tooltip.
+ * @see #setMainImage(Image)
+ * @see #getDescriptionSections()
+ */
+ public Image getMainImage() {
+ return this.mainImage;
+ }
+
+ /**
+ * Returns an unmodifiable list of description sections of this tooltip.
+ * Guaranteed to return a non-<code>null</code> list.
+ *
+ * @return An unmodifiable list of description sections of this tooltip.
+ * @see #RichTooltip(String, String)
+ * @see #addDescriptionSection(String)
+ * @see #getTitle()
+ * @see #getMainImage()
+ */
+ @SuppressWarnings("unchecked")
+ public List<String> getDescriptionSections() {
+ if (this.descriptionSections == null)
+ return Collections.EMPTY_LIST;
+ return Collections.unmodifiableList(this.descriptionSections);
+ }
+
+ /**
+ * Returns the footer image of this tooltip. Can return <code>null</code>.
+ *
+ * @return The footer image of this tooltip.
+ * @see #setFooterImage(Image)
+ * @see #getFooterSections()
+ */
+ public Image getFooterImage() {
+ return this.footerImage;
+ }
+
+ /**
+ * Returns an unmodifiable list of footer sections of this tooltip.
+ * Guaranteed to return a non-<code>null</code> list.
+ *
+ * @return An unmodifiable list of footer sections of this tooltip.
+ * @see #addFooterSection(String)
+ * @see #getFooterImage()
+ */
+ @SuppressWarnings("unchecked")
+ public List<String> getFooterSections() {
+ if (this.footerSections == null)
+ return Collections.EMPTY_LIST;
+ return Collections.unmodifiableList(this.footerSections);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/RolloverActionListener.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/RolloverActionListener.java
new file mode 100644
index 0000000..f751189
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/RolloverActionListener.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+import java.awt.event.ActionListener;
+
+/**
+ * Rollover action listener. Is used to associate application logic with
+ * rollover of {@link JCommandMenuButton} component.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface RolloverActionListener extends ActionListener {
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/StringValuePair.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/StringValuePair.java
new file mode 100644
index 0000000..7d7864e
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/StringValuePair.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common;
+
+/**
+ * Key-value pair with a {@link String} key.
+ *
+ * @author Kirill Grouchnikov
+ * @param <T>
+ * Value class.
+ */
+public class StringValuePair<T> extends KeyValuePair<String, T> {
+ /**
+ * Creates a new pair.
+ *
+ * @param key
+ * Pair key.
+ * @param value
+ * Pair value.
+ */
+ public StringValuePair(String key, T value) {
+ super(key, value);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/DecoratedResizableIcon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/DecoratedResizableIcon.java
new file mode 100644
index 0000000..d1fb396
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/DecoratedResizableIcon.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.icon;
+
+import java.awt.*;
+import java.util.ArrayList;
+
+import org.pushingpixels.flamingo.api.common.AsynchronousLoadListener;
+import org.pushingpixels.flamingo.api.common.AsynchronousLoading;
+
+/**
+ * Implementation of {@link ResizableIcon} that adds decorations to a main icon.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DecoratedResizableIcon implements ResizableIcon,
+ AsynchronousLoading {
+ /**
+ * The main delegate icon.
+ */
+ protected ResizableIcon delegate;
+
+ /**
+ * List of icon decorators.
+ */
+ protected java.util.List<IconDecorator> decorators;
+
+ /**
+ * Icon decorator interface.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static interface IconDecorator {
+ /**
+ * Paints the icon decoration.
+ *
+ * @param c
+ * Component.
+ * @param g
+ * Graphics context.
+ * @param mainIconX
+ * X position of main icon painting.
+ * @param mainIconY
+ * Y position of main icon painting.
+ * @param mainIconWidth
+ * Width of main icon.
+ * @param mainIconHeight
+ * Height of main icon.
+ */
+ public void paintIconDecoration(Component c, Graphics g, int mainIconX,
+ int mainIconY, int mainIconWidth, int mainIconHeight);
+ }
+
+ /**
+ * Creates a new decorated icon.
+ *
+ * @param delegate
+ * The main icon.
+ * @param decorators
+ * Icon decorators.
+ */
+ public DecoratedResizableIcon(ResizableIcon delegate,
+ IconDecorator... decorators) {
+ this.delegate = delegate;
+ this.decorators = new ArrayList<IconDecorator>();
+ if (decorators != null) {
+ for (IconDecorator decorator : decorators) {
+ this.decorators.add(decorator);
+ }
+ }
+ }
+
+ /**
+ * Creates a new decorated icon with no decorators. Decorators can be added
+ * later with {@link #addIconDecorator(IconDecorator)}.
+ *
+ * @param delegate
+ * Main icon.
+ */
+ public DecoratedResizableIcon(ResizableIcon delegate) {
+ this(delegate, (IconDecorator) null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.delegate.getIconHeight();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.delegate.getIconWidth();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ this.delegate.paintIcon(c, g, x, y);
+ for (IconDecorator decorator : this.decorators) {
+ decorator.paintIconDecoration(c, g, x, y, this.delegate
+ .getIconWidth(), this.delegate.getIconHeight());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.icon.ResizableIcon#setDimension(java.awt.Dimension
+ * )
+ */
+ @Override
+ public void setDimension(Dimension newDimension) {
+ this.delegate.setDimension(newDimension);
+ }
+
+ /**
+ * Adds the specified decorator to the end of the decorator sequence. If the
+ * specified decorator already exists, it is not moved to the end of the
+ * sequence.
+ *
+ * @param decorator
+ * Decorator to add.
+ */
+ public void addIconDecorator(IconDecorator decorator) {
+ if (this.decorators.contains(decorator))
+ return;
+ this.decorators.add(decorator);
+ }
+
+ /**
+ * Removes the specified decorator.
+ *
+ * @param decorator
+ * Decorator to remove.
+ */
+ public void removeIconDecorator(IconDecorator decorator) {
+ this.decorators.remove(decorator);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.AsynchronousLoading#addAsynchronousLoadListener
+ * (org.jvnet.flamingo.common.AsynchronousLoadListener)
+ */
+ @Override
+ public void addAsynchronousLoadListener(AsynchronousLoadListener l) {
+ if (this.delegate instanceof AsynchronousLoading) {
+ ((AsynchronousLoading) this.delegate)
+ .addAsynchronousLoadListener(l);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.AsynchronousLoading#removeAsynchronousLoadListener
+ * (org.jvnet.flamingo.common.AsynchronousLoadListener)
+ */
+ @Override
+ public void removeAsynchronousLoadListener(AsynchronousLoadListener l) {
+ if (this.delegate instanceof AsynchronousLoading) {
+ ((AsynchronousLoading) this.delegate)
+ .removeAsynchronousLoadListener(l);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.AsynchronousLoading#isLoading()
+ */
+ @Override
+ public synchronized boolean isLoading() {
+ if (this.delegate instanceof AsynchronousLoading) {
+ if (((AsynchronousLoading) this.delegate).isLoading())
+ return true;
+ }
+ for (IconDecorator decorator : this.decorators) {
+ if (decorator instanceof AsynchronousLoading) {
+ if (((AsynchronousLoading) decorator).isLoading())
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/EmptyResizableIcon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/EmptyResizableIcon.java
new file mode 100644
index 0000000..972409d
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/EmptyResizableIcon.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.icon;
+
+import java.awt.*;
+
+/**
+ * Implementation of {@link ResizableIcon} that paints nothing.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class EmptyResizableIcon implements ResizableIcon {
+ /**
+ * The current icon width.
+ */
+ protected int width;
+
+ /**
+ * The current icon height.
+ */
+ protected int height;
+
+ /**
+ * Creates a new empty resizable icon of the specified size.
+ *
+ * @param initialDim
+ * Initial dimension of the icon.
+ */
+ public EmptyResizableIcon(Dimension initialDim) {
+ this.width = initialDim.width;
+ this.height = initialDim.height;
+ }
+
+ /**
+ * Creates a new empty resizable icon of the specified size.
+ *
+ * @param initialDim
+ * Initial dimension of the icon.
+ */
+ public EmptyResizableIcon(int initialDim) {
+ this(new Dimension(initialDim, initialDim));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.icon.ResizableIcon#setDimension(java.awt.Dimension
+ * )
+ */
+ @Override
+ public void setDimension(Dimension newDimension) {
+ this.width = newDimension.width;
+ this.height = newDimension.height;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.height;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.width;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/FilteredResizableIcon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/FilteredResizableIcon.java
new file mode 100644
index 0000000..7df2050
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/FilteredResizableIcon.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.icon;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.pushingpixels.flamingo.api.common.AsynchronousLoading;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+/**
+ * Implementation of {@link ResizableIcon} that allows applying a
+ * {@link BufferedImageOp} on another icon.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class FilteredResizableIcon implements ResizableIcon {
+ /**
+ * Image cache to speed up rendering.
+ */
+ protected Map<String, BufferedImage> cachedImages;
+
+ /**
+ * The main (pre-filtered) icon.
+ */
+ protected ResizableIcon delegate;
+
+ /**
+ * Filter operation.
+ */
+ protected BufferedImageOp operation;
+
+ /**
+ * Creates a new filtered icon.
+ *
+ * @param delegate
+ * The main (pre-filtered) icon.
+ * @param operation
+ * Filter operation.
+ */
+ public FilteredResizableIcon(ResizableIcon delegate,
+ BufferedImageOp operation) {
+ super();
+ this.delegate = delegate;
+ this.operation = operation;
+ this.cachedImages = new LinkedHashMap<String, BufferedImage>() {
+ @Override
+ protected boolean removeEldestEntry(
+ java.util.Map.Entry<String, BufferedImage> eldest) {
+ return size() > 5;
+ }
+ };
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return delegate.getIconHeight();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return delegate.getIconWidth();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.icon.ResizableIcon#setDimension(java.awt.Dimension
+ * )
+ */
+ @Override
+ public void setDimension(Dimension newDimension) {
+ delegate.setDimension(newDimension);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ // check cache
+ String key = this.getIconWidth() + ":" + this.getIconHeight();
+ if (!this.cachedImages.containsKey(key)) {
+ // check if loading
+ if (this.delegate instanceof AsynchronousLoading) {
+ AsynchronousLoading asyncDelegate = (AsynchronousLoading) this.delegate;
+ // if the delegate is still loading - do nothing
+ if (asyncDelegate.isLoading())
+ return;
+ }
+ BufferedImage offscreen = FlamingoUtilities.getBlankImage(this
+ .getIconWidth(), this.getIconHeight());
+ Graphics2D g2d = offscreen.createGraphics();
+ this.delegate.paintIcon(c, g2d, 0, 0);
+ g2d.dispose();
+ BufferedImage filtered = this.operation.filter(offscreen, null);
+ this.cachedImages.put(key, filtered);
+ }
+ g.drawImage(this.cachedImages.get(key), x, y, null);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/IcoWrapperIcon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/IcoWrapperIcon.java
new file mode 100644
index 0000000..bf5310a
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/IcoWrapperIcon.java
@@ -0,0 +1,684 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.icon;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+
+import javax.imageio.ImageIO;
+import javax.swing.Icon;
+import javax.swing.SwingWorker;
+import javax.swing.event.EventListenerList;
+
+import org.pushingpixels.flamingo.api.common.AsynchronousLoadListener;
+import org.pushingpixels.flamingo.api.common.AsynchronousLoading;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+/**
+ * Helper class to load image planes from .ICO files.
+ *
+ * @author Kirill Grouchnikov
+ */
+abstract class IcoWrapperIcon implements Icon, AsynchronousLoading {
+ /**
+ * The input stream of the original image.
+ */
+ protected final InputStream icoInputStream;
+
+ /**
+ * Image planes of the original ICO image.
+ */
+ protected Map<Integer, BufferedImage> icoPlaneMap;
+
+ /**
+ * Contains all precomputed images.
+ */
+ protected Map<String, BufferedImage> cachedImages;
+
+ /**
+ * The width of the current image.
+ */
+ protected int width;
+
+ /**
+ * The height of the current image.
+ */
+ protected int height;
+
+ /**
+ * The listeners.
+ */
+ protected EventListenerList listenerList = new EventListenerList();
+
+ /**
+ * Create a new ICO icon.
+ *
+ * @param inputStream
+ * The input stream to read the ICO bits from.
+ * @param w
+ * The width of the icon.
+ * @param h
+ * The height of the icon.
+ */
+ public IcoWrapperIcon(InputStream inputStream, int w, int h) {
+ this.icoInputStream = inputStream;
+ this.width = w;
+ this.height = h;
+ this.listenerList = new EventListenerList();
+ this.cachedImages = new LinkedHashMap<String, BufferedImage>() {
+ @Override
+ protected boolean removeEldestEntry(
+ Map.Entry<String, BufferedImage> eldest) {
+ return size() > 5;
+ }
+ };
+ this.renderImage(this.width, this.height);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.AsynchronousLoading#addAsynchronousLoadListener
+ * (org.jvnet.flamingo.common.AsynchronousLoadListener)
+ */
+ @Override
+ public void addAsynchronousLoadListener(AsynchronousLoadListener l) {
+ this.listenerList.add(AsynchronousLoadListener.class, l);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.AsynchronousLoading#removeAsynchronousLoadListener
+ * (org.jvnet.flamingo.common.AsynchronousLoadListener)
+ */
+ @Override
+ public void removeAsynchronousLoadListener(AsynchronousLoadListener l) {
+ this.listenerList.remove(AsynchronousLoadListener.class, l);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return width;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return height;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ BufferedImage image = this.cachedImages.get(this.getIconWidth() + ":"
+ + this.getIconHeight());
+ if (image != null) {
+ int dx = (this.width - image.getWidth()) / 2;
+ int dy = (this.height - image.getHeight()) / 2;
+ g.drawImage(image, x + dx, y + dy, null);
+ }
+ }
+
+ /**
+ * Sets the preferred size for <code>this</code> icon. The rendering is
+ * scheduled automatically.
+ *
+ * @param dim
+ * Preferred size.
+ */
+ public synchronized void setPreferredSize(Dimension dim) {
+ if ((dim.width == this.width) && (dim.height == this.height))
+ return;
+ this.width = dim.width;
+ this.height = dim.height;
+
+ this.renderImage(this.width, this.height);
+ }
+
+ /**
+ * Renders the image.
+ *
+ * @param renderWidth
+ * Requested rendering width.
+ * @param renderHeight
+ * Requested rendering height.
+ */
+ protected synchronized void renderImage(final int renderWidth,
+ final int renderHeight) {
+ String key = renderWidth + ":" + renderHeight;
+ if (this.cachedImages.containsKey(key)) {
+ fireAsyncCompleted(true);
+ return;
+ }
+
+ SwingWorker<BufferedImage, Void> worker = new SwingWorker<BufferedImage, Void>() {
+ @Override
+ protected BufferedImage doInBackground() throws Exception {
+ synchronized (icoInputStream) {
+ if (icoPlaneMap == null) {
+ // read original ICO image
+ Ico ico = new Ico(icoInputStream);
+ icoPlaneMap = new TreeMap<Integer, BufferedImage>();
+
+ Set<Integer> widths = new HashSet<Integer>();
+ for (int i = 0; i < ico.getNumImages(); i++) {
+ BufferedImage icoPlane = ico.getImage(i);
+ widths.add(icoPlane.getWidth());
+ }
+
+ for (int width : widths) {
+ // find the ico plane with the largest color count
+ BufferedImage bestMatch = null;
+ int bestColorCount = -1;
+ for (int i = 0; i < ico.getNumImages(); i++) {
+ BufferedImage icoPlane = ico.getImage(i);
+ if (icoPlane.getWidth() != width)
+ continue;
+ int icoPlaneColorCount = ico.getNumColors(i);
+ if (icoPlaneColorCount == 0) {
+ bestMatch = icoPlane;
+ bestColorCount = 0;
+ } else {
+ if (bestColorCount == 0)
+ continue;
+ if (icoPlaneColorCount > bestColorCount) {
+ bestMatch = icoPlane;
+ bestColorCount = icoPlaneColorCount;
+ }
+ }
+ }
+ icoPlaneMap.put(width, bestMatch);
+ }
+ }
+ }
+
+ // find the best match
+ int indexOfBestMatch = -1;
+ int bestMatchWidth = -1;
+ for (Map.Entry<Integer, BufferedImage> icoPlaneMapEntry : icoPlaneMap
+ .entrySet()) {
+ BufferedImage icoPlane = icoPlaneMapEntry.getValue();
+ int icoPlaneWidth = icoPlane.getWidth();
+ if (icoPlaneWidth > renderWidth) {
+ // check if the ICO plane width is closer
+ // to the required width than the best match so far
+ if (bestMatchWidth < 0) {
+ bestMatchWidth = icoPlaneWidth;
+ } else {
+ if (bestMatchWidth > icoPlaneWidth) {
+ bestMatchWidth = icoPlaneWidth;
+ }
+ }
+ }
+ }
+
+ // if at this point the best match is not found, it
+ // means that the requested width is bigger than
+ // any of the ICO planes. Take the biggest ICO plane
+ // available
+ if (indexOfBestMatch < 0) {
+ for (Map.Entry<Integer, BufferedImage> icoPlaneMapEntry : icoPlaneMap
+ .entrySet()) {
+ BufferedImage icoPlane = icoPlaneMapEntry.getValue();
+ int icoPlaneWidth = icoPlane.getWidth();
+ if (icoPlaneWidth > bestMatchWidth) {
+ bestMatchWidth = icoPlaneWidth;
+ }
+ }
+ }
+
+ BufferedImage bestMatchPlane = icoPlaneMap.get(bestMatchWidth);
+ BufferedImage result = bestMatchPlane;
+ float scaleX = (float) bestMatchPlane.getWidth()
+ / (float) renderWidth;
+ float scaleY = (float) bestMatchPlane.getHeight()
+ / (float) renderHeight;
+
+ float scale = Math.max(scaleX, scaleY);
+ if (scale > 1.0f) {
+ int finalWidth = (int) (bestMatchPlane.getWidth() / scale);
+ result = FlamingoUtilities.createThumbnail(bestMatchPlane,
+ finalWidth);
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void done() {
+ try {
+ BufferedImage bufferedImage = get();
+ cachedImages.put(renderWidth + ":" + renderHeight,
+ bufferedImage);
+ fireAsyncCompleted(true);
+ } catch (Exception exc) {
+ fireAsyncCompleted(false);
+ }
+ }
+ };
+ worker.execute();
+ }
+
+ /**
+ * Fires the asynchronous load event.
+ *
+ * @param event
+ * Event object.
+ */
+ protected void fireAsyncCompleted(Boolean event) {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == AsynchronousLoadListener.class) {
+ ((AsynchronousLoadListener) listeners[i + 1]).completed(event);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.AsynchronousLoading#isLoading()
+ */
+ @Override
+ public synchronized boolean isLoading() {
+ BufferedImage image = this.cachedImages.get(this.getIconWidth() + ":"
+ + this.getIconHeight());
+ return (image == null);
+ }
+}
+
+/**
+ * The code below is copyrighted by Jeff Friesen and first appeared on <a
+ * href="<a href="http://www.informit.com">InformIT.com</a> at <a
+ * href="http://www.informit.com/articles/article.aspx?p=1186882">this
+ * location</a>. This code is licensed under BSD license and can be reused as
+ * long as the credit is given to Jeff Friesen, InformIT.com and the original
+ * URL above.
+ *
+ * @author Jeff Friesen
+ */
+class Ico {
+ private final static int FDE_OFFSET = 6; // first directory entry offset
+ private final static int DE_LENGTH = 16; // directory entry length
+
+ private final static int BMIH_LENGTH = 40; // BITMAPINFOHEADER length
+
+ private byte[] icoimage = new byte[0]; // new byte [0] facilitates read()
+
+ private int numImages;
+
+ private BufferedImage[] bi;
+
+ private int[] colorCount;
+
+ public Ico(File file) throws BadIcoResException, IOException {
+ this(file.getAbsolutePath());
+ }
+
+ public Ico(InputStream is) throws BadIcoResException, IOException {
+ try {
+ read(is);
+ parseICOImage();
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ public Ico(String filename) throws BadIcoResException, IOException {
+ this(new FileInputStream(filename));
+ }
+
+ public Ico(URL url) throws BadIcoResException, IOException {
+ this(url.openStream());
+ }
+
+ public BufferedImage getImage(int index) {
+ if (index < 0 || index >= numImages)
+ throw new IllegalArgumentException("index out of range");
+
+ return bi[index];
+ }
+
+ public int getNumColors(int index) {
+ if (index < 0 || index >= numImages)
+ throw new IllegalArgumentException("index out of range");
+
+ return colorCount[index];
+ }
+
+ public int getNumImages() {
+ return numImages;
+ }
+
+ private int calcScanlineBytes(int width, int bitCount) {
+ // Calculate minimum number of double-words required to store width
+ // pixels where each pixel occupies bitCount bits. XOR and AND bitmaps
+ // are stored such that each scanline is aligned on a double-word
+ // boundary.
+
+ return (((width * bitCount) + 31) / 32) * 4;
+ }
+
+ private void parseICOImage() throws BadIcoResException, IOException {
+ // Check resource type field.
+
+ if (icoimage[2] != 1 || icoimage[3] != 0)
+ throw new BadIcoResException("Not an ICO resource");
+
+ numImages = ubyte(icoimage[5]);
+ numImages <<= 8;
+ numImages |= icoimage[4];
+
+ bi = new BufferedImage[numImages];
+
+ colorCount = new int[numImages];
+
+ for (int i = 0; i < numImages; i++) {
+ int width = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH]);
+
+ int height = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 1]);
+
+ colorCount[i] = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 2]);
+
+ int bytesInRes = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 11]);
+ bytesInRes <<= 8;
+ bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 10]);
+ bytesInRes <<= 8;
+ bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 9]);
+ bytesInRes <<= 8;
+ bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 8]);
+
+ int imageOffset = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 15]);
+ imageOffset <<= 8;
+ imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 14]);
+ imageOffset <<= 8;
+ imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 13]);
+ imageOffset <<= 8;
+ imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 12]);
+
+ if (icoimage[imageOffset] == 40 && icoimage[imageOffset + 1] == 0
+ && icoimage[imageOffset + 2] == 0
+ && icoimage[imageOffset + 3] == 0) {
+ // BITMAPINFOHEADER detected
+
+ int _width = ubyte(icoimage[imageOffset + 7]);
+ _width <<= 8;
+ _width |= ubyte(icoimage[imageOffset + 6]);
+ _width <<= 8;
+ _width |= ubyte(icoimage[imageOffset + 5]);
+ _width <<= 8;
+ _width |= ubyte(icoimage[imageOffset + 4]);
+
+ // If width is 0 (for 256 pixels or higher), _width contains
+ // actual width.
+
+ if (width == 0)
+ width = _width;
+
+ int _height = ubyte(icoimage[imageOffset + 11]);
+ _height <<= 8;
+ _height |= ubyte(icoimage[imageOffset + 10]);
+ _height <<= 8;
+ _height |= ubyte(icoimage[imageOffset + 9]);
+ _height <<= 8;
+ _height |= ubyte(icoimage[imageOffset + 8]);
+
+ // If height is 0 (for 256 pixels or higher), _height contains
+ // actual height times 2.
+
+ if (height == 0)
+ height = _height >> 1; // Divide by 2.
+
+ int planes = ubyte(icoimage[imageOffset + 13]);
+ planes <<= 8;
+ planes |= ubyte(icoimage[imageOffset + 12]);
+
+ int bitCount = ubyte(icoimage[imageOffset + 15]);
+ bitCount <<= 8;
+ bitCount |= ubyte(icoimage[imageOffset + 14]);
+
+ // If colorCount [i] is 0, the number of colors is determined
+ // from the planes and bitCount values. For example, the number
+ // of colors is 256 when planes is 1 and bitCount is 8. Leave
+ // colorCount [i] set to 0 when planes is 1 and bitCount is 32.
+
+ if (colorCount[i] == 0) {
+ if (planes == 1) {
+ if (bitCount == 1)
+ colorCount[i] = 2;
+ else if (bitCount == 4)
+ colorCount[i] = 16;
+ else if (bitCount == 8)
+ colorCount[i] = 256;
+ else if (bitCount != 32)
+ colorCount[i] = (int) Math.pow(2, bitCount);
+ } else
+ colorCount[i] = (int) Math.pow(2, bitCount * planes);
+ }
+
+ bi[i] = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+
+ // Parse image to image buffer.
+
+ int colorTableOffset = imageOffset + BMIH_LENGTH;
+
+ if (colorCount[i] == 2) {
+ int xorImageOffset = colorTableOffset + 2 * 4;
+
+ int scanlineBytes = calcScanlineBytes(width, 1);
+ int andImageOffset = xorImageOffset + scanlineBytes
+ * height;
+
+ int[] masks = { 128, 64, 32, 16, 8, 4, 2, 1 };
+
+ for (int row = 0; row < height; row++)
+ for (int col = 0; col < width; col++) {
+ int index;
+
+ if ((ubyte(icoimage[xorImageOffset + row
+ * scanlineBytes + col / 8]) & masks[col % 8]) != 0)
+ index = 1;
+ else
+ index = 0;
+
+ int rgb = 0;
+ rgb |= (ubyte(icoimage[colorTableOffset + index * 4
+ + 2]));
+ rgb <<= 8;
+ rgb |= (ubyte(icoimage[colorTableOffset + index * 4
+ + 1]));
+ rgb <<= 8;
+ rgb |= (ubyte(icoimage[colorTableOffset + index * 4]));
+
+ if ((ubyte(icoimage[andImageOffset + row
+ * scanlineBytes + col / 8]) & masks[col % 8]) != 0)
+ bi[i].setRGB(col, height - 1 - row, rgb);
+ else
+ bi[i].setRGB(col, height - 1 - row,
+ 0xff000000 | rgb);
+ }
+ } else if (colorCount[i] == 16) {
+ int xorImageOffset = colorTableOffset + 16 * 4;
+
+ int scanlineBytes = calcScanlineBytes(width, 4);
+ int andImageOffset = xorImageOffset + scanlineBytes
+ * height;
+
+ int[] masks = { 128, 64, 32, 16, 8, 4, 2, 1 };
+
+ for (int row = 0; row < height; row++)
+ for (int col = 0; col < width; col++) {
+ int index;
+ if ((col & 1) == 0) // even
+ {
+ index = ubyte(icoimage[xorImageOffset + row
+ * scanlineBytes + col / 2]);
+ index >>= 4;
+ } else {
+ index = ubyte(icoimage[xorImageOffset + row
+ * scanlineBytes + col / 2]) & 15;
+ }
+
+ int rgb = 0;
+ rgb |= (ubyte(icoimage[colorTableOffset + index * 4
+ + 2]));
+ rgb <<= 8;
+ rgb |= (ubyte(icoimage[colorTableOffset + index * 4
+ + 1]));
+ rgb <<= 8;
+ rgb |= (ubyte(icoimage[colorTableOffset + index * 4]));
+
+ if ((ubyte(icoimage[andImageOffset + row
+ * calcScanlineBytes(width, 1) + col / 8]) & masks[col % 8]) != 0)
+ bi[i].setRGB(col, height - 1 - row, rgb);
+ else
+ bi[i].setRGB(col, height - 1 - row,
+ 0xff000000 | rgb);
+ }
+ } else if (colorCount[i] == 256) {
+ int xorImageOffset = colorTableOffset + 256 * 4;
+
+ int scanlineBytes = calcScanlineBytes(width, 8);
+ int andImageOffset = xorImageOffset + scanlineBytes
+ * height;
+
+ int[] masks = { 128, 64, 32, 16, 8, 4, 2, 1 };
+
+ for (int row = 0; row < height; row++)
+ for (int col = 0; col < width; col++) {
+ int index;
+ index = ubyte(icoimage[xorImageOffset + row
+ * scanlineBytes + col]);
+
+ int rgb = 0;
+ rgb |= (ubyte(icoimage[colorTableOffset + index * 4
+ + 2]));
+ rgb <<= 8;
+ rgb |= (ubyte(icoimage[colorTableOffset + index * 4
+ + 1]));
+ rgb <<= 8;
+ rgb |= (ubyte(icoimage[colorTableOffset + index * 4]));
+
+ if ((ubyte(icoimage[andImageOffset + row
+ * calcScanlineBytes(width, 1) + col / 8]) & masks[col % 8]) != 0)
+ bi[i].setRGB(col, height - 1 - row, rgb);
+ else
+ bi[i].setRGB(col, height - 1 - row,
+ 0xff000000 | rgb);
+ }
+ } else if (colorCount[i] == 0) {
+ int scanlineBytes = calcScanlineBytes(width, 32);
+
+ for (int row = 0; row < height; row++)
+ for (int col = 0; col < width; col++) {
+ int rgb = ubyte(icoimage[colorTableOffset + row
+ * scanlineBytes + col * 4 + 3]);
+ rgb <<= 8;
+ rgb |= ubyte(icoimage[colorTableOffset + row
+ * scanlineBytes + col * 4 + 2]);
+ rgb <<= 8;
+ rgb |= ubyte(icoimage[colorTableOffset + row
+ * scanlineBytes + col * 4 + 1]);
+ rgb <<= 8;
+ rgb |= ubyte(icoimage[colorTableOffset + row
+ * scanlineBytes + col * 4]);
+
+ bi[i].setRGB(col, height - 1 - row, rgb);
+ }
+ }
+ } else if (ubyte(icoimage[imageOffset]) == 0x89
+ && icoimage[imageOffset + 1] == 0x50
+ && icoimage[imageOffset + 2] == 0x4e
+ && icoimage[imageOffset + 3] == 0x47
+ && icoimage[imageOffset + 4] == 0x0d
+ && icoimage[imageOffset + 5] == 0x0a
+ && icoimage[imageOffset + 6] == 0x1a
+ && icoimage[imageOffset + 7] == 0x0a) {
+ // PNG detected
+
+ ByteArrayInputStream bais;
+ bais = new ByteArrayInputStream(icoimage, imageOffset,
+ bytesInRes);
+ bi[i] = ImageIO.read(bais);
+ } else
+ throw new BadIcoResException("BITMAPINFOHEADER or PNG "
+ + "expected");
+ }
+
+ icoimage = null; // This array can now be garbage collected.
+ }
+
+ private void read(InputStream is) throws IOException {
+ int bytesToRead;
+ while ((bytesToRead = is.available()) != 0) {
+ byte[] icoimage2 = new byte[icoimage.length + bytesToRead];
+ System.arraycopy(icoimage, 0, icoimage2, 0, icoimage.length);
+ is.read(icoimage2, icoimage.length, bytesToRead);
+ icoimage = icoimage2;
+ }
+ }
+
+ private int ubyte(byte b) {
+ return (b < 0) ? 256 + b : b; // Convert byte to unsigned byte.
+ }
+}
+
+class BadIcoResException extends Exception {
+ public BadIcoResException(String message) {
+ super(message);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/IcoWrapperResizableIcon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/IcoWrapperResizableIcon.java
new file mode 100644
index 0000000..d174085
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/IcoWrapperResizableIcon.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.icon;
+
+import java.awt.Dimension;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * Implementation of {@link ResizableIcon} interface that wraps ICO files.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class IcoWrapperResizableIcon extends IcoWrapperIcon implements
+ ResizableIcon {
+ /**
+ * Returns the icon for the specified URL.
+ *
+ * @param location
+ * Icon URL.
+ * @param initialDim
+ * Initial dimension of the icon.
+ * @return Icon instance.
+ */
+ public static IcoWrapperResizableIcon getIcon(URL location,
+ final Dimension initialDim) {
+ try {
+ return new IcoWrapperResizableIcon(location.openStream(),
+ initialDim);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Returns the icon for the specified input stream.
+ *
+ * @param inputStream
+ * Icon input stream.
+ * @param initialDim
+ * Initial dimension of the icon.
+ * @return Icon instance.
+ */
+ public static IcoWrapperResizableIcon getIcon(InputStream inputStream,
+ final Dimension initialDim) {
+ return new IcoWrapperResizableIcon(inputStream, initialDim);
+ }
+
+ /**
+ * Creates a new ICO-based resizable icon.
+ *
+ * @param inputStream
+ * Input stream with the ICO content.
+ * @param initialDim
+ * Initial dimension of the icon.
+ */
+ private IcoWrapperResizableIcon(InputStream inputStream,
+ Dimension initialDim) {
+ super(inputStream, initialDim.width, initialDim.height);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.icon.ResizableIcon#setDimension(java.awt.Dimension
+ * )
+ */
+ @Override
+ public void setDimension(Dimension dim) {
+ this.setPreferredSize(dim);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/IconDeckResizableIcon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/IconDeckResizableIcon.java
new file mode 100644
index 0000000..4700b5e
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/IconDeckResizableIcon.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.icon;
+
+import java.awt.*;
+import java.util.Map;
+
+import org.pushingpixels.flamingo.api.common.AsynchronousLoadListener;
+import org.pushingpixels.flamingo.api.common.AsynchronousLoading;
+
+/**
+ * Implementation of the {@link ResizableIcon} that allows switching the icon
+ * painting at runtime. This class can be used as a delegate in the
+ * {@link DecoratedResizableIcon} where the "base" icon is changed at runtime
+ * without the need to recompute all the decorators.
+ *
+ * @param <T>
+ * enumeration key into the deck
+ * @author Kenneth Flynn flynnk at darkcornersoftware.com.
+ */
+public class IconDeckResizableIcon<T> implements ResizableIcon,
+ AsynchronousLoading {
+ /**
+ * Currently shown icon.
+ */
+ private ResizableIcon currentIcon;
+
+ /**
+ * The icon deck.
+ */
+ private final Map<T, ? extends ResizableIcon> iconDeck;
+
+ /**
+ * Creates the icon deck.
+ *
+ * @param iconDeck
+ * Icon deck.
+ */
+ public IconDeckResizableIcon(Map<T, ? extends ResizableIcon> iconDeck) {
+ if (iconDeck.isEmpty())
+ throw new IllegalArgumentException(
+ "Icon deck is empty; must have at least one icon");
+ this.iconDeck = iconDeck;
+ this.currentIcon = iconDeck.values().iterator().next();
+ }
+
+ /**
+ * Sets the currently shown icon.
+ *
+ * @param key
+ * Icon key.
+ */
+ public void setIcon(T key) {
+ this.currentIcon = iconDeck.get(key);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.icon.ResizableIcon#setDimension(java.awt.Dimension
+ * )
+ */
+ @Override
+ public void setDimension(Dimension dim) {
+ for (ResizableIcon icon : iconDeck.values()) {
+ int currH = icon.getIconHeight();
+ int currW = icon.getIconWidth();
+ if ((currH != dim.height) || (currW != dim.width))
+ icon.setDimension(dim);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return currentIcon.getIconHeight();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return currentIcon.getIconWidth();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ currentIcon.paintIcon(c, g, x, y);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.AsynchronousLoading#addAsynchronousLoadListener
+ * (org.jvnet.flamingo.common.AsynchronousLoadListener)
+ */
+ @Override
+ public void addAsynchronousLoadListener(AsynchronousLoadListener l) {
+ for (ResizableIcon icon : iconDeck.values()) {
+ if (icon instanceof AsynchronousLoading)
+ ((AsynchronousLoading) icon).addAsynchronousLoadListener(l);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.AsynchronousLoading#isLoading()
+ */
+ @Override
+ public boolean isLoading() {
+ for (ResizableIcon icon : iconDeck.values()) {
+ if (icon instanceof AsynchronousLoading) {
+ if (((AsynchronousLoading) icon).isLoading())
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.AsynchronousLoading#removeAsynchronousLoadListener
+ * (org.jvnet.flamingo.common.AsynchronousLoadListener)
+ */
+ @Override
+ public void removeAsynchronousLoadListener(AsynchronousLoadListener l) {
+ for (ResizableIcon icon : iconDeck.values()) {
+ if (icon instanceof AsynchronousLoading)
+ ((AsynchronousLoading) icon).removeAsynchronousLoadListener(l);
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/ImageWrapperIcon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/ImageWrapperIcon.java
new file mode 100644
index 0000000..5ee9a38
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/ImageWrapperIcon.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.icon;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.InputStream;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+import javax.swing.Icon;
+import javax.swing.SwingWorker;
+import javax.swing.event.EventListenerList;
+
+import org.pushingpixels.flamingo.api.common.AsynchronousLoadListener;
+import org.pushingpixels.flamingo.api.common.AsynchronousLoading;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+/**
+ * Helper class to load images and expose them as icons of dynamic size.
+ *
+ * @author Kirill Grouchnikov
+ */
+abstract class ImageWrapperIcon implements Icon, AsynchronousLoading {
+ /**
+ * The original image.
+ */
+ protected BufferedImage originalImage;
+
+ /**
+ * The input stream of the original image.
+ */
+ protected InputStream imageInputStream;
+
+ /**
+ * The input stream of the original image.
+ */
+ protected Image image;
+
+ /**
+ * Contains all precomputed images.
+ */
+ protected Map<String, BufferedImage> cachedImages;
+
+ /**
+ * The width of the current image.
+ */
+ protected int width;
+
+ /**
+ * The height of the current image.
+ */
+ protected int height;
+
+ /**
+ * The listeners.
+ */
+ protected EventListenerList listenerList = new EventListenerList();
+
+ /**
+ * Create a new image-wrapper icon.
+ *
+ * @param inputStream
+ * The input stream to read the image from.
+ * @param w
+ * The width of the icon.
+ * @param h
+ * The height of the icon.
+ */
+ public ImageWrapperIcon(InputStream inputStream, int w, int h) {
+ this.imageInputStream = inputStream;
+ this.width = w;
+ this.height = h;
+ this.listenerList = new EventListenerList();
+ this.cachedImages = new LinkedHashMap<String, BufferedImage>() {
+ @Override
+ protected boolean removeEldestEntry(
+ Map.Entry<String, BufferedImage> eldest) {
+ return size() > 5;
+ };
+ };
+ this.renderImage(this.width, this.height);
+ }
+
+ /**
+ * Create a new image-wrapper icon.
+ *
+ * @param image
+ * The original image.
+ * @param w
+ * The width of the icon.
+ * @param h
+ * The height of the icon.
+ */
+ public ImageWrapperIcon(Image image, int w, int h) {
+ this.imageInputStream = null;
+ this.image = image;
+ this.width = w;
+ this.height = h;
+ this.listenerList = new EventListenerList();
+ this.cachedImages = new LinkedHashMap<String, BufferedImage>() {
+ @Override
+ protected boolean removeEldestEntry(
+ Map.Entry<String, BufferedImage> eldest) {
+ return size() > 5;
+ };
+ };
+ this.renderImage(this.width, this.height);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.AsynchronousLoading#addAsynchronousLoadListener
+ * (org.jvnet.flamingo.common.AsynchronousLoadListener)
+ */
+ @Override
+ public void addAsynchronousLoadListener(AsynchronousLoadListener l) {
+ this.listenerList.add(AsynchronousLoadListener.class, l);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.AsynchronousLoading#removeAsynchronousLoadListener
+ * (org.jvnet.flamingo.common.AsynchronousLoadListener)
+ */
+ @Override
+ public void removeAsynchronousLoadListener(AsynchronousLoadListener l) {
+ this.listenerList.remove(AsynchronousLoadListener.class, l);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return width;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return height;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ BufferedImage image = this.cachedImages.get(this.getIconWidth() + ":"
+ + this.getIconHeight());
+ if (image != null) {
+ int dx = (this.width - image.getWidth()) / 2;
+ int dy = (this.height - image.getHeight()) / 2;
+ g.drawImage(image, x + dx, y + dy, null);
+ }
+ }
+
+ /**
+ * Sets the preferred size for <code>this</code> icon. The rendering is
+ * scheduled automatically.
+ *
+ * @param dim
+ * Preferred size.
+ */
+ public synchronized void setPreferredSize(Dimension dim) {
+ if ((dim.width == this.width) && (dim.height == this.height))
+ return;
+ this.width = dim.width;
+ this.height = dim.height;
+
+ this.renderImage(this.width, this.height);
+ }
+
+ /**
+ * Renders the image.
+ *
+ * @param renderWidth
+ * Requested rendering width.
+ * @param renderHeight
+ * Requested rendering height.
+ */
+ protected synchronized void renderImage(final int renderWidth,
+ final int renderHeight) {
+ String key = renderWidth + ":" + renderHeight;
+ if (this.cachedImages.containsKey(key)) {
+ fireAsyncCompleted(true);
+ return;
+ }
+
+ SwingWorker<BufferedImage, Void> worker = new SwingWorker<BufferedImage, Void>() {
+ @Override
+ protected BufferedImage doInBackground() throws Exception {
+ if (imageInputStream != null) {
+ synchronized (imageInputStream) {
+ if (originalImage == null) {
+ // read original image
+ originalImage = ImageIO.read(imageInputStream);
+ }
+ }
+ } else {
+ GraphicsEnvironment e = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice d = e.getDefaultScreenDevice();
+ GraphicsConfiguration c = d.getDefaultConfiguration();
+ originalImage = c.createCompatibleImage(image
+ .getWidth(null), image.getHeight(null),
+ Transparency.TRANSLUCENT);
+ Graphics g = originalImage.getGraphics();
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+ }
+
+ BufferedImage result = originalImage;
+ float scaleX = (float) originalImage.getWidth()
+ / (float) renderWidth;
+ float scaleY = (float) originalImage.getHeight()
+ / (float) height;
+
+ float scale = Math.max(scaleX, scaleY);
+ if (scale > 1.0f) {
+ int finalWidth = (int) (originalImage.getWidth() / scale);
+ result = FlamingoUtilities.createThumbnail(originalImage,
+ finalWidth);
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void done() {
+ try {
+ BufferedImage bufferedImage = get();
+ cachedImages.put(renderWidth + ":" + renderHeight,
+ bufferedImage);
+ fireAsyncCompleted(true);
+ } catch (Exception exc) {
+ fireAsyncCompleted(false);
+ }
+ }
+ };
+ worker.execute();
+ }
+
+ /**
+ * Fires the asynchronous load event.
+ *
+ * @param event
+ * Event object.
+ */
+ protected void fireAsyncCompleted(Boolean event) {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == AsynchronousLoadListener.class) {
+ ((AsynchronousLoadListener) listeners[i + 1]).completed(event);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.AsynchronousLoading#isLoading()
+ */
+ @Override
+ public synchronized boolean isLoading() {
+ BufferedImage image = this.cachedImages.get(this.getIconWidth() + ":"
+ + this.getIconHeight());
+ return (image == null);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/ImageWrapperResizableIcon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/ImageWrapperResizableIcon.java
new file mode 100644
index 0000000..a34459c
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/ImageWrapperResizableIcon.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.icon;
+
+import java.awt.Dimension;
+import java.awt.Image;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * Implementation of {@link ResizableIcon} interface that wraps image files.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ImageWrapperResizableIcon extends ImageWrapperIcon implements
+ ResizableIcon {
+ /**
+ * Returns the icon for the specified URL.
+ *
+ * @param image
+ * Image.
+ * @param initialDim
+ * Initial dimension of the icon.
+ * @return Icon instance.
+ */
+ public static ImageWrapperResizableIcon getIcon(Image image,
+ Dimension initialDim) {
+ return new ImageWrapperResizableIcon(image, initialDim);
+ }
+
+ /**
+ * Returns the icon for the specified URL.
+ *
+ * @param location
+ * Icon URL.
+ * @param initialDim
+ * Initial dimension of the icon.
+ * @return Icon instance.
+ */
+ public static ImageWrapperResizableIcon getIcon(URL location,
+ Dimension initialDim) {
+ try {
+ return new ImageWrapperResizableIcon(location.openStream(),
+ initialDim);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Returns the icon for the specified input stream.
+ *
+ * @param inputStream
+ * Icon input stream.
+ * @param initialDim
+ * Initial dimension of the icon.
+ * @return Icon instance.
+ */
+ public static ImageWrapperResizableIcon getIcon(InputStream inputStream,
+ Dimension initialDim) {
+ return new ImageWrapperResizableIcon(inputStream, initialDim);
+ }
+
+ /**
+ * Creates a new image-based resizable icon.
+ *
+ * @param image
+ * Image.
+ * @param initialDim
+ * Initial dimension of the icon.
+ */
+ private ImageWrapperResizableIcon(Image image, Dimension initialDim) {
+ super(image, initialDim.width, initialDim.height);
+ }
+
+ /**
+ * Creates a new image-based resizable icon.
+ *
+ * @param inputStream
+ * Input stream with the image content.
+ * @param initialDim
+ * Initial dimension of the icon.
+ */
+ private ImageWrapperResizableIcon(InputStream inputStream,
+ final Dimension initialDim) {
+ super(inputStream, initialDim.width, initialDim.height);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.icon.ResizableIcon#setDimension(java.awt.Dimension
+ * )
+ */
+ @Override
+ public void setDimension(Dimension dim) {
+ this.setPreferredSize(dim);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/LayeredIcon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/LayeredIcon.java
new file mode 100644
index 0000000..716d331
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/LayeredIcon.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.icon;
+
+import java.awt.*;
+
+/**
+ * Decorator icon that layers icons one on top of the other. The original icons
+ * are drawn and resized together as one layered stack.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LayeredIcon implements ResizableIcon {
+ /**
+ * The layer icons.
+ */
+ protected ResizableIcon[] layers;
+
+ /**
+ * Creates a new layered icon.
+ *
+ * @param layers
+ * Layer icons.
+ */
+ public LayeredIcon(ResizableIcon... layers) {
+ this.layers = layers;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ResizableIcon#setDimension(java.awt.Dimension)
+ */
+ @Override
+ public void setDimension(Dimension newDimension) {
+ for (ResizableIcon layer : layers)
+ layer.setDimension(newDimension);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return layers[0].getIconHeight();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return layers[0].getIconWidth();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ for (ResizableIcon layer : layers)
+ layer.paintIcon(c, g, x, y);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/ResizableIcon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/ResizableIcon.java
new file mode 100644
index 0000000..3ce483e
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/icon/ResizableIcon.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.icon;
+
+import java.awt.Dimension;
+
+import javax.swing.Icon;
+
+/**
+ * Interface for icons that have resizability behaviour.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface ResizableIcon extends Icon {
+ /**
+ * Changes the dimension of <code>this</code> icon.
+ *
+ * @param newDimension
+ * New dimension for <code>this</code> icon.
+ */
+ public void setDimension(Dimension newDimension);
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/model/ActionButtonModel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/model/ActionButtonModel.java
new file mode 100644
index 0000000..dacf158
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/model/ActionButtonModel.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.model;
+
+import javax.swing.ButtonModel;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+
+/**
+ * Model for the action area of {@link AbstractCommandButton} component.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface ActionButtonModel extends ButtonModel {
+ /**
+ * Sets indication whether the associated actions should be fired on mouse
+ * press instead of mouse release.
+ *
+ * @param toFireActionOnPress
+ * if <code>true</code>, the associated actions will be fired on
+ * mouse press, otherwise the associated actions will be fired on
+ * mouse release.
+ */
+ public void setFireActionOnPress(boolean toFireActionOnPress);
+
+ /**
+ * Returns indication whether the associated actions should be fired on
+ * mouse press instead of mouse release.
+ *
+ * @return <code>true</code> if the associated actions are fired on mouse
+ * press, <code>false</code> if the associated actions are fired on
+ * mouse release.
+ */
+ public boolean isFireActionOnPress();
+}
\ No newline at end of file
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/model/ActionRepeatableButtonModel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/model/ActionRepeatableButtonModel.java
new file mode 100644
index 0000000..2d54e7b
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/model/ActionRepeatableButtonModel.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.model;
+
+import java.awt.AWTEvent;
+import java.awt.EventQueue;
+import java.awt.event.*;
+
+import javax.swing.DefaultButtonModel;
+import javax.swing.Timer;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+
+/**
+ * Extension of the default button model that supports the
+ * {@link ActionButtonModel} interface and repeated invocation of action
+ * listeners on mouse rollover. This is the default core action model set on
+ * {@link JCommandButton}s.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ActionRepeatableButtonModel extends DefaultButtonModel implements
+ ActionButtonModel {
+ /**
+ * The button behind the model.
+ */
+ private JCommandButton commandButton;
+
+ /**
+ * Timer for the auto-repeat action mode.
+ */
+ protected Timer autoRepeatTimer;
+
+ /**
+ * Indication whether the action is fired on mouse press (as opposed to
+ * mouse release).
+ */
+ protected boolean toFireActionOnPress;
+
+ /**
+ * Creates a new button model.
+ *
+ * @param commandButton
+ * The associated command button.
+ */
+ public ActionRepeatableButtonModel(JCommandButton commandButton) {
+ super();
+ this.commandButton = commandButton;
+ this.toFireActionOnPress = false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.DefaultButtonModel#setPressed(boolean)
+ */
+ @Override
+ public void setPressed(boolean b) {
+ if ((isPressed() == b) || !isEnabled()) {
+ return;
+ }
+
+ if (b) {
+ stateMask |= PRESSED;
+ } else {
+ stateMask &= ~PRESSED;
+ }
+
+ boolean toFireFirstAction = isArmed();
+ // if the button is in auto-repeat action mode, the action
+ // starts firing on press-down and not on press-up
+ if (commandButton.isAutoRepeatAction() || isFireActionOnPress())
+ toFireFirstAction = isPressed() && toFireFirstAction;
+ else
+ toFireFirstAction = !isPressed() && toFireFirstAction;
+
+ // no action on popup only command buttons
+ if (commandButton.getCommandButtonKind() == CommandButtonKind.POPUP_ONLY)
+ toFireFirstAction = false;
+
+ if (this.commandButton.isFireActionOnRollover()) {
+ // the action is invoked on rollover
+ toFireFirstAction = false;
+ }
+
+ int modifiers = 0;
+ AWTEvent currentEvent = EventQueue.getCurrentEvent();
+ if (currentEvent instanceof InputEvent) {
+ modifiers = ((InputEvent) currentEvent).getModifiers();
+ } else if (currentEvent instanceof ActionEvent) {
+ modifiers = ((ActionEvent) currentEvent).getModifiers();
+ }
+
+ if (toFireFirstAction) {
+ fireActionPerformed(new ActionEvent(this,
+ ActionEvent.ACTION_PERFORMED, getActionCommand(),
+ EventQueue.getMostRecentEventTime(), modifiers));
+ if (commandButton.isAutoRepeatAction()) {
+ // start timer
+ this.startActionTimer(modifiers);
+ }
+ }
+
+ // we need to stop timer when the non-action-on-rollover button
+ // gets pressed=false and it is in auto-repeat mode
+ if (!this.commandButton.isFireActionOnRollover()) {
+ if (this.commandButton.isAutoRepeatAction() && !b) {
+ this.stopActionTimer();
+ }
+ }
+
+ fireStateChanged();
+ }
+
+ @Override
+ public void setRollover(boolean b) {
+ if ((isRollover() == b) || !isEnabled()) {
+ return;
+ }
+
+ if (b) {
+ stateMask |= ROLLOVER;
+ } else {
+ stateMask &= ~ROLLOVER;
+ }
+
+ if (this.commandButton.isFireActionOnRollover()) {
+ if (b
+ && !this.isActionTimerRunning()
+ && (this.commandButton.getCommandButtonKind() != CommandButtonKind.POPUP_ONLY)) {
+ // action-on-rollover non-popup-only button that gained
+ // rollover and the action timer is not running - fire the
+ // first event and start the action timer.
+ int modifiers = 0;
+ AWTEvent currentEvent = EventQueue.getCurrentEvent();
+ if (currentEvent instanceof InputEvent) {
+ modifiers = ((InputEvent) currentEvent).getModifiers();
+ } else if (currentEvent instanceof ActionEvent) {
+ modifiers = ((ActionEvent) currentEvent).getModifiers();
+ }
+
+ fireActionPerformed(new ActionEvent(this,
+ ActionEvent.ACTION_PERFORMED, getActionCommand(),
+ EventQueue.getMostRecentEventTime(), modifiers));
+ if (commandButton.isAutoRepeatAction()) {
+ // start timer
+ this.startActionTimer(modifiers);
+ }
+ }
+
+ if (!b) {
+ this.stopActionTimer();
+ }
+ }
+
+ fireStateChanged();
+ }
+
+ /**
+ * Stop the action timer.
+ */
+ private void stopActionTimer() {
+ if (this.autoRepeatTimer != null)
+ this.autoRepeatTimer.stop();
+ }
+
+ /**
+ * Checks whether the action timer is running.
+ *
+ * @return <code>true</code> if the action timer is running,
+ * <code>false</code> otherwise.
+ */
+ private boolean isActionTimerRunning() {
+ if (this.autoRepeatTimer == null)
+ return false;
+ return this.autoRepeatTimer.isRunning();
+ }
+
+ /**
+ * Starts the action timer, passing the specified modifiers to the action
+ * event that will be fired in a loop.
+ *
+ * @param modifiers
+ * Modifiers for the action event to be fired.
+ */
+ private void startActionTimer(final int modifiers) {
+ this.autoRepeatTimer = new Timer(this.commandButton
+ .getAutoRepeatSubsequentInterval(), new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (!isEnabled() || !commandButton.isVisible()
+ || !commandButton.isDisplayable()) {
+ // stop the timer when the button becomes
+ // disabled, invisible or undisplayable
+ autoRepeatTimer.stop();
+ return;
+ }
+ fireActionPerformed(new ActionEvent(this,
+ ActionEvent.ACTION_PERFORMED, getActionCommand(),
+ EventQueue.getMostRecentEventTime(), modifiers));
+ }
+ });
+ this.autoRepeatTimer.setInitialDelay(this.commandButton
+ .getAutoRepeatInitialInterval());
+ this.autoRepeatTimer.start();
+ }
+
+ @Override
+ public boolean isFireActionOnPress() {
+ return this.toFireActionOnPress;
+ }
+
+ @Override
+ public void setFireActionOnPress(boolean toFireActionOnPress) {
+ this.toFireActionOnPress = toFireActionOnPress;
+ }
+}
\ No newline at end of file
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/model/ActionToggleButtonModel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/model/ActionToggleButtonModel.java
new file mode 100644
index 0000000..6732929
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/model/ActionToggleButtonModel.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.model;
+
+import java.awt.AWTEvent;
+import java.awt.EventQueue;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+
+import javax.swing.JToggleButton.ToggleButtonModel;
+
+import org.pushingpixels.flamingo.api.common.JCommandToggleButton;
+
+/**
+ * Extension of the default toggle button model that supports the
+ * {@link ActionButtonModel} interface. This is the default core action model
+ * set on {@link JCommandToggleButton}s.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ActionToggleButtonModel extends ToggleButtonModel implements
+ ActionButtonModel {
+ /**
+ * Indication whether the action is fired on mouse press (as opposed to
+ * mouse release).
+ */
+ protected boolean toFireActionOnPress;
+
+ /**
+ * Creates a new model.
+ *
+ * @param toFireActionOnPress
+ * If <code>true</code>, the action will be fired on mouse press,
+ * if <code>false</code>, the action will be fired on mouse
+ * release.
+ */
+ public ActionToggleButtonModel(boolean toFireActionOnPress) {
+ this.toFireActionOnPress = toFireActionOnPress;
+ }
+
+ @Override
+ public boolean isFireActionOnPress() {
+ return this.toFireActionOnPress;
+ }
+
+ @Override
+ public void setFireActionOnPress(boolean toFireActionOnPress) {
+ this.toFireActionOnPress = toFireActionOnPress;
+ }
+
+ @Override
+ public void setPressed(boolean b) {
+ if ((isPressed() == b) || !isEnabled()) {
+ return;
+ }
+
+ if (isArmed()) {
+ // change selection prior to firing the action event
+ if (!this.isFireActionOnPress()) {
+ if (!b) {
+ setSelected(!this.isSelected());
+ }
+ } else {
+ if (b) {
+ setSelected(!this.isSelected());
+ }
+ }
+ }
+
+ if (b) {
+ stateMask |= PRESSED;
+ } else {
+ stateMask &= ~PRESSED;
+ }
+
+ fireStateChanged();
+
+ boolean toFireAction = false;
+ if (this.isFireActionOnPress()) {
+ toFireAction = isPressed() && isArmed();
+ } else {
+ toFireAction = !isPressed() && isArmed();
+ }
+
+ if (toFireAction) {
+ int modifiers = 0;
+ AWTEvent currentEvent = EventQueue.getCurrentEvent();
+ if (currentEvent instanceof InputEvent) {
+ modifiers = ((InputEvent) currentEvent).getModifiers();
+ } else if (currentEvent instanceof ActionEvent) {
+ modifiers = ((ActionEvent) currentEvent).getModifiers();
+ }
+ fireActionPerformed(new ActionEvent(this,
+ ActionEvent.ACTION_PERFORMED, getActionCommand(),
+ EventQueue.getMostRecentEventTime(), modifiers));
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/model/PopupButtonModel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/model/PopupButtonModel.java
new file mode 100644
index 0000000..ce4bb54
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/model/PopupButtonModel.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.model;
+
+import javax.swing.ButtonModel;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.flamingo.api.common.PopupActionListener;
+
+/**
+ * Model for the popup area of {@link JCommandButton} component.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface PopupButtonModel extends ButtonModel {
+ /**
+ * Adds an <code>PopupActionListener</code> to the model.
+ *
+ * @param l
+ * the listener to add
+ */
+ void addPopupActionListener(PopupActionListener l);
+
+ /**
+ * Removes an <code>PopupActionListener</code> from the model.
+ *
+ * @param l
+ * the listener to remove
+ */
+ void removePopupActionListener(PopupActionListener l);
+
+ /**
+ * Sets indication on the visibility status of the associated popup.
+ *
+ * @param flag
+ * The visibility status of the associated popup.
+ */
+ void setPopupShowing(boolean flag);
+
+ /**
+ * Returns indication whether the associated popup is showing.
+ *
+ * @return <code>true</code> if the associated popup is showing,
+ * <code>false</code> otherwise.
+ */
+ boolean isPopupShowing();
+}
\ No newline at end of file
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/JColorSelectorPopupMenu.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/JColorSelectorPopupMenu.java
new file mode 100644
index 0000000..2504e19
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/JColorSelectorPopupMenu.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.popup;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.JPanel;
+
+import org.pushingpixels.flamingo.api.common.JCommandMenuButton;
+import org.pushingpixels.flamingo.api.common.JCommandToggleMenuButton;
+import org.pushingpixels.flamingo.internal.ui.common.popup.JColorSelectorComponent;
+import org.pushingpixels.flamingo.internal.ui.common.popup.JColorSelectorPanel;
+
+public class JColorSelectorPopupMenu extends JCommandPopupMenu {
+ private ColorSelectorCallback colorSelectorCallback;
+
+ private JColorSelectorPanel lastColorSelectorPanel;
+
+ private static LinkedList<Color> recentlySelected = new LinkedList<Color>();
+
+ public static interface ColorSelectorCallback {
+ public void onColorRollover(Color color);
+
+ public void onColorSelected(Color color);
+ }
+
+ public JColorSelectorPopupMenu(ColorSelectorCallback colorSelectorCallback) {
+ this.colorSelectorCallback = colorSelectorCallback;
+ }
+
+ public void addColorSectionWithDerived(String label, Color[] primaryColors) {
+ if ((primaryColors == null) || (primaryColors.length != 10)) {
+ throw new IllegalArgumentException("Must pass exactly 10 colors");
+ }
+ JPanel selectorContainer = new MultiRowSelector(primaryColors);
+ JColorSelectorPanel selector = new JColorSelectorPanel(label,
+ selectorContainer);
+ this.addMenuPanel(selector);
+
+ this.lastColorSelectorPanel = selector;
+ }
+
+ public void addColorSection(String label, Color[] primaryColors) {
+ if ((primaryColors == null) || (primaryColors.length != 10)) {
+ throw new IllegalArgumentException("Must pass exactly 10 colors");
+ }
+ JPanel selectorContainer = new SingleRowSelector(primaryColors);
+ JColorSelectorPanel selector = new JColorSelectorPanel(label,
+ selectorContainer);
+ this.addMenuPanel(selector);
+ this.lastColorSelectorPanel = selector;
+ }
+
+ public void addRecentSection(String label) {
+ JPanel recent = new SingleRowSelector(recentlySelected
+ .toArray(new Color[0]));
+ JColorSelectorPanel recentPanel = new JColorSelectorPanel(label, recent);
+ recentPanel.setLastPanel(true);
+ this.addMenuPanel(recentPanel);
+ this.lastColorSelectorPanel = recentPanel;
+ }
+
+ @Override
+ public void addMenuButton(JCommandMenuButton menuButton) {
+ super.addMenuButton(menuButton);
+ this.updateLastColorSelectorPanel();
+ }
+
+ @Override
+ public void addMenuButton(JCommandToggleMenuButton menuButton) {
+ super.addMenuButton(menuButton);
+ this.updateLastColorSelectorPanel();
+ }
+
+ @Override
+ public void addMenuSeparator() {
+ super.addMenuSeparator();
+ this.updateLastColorSelectorPanel();
+ }
+
+ private void updateLastColorSelectorPanel() {
+ if (this.lastColorSelectorPanel != null) {
+ this.lastColorSelectorPanel.setLastPanel(true);
+ this.lastColorSelectorPanel = null;
+ }
+ }
+
+ public ColorSelectorCallback getColorSelectorCallback() {
+ return this.colorSelectorCallback;
+ }
+
+ private static void wireToLRU(JColorSelectorComponent colorSelector) {
+ colorSelector
+ .addColorSelectorCallback(new JColorSelectorPopupMenu.ColorSelectorCallback() {
+ @Override
+ public void onColorSelected(Color color) {
+ addColorToRecentlyUsed(color);
+ }
+
+ @Override
+ public void onColorRollover(Color color) {
+ }
+ });
+ }
+
+ public synchronized static List<Color> getRecentlyUsedColors() {
+ return Collections.unmodifiableList(recentlySelected);
+ }
+
+ public synchronized static void addColorToRecentlyUsed(Color color) {
+ // is in?
+ if (recentlySelected.contains(color)) {
+ recentlySelected.remove(color);
+ recentlySelected.addLast(color);
+ return;
+ }
+
+ if (recentlySelected.size() == 10) {
+ recentlySelected.removeFirst();
+ }
+ recentlySelected.addLast(color);
+ }
+
+ private class SingleRowSelector extends JPanel {
+ public SingleRowSelector(final Color... colors) {
+ final JColorSelectorComponent[] comps = new JColorSelectorComponent[colors.length];
+ for (int i = 0; i < colors.length; i++) {
+ comps[i] = new JColorSelectorComponent(colors[i],
+ colorSelectorCallback);
+ wireToLRU(comps[i]);
+ this.add(comps[i]);
+ }
+
+ this.setLayout(new LayoutManager() {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return new Dimension(10, 10);
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ int gap = getGap();
+ int size = getSize();
+ return new Dimension(colors.length * size
+ + (colors.length + 1) * gap, size + 2 * gap);
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ int gap = getGap();
+ int size = getSize();
+
+ if (parent.getComponentOrientation().isLeftToRight()) {
+ int x = gap;
+ int y = gap;
+ for (int i = 0; i < colors.length; i++) {
+ comps[i].setBounds(x, y, size, size);
+ x += (size + gap);
+ }
+ } else {
+ int x = getWidth() - gap;
+ int y = gap;
+ for (int i = 0; i < colors.length; i++) {
+ comps[i].setBounds(x - size, y, size, size);
+ x -= (size + gap);
+ }
+ }
+ }
+
+ private int getGap() {
+ return 4;
+ }
+
+ private int getSize() {
+ return 13;
+ }
+ });
+ }
+ }
+
+ private class MultiRowSelector extends JPanel {
+ static final int SECONDARY_ROWS = 5;
+
+ public MultiRowSelector(final Color... colors) {
+ final JColorSelectorComponent[][] comps = new JColorSelectorComponent[colors.length][1 + SECONDARY_ROWS];
+ for (int i = 0; i < colors.length; i++) {
+ Color primary = colors[i];
+
+ comps[i][0] = new JColorSelectorComponent(primary,
+ colorSelectorCallback);
+ wireToLRU(comps[i][0]);
+ this.add(comps[i][0]);
+
+ float[] primaryHsb = new float[3];
+ Color.RGBtoHSB(primary.getRed(), primary.getGreen(), primary
+ .getBlue(), primaryHsb);
+
+ for (int row = 1; row <= SECONDARY_ROWS; row++) {
+ float bFactor = (float) (row - 1)
+ / (float) (SECONDARY_ROWS);
+ bFactor = (float) Math.pow(bFactor, 1.4f);
+ float brightness = 1.0f - bFactor;
+
+ if (primaryHsb[1] == 0.0f) {
+ // special handling for gray scale
+ float max = 0.5f + 0.5f * primaryHsb[2];
+ brightness = max * (SECONDARY_ROWS - row + 1)
+ / SECONDARY_ROWS;
+ }
+
+ Color secondary = new Color(Color.HSBtoRGB(primaryHsb[0],
+ primaryHsb[1] * (row + 1) / (SECONDARY_ROWS + 1),
+ brightness));
+
+ comps[i][row] = new JColorSelectorComponent(secondary,
+ colorSelectorCallback);
+ comps[i][row].setTopOpen(row > 1);
+ comps[i][row].setBottomOpen(row < SECONDARY_ROWS);
+ wireToLRU(comps[i][row]);
+ this.add(comps[i][row]);
+ }
+ }
+
+ this.setLayout(new LayoutManager() {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return new Dimension(10, 10);
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ int gap = getGap();
+ int size = getSize();
+ return new Dimension(colors.length * size
+ + (colors.length + 1) * gap, gap + size + gap
+ + SECONDARY_ROWS * size + gap);
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ int gap = getGap();
+ int size = getSize();
+
+ if (parent.getComponentOrientation().isLeftToRight()) {
+ int y = gap;
+ for (int row = 0; row <= SECONDARY_ROWS; row++) {
+ int x = gap;
+ for (int i = 0; i < colors.length; i++) {
+ comps[i][row].setBounds(x, y, size, size);
+ x += (size + gap);
+ }
+ y += size;
+ if (row == 0) {
+ y += gap;
+ }
+ }
+ } else {
+ int y = gap;
+
+ for (int row = 0; row <= SECONDARY_ROWS; row++) {
+ int x = getWidth() - gap;
+ for (int i = 0; i < colors.length; i++) {
+ comps[i][row]
+ .setBounds(x - size, y, size, size);
+ x -= (size + gap);
+ }
+ y += size;
+ if (row == 0) {
+ y += gap;
+ }
+ }
+ }
+ }
+
+ private int getGap() {
+ return 4;
+ }
+
+ private int getSize() {
+ return 13;
+ }
+ });
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/JCommandPopupMenu.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/JCommandPopupMenu.java
new file mode 100644
index 0000000..515d8b7
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/JCommandPopupMenu.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.popup;
+
+import java.awt.Component;
+import java.util.ArrayList;
+import java.util.Collections;
+
+import javax.swing.*;
+import javax.swing.JPopupMenu.Separator;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.internal.ui.common.popup.BasicCommandPopupMenuUI;
+import org.pushingpixels.flamingo.internal.ui.common.popup.PopupPanelUI;
+
+/**
+ * Popup menu. Can host any number of command menu buttons added with
+ * {@link #addMenuButton(JCommandMenuButton)} separated with optional
+ * {@link #addMenuSeparator()}. The
+ * {@link #JCommandPopupMenu(JCommandButtonPanel, int, int)} constructor allows
+ * placing a scrollable command button panel in the top part of the popup menu.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JCommandPopupMenu extends JPopupPanel {
+ /**
+ * @see #getUIClassID
+ */
+ public static final String uiClassID = "CommandPopupMenuUI";
+
+ /**
+ * The main button panel. Can be <code>null</code> if this command popup
+ * menu was created with the {@link #JCommandPopupMenu()} constructor.
+ *
+ * @see #JCommandPopupMenu(JCommandButtonPanel, int, int)
+ * @see #hasCommandButtonPanel()
+ * @see #getMainButtonPanel()
+ */
+ protected JCommandButtonPanel mainButtonPanel;
+
+ /**
+ * Menu components. This list holds:
+ * <ul>
+ * <li>{@link JCommandMenuButton}s added with
+ * {@link #addMenuButton(JCommandMenuButton)}</li>
+ * <li>{@link JCommandToggleMenuButton}s added with
+ * {@link #addMenuButton(JCommandToggleMenuButton)}</li>
+ * <li>{@link Separator}s added with {@link #addMenuSeparator()}</li>
+ * <li>{@link JPanel}s added by the subclasses with
+ * {@link #addMenuPanel(JPanel)}</li>
+ * </ul>
+ *
+ * @see #addMenuButton(JCommandMenuButton)
+ * @see #addMenuButton(JCommandToggleMenuButton)
+ * @see #addMenuSeparator()
+ * @see #addMenuPanel(JPanel)
+ * @see #getMenuComponents()
+ */
+ protected java.util.List<Component> menuComponents;
+
+ /**
+ * Maximum number of button columns visible in the {@link #mainButtonPanel}.
+ *
+ * @see #JCommandPopupMenu(JCommandButtonPanel, int, int)
+ * @see #getMaxButtonColumns()
+ */
+ protected int maxButtonColumns;
+
+ /**
+ * Maximum number of button rows visible in the {@link #mainButtonPanel}.
+ *
+ * @see #JCommandPopupMenu(JCommandButtonPanel, int, int)
+ * @see #getMaxVisibleButtonRows()
+ */
+ protected int maxVisibleButtonRows;
+
+ /**
+ * Maximum number of menu items visible in this menu. If more buttons are
+ * added with the {@link #addMenuButton(JCommandMenuButton)} and
+ * {@link #addMenuButton(JCommandToggleMenuButton)} APIs, the menu part will
+ * show scroller buttons above the first and below the last menu button. If
+ * the value is negative, there is no limitation on how many menu buttons
+ * are shown, and the entire popup menu can overflow the monitor edges.
+ */
+ protected int maxVisibleMenuButtons;
+
+ private boolean toDismissOnChildClick;
+
+ /**
+ * Creates an empty popup menu with no button panel.
+ */
+ public JCommandPopupMenu() {
+ this.menuComponents = new ArrayList<Component>();
+
+ this.maxVisibleMenuButtons = -1;
+ this.toDismissOnChildClick = true;
+ }
+
+ /**
+ * Creates a popup menu hosting the specified button panel.
+ *
+ * @param buttonPanel
+ * Fully constructed button panel.
+ * @param maxButtonColumns
+ * Maximum number of button columns visible in
+ * <code>buttonPanel</code>.
+ * @param maxVisibleButtonRows
+ * Maximum number of button rows visible in
+ * <code>buttonPanel</code>.
+ */
+ public JCommandPopupMenu(JCommandButtonPanel buttonPanel,
+ int maxButtonColumns, int maxVisibleButtonRows) {
+ this();
+
+ this.mainButtonPanel = buttonPanel;
+ this.maxButtonColumns = maxButtonColumns;
+ this.maxVisibleButtonRows = maxVisibleButtonRows;
+
+ this.updateUI();
+ }
+
+ /**
+ * Adds the specified menu button to this menu.
+ *
+ * @param menuButton
+ * Menu button to add.
+ */
+ public void addMenuButton(JCommandMenuButton menuButton) {
+ menuButton.setHorizontalAlignment(SwingUtilities.LEFT);
+ this.menuComponents.add(menuButton);
+ this.fireStateChanged();
+ }
+
+ /**
+ * Adds the specified toggle menu button to this menu.
+ *
+ * @param menuButton
+ * Menu button to add.
+ */
+ public void addMenuButton(JCommandToggleMenuButton menuButton) {
+ menuButton.setHorizontalAlignment(SwingUtilities.LEFT);
+ this.menuComponents.add(menuButton);
+ this.fireStateChanged();
+ }
+
+ /**
+ * Adds a menu separator to this menu.
+ */
+ public void addMenuSeparator() {
+ this.menuComponents.add(new JPopupMenu.Separator());
+ this.fireStateChanged();
+ }
+
+ /**
+ * Adds a menu panel to this menu.
+ *
+ * @param menuPanel
+ * Menu panel to add.
+ */
+ protected void addMenuPanel(JPanel menuPanel) {
+ if (this.maxVisibleMenuButtons > 0) {
+ throw new IllegalStateException(
+ "This method is not supported on menu that contains a command button panel");
+ }
+ this.menuComponents.add(menuPanel);
+ this.fireStateChanged();
+ }
+
+ /**
+ * Returns indication whether this menu has a command button panel.
+ *
+ * @return <code>true</code> if this menu has a command button panel,
+ * <code>false</code> otherwise.
+ * @see #getMainButtonPanel()
+ */
+ public boolean hasCommandButtonPanel() {
+ return (this.mainButtonPanel != null);
+ }
+
+ /**
+ * Returns the command button panel of this menu. Can return
+ * <code>null</code>.
+ *
+ * @return The command button panel of this menu.
+ * @see #hasCommandButtonPanel()
+ */
+ public JCommandButtonPanel getMainButtonPanel() {
+ return this.mainButtonPanel;
+ }
+
+ /**
+ * Returns an unmodifiable list of all the menu components. Can return
+ * <code>null</code>.
+ *
+ * @return An unmodifiable list of all the menu components
+ */
+ public java.util.List<Component> getMenuComponents() {
+ if (this.menuComponents == null)
+ return null;
+ return Collections.unmodifiableList(this.menuComponents);
+ }
+
+ /**
+ * Returns the maximum number of button columns visible in the command
+ * button panel of this menu. If this menu has been created with the
+ * {@link #JCommandPopupMenu()} constructor, zero is returned.
+ *
+ * @return The maximum number of button columns visible in the command
+ * button panel of this menu.
+ * @see #JCommandPopupMenu(JCommandButtonPanel, int, int)
+ * @see #getMaxVisibleButtonRows()
+ */
+ public int getMaxButtonColumns() {
+ return this.maxButtonColumns;
+ }
+
+ /**
+ * Returns the maximum number of button rows visible in the command button
+ * panel of this menu. If this menu has been created with the
+ * {@link #JCommandPopupMenu()} constructor, zero is returned.
+ *
+ * @return The maximum number of button rows visible in the command button
+ * panel of this menu.
+ * @see #JCommandPopupMenu(JCommandButtonPanel, int, int)
+ * @see #getMaxButtonColumns()
+ */
+ public int getMaxVisibleButtonRows() {
+ return this.maxVisibleButtonRows;
+ }
+
+ /**
+ * Returns the maximum number of menu items visible in this menu.
+ *
+ * @return The maximum number of menu items visible in this menu. If the
+ * value is negative, there is no limitation on how many menu
+ * buttons are shown, and the entire popup menu can overflow the
+ * monitor edges.
+ */
+ public int getMaxVisibleMenuButtons() {
+ return this.maxVisibleMenuButtons;
+ }
+
+ /**
+ * Sets the maximum number of menu items visible in this menu. If the value
+ * is negative, there is no limitation on how many menu buttons are shown,
+ * and the entire popup menu can overflow the monitor edges.
+ *
+ * @param maxVisibleMenuButtons
+ * The new value for the maximum number of menu items visible in
+ * this menu.
+ */
+ public void setMaxVisibleMenuButtons(int maxVisibleMenuButtons) {
+ for (Component menuComp : this.menuComponents) {
+ if (menuComp instanceof JPanel) {
+ throw new IllegalStateException(
+ "This method is not supported on menus with panels");
+ }
+ }
+
+ int old = this.maxVisibleMenuButtons;
+ this.maxVisibleMenuButtons = maxVisibleMenuButtons;
+
+ if (old != this.maxVisibleMenuButtons) {
+ this.firePropertyChange("maxVisibleMenuButtons", old,
+ this.maxVisibleMenuButtons);
+ }
+ }
+
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((PopupPanelUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicCommandPopupMenuUI.createUI(this));
+ }
+ }
+
+ /**
+ * Adds the specified change listener to track changes to this popup menu.
+ *
+ * @param l
+ * Change listener to add.
+ * @see #removeChangeListener(ChangeListener)
+ */
+ public void addChangeListener(ChangeListener l) {
+ this.listenerList.add(ChangeListener.class, l);
+ }
+
+ /**
+ * Removes the specified change listener from tracking changes to this popup
+ * menu.
+ *
+ * @param l
+ * Change listener to remove.
+ * @see #addChangeListener(ChangeListener)
+ */
+ public void removeChangeListener(ChangeListener l) {
+ this.listenerList.remove(ChangeListener.class, l);
+ }
+
+ /**
+ * Notifies all registered listener that the state of this popup menu has
+ * changed.
+ */
+ protected void fireStateChanged() {
+ // Guaranteed to return a non-null array
+ Object[] listeners = this.listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ ChangeEvent event = new ChangeEvent(this);
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ChangeListener.class) {
+ ((ChangeListener) listeners[i + 1]).stateChanged(event);
+ }
+ }
+ }
+
+ public boolean isToDismissOnChildClick() {
+ return toDismissOnChildClick;
+ }
+
+ public void setToDismissOnChildClick(boolean toDismissOnChildClick) {
+ this.toDismissOnChildClick = toDismissOnChildClick;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/JPopupPanel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/JPopupPanel.java
new file mode 100644
index 0000000..343b9c1
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/JPopupPanel.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.popup;
+
+import java.awt.Rectangle;
+
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+
+import org.pushingpixels.flamingo.internal.ui.common.popup.BasicPopupPanelUI;
+import org.pushingpixels.flamingo.internal.ui.common.popup.PopupPanelUI;
+
+/**
+ * Base class for popup panels.
+ *
+ * @author Kirill Grouchnikov
+ * @see PopupPanelManager#addPopup(javax.swing.JComponent, javax.swing.Popup,
+ * JPopupPanel)
+ */
+public abstract class JPopupPanel extends JPanel {
+ /**
+ * @see #getUIClassID
+ */
+ public static final String uiClassID = "PopupPanelUI";
+
+ /**
+ * The customizer for this popup panel. Can be <code>null</code>.
+ *
+ * @see #getCustomizer()
+ * @see #setCustomizer(PopupPanelCustomizer)
+ */
+ protected PopupPanelCustomizer customizer;
+
+ /**
+ * Allows providing custom application logic for computing the screen bounds
+ * of popup panels before they are shown on the screen.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static interface PopupPanelCustomizer {
+ /**
+ * Returns the requested screen bounds of the associated popup panel.
+ *
+ * @return The requested screen bounds of the associated popup panel.
+ */
+ public Rectangle getScreenBounds();
+ }
+
+ /**
+ * Protected to prevent direct instantiation.
+ */
+ protected JPopupPanel() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#getUI()
+ */
+ @Override
+ public PopupPanelUI getUI() {
+ return (PopupPanelUI) ui;
+ }
+
+ /**
+ * Sets the look and feel (L&F) object that renders this component.
+ *
+ * @param ui
+ * the PopupGalleryUI L&F object
+ */
+ protected void setUI(PopupPanelUI ui) {
+ super.setUI(ui);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((PopupPanelUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicPopupPanelUI.createUI(this));
+ }
+ }
+
+ /**
+ * Sets the customizer for this popup panel.
+ *
+ * @param customizer
+ * The customizer for this popup panel.
+ * @see #getCustomizer()
+ */
+ public void setCustomizer(PopupPanelCustomizer customizer) {
+ this.customizer = customizer;
+ }
+
+ /**
+ * Returns the customizer of this popup panel. Can return <code>null</code>.
+ *
+ * @return The customizer of this popup panel.
+ * @see #setCustomizer(PopupPanelCustomizer)
+ */
+ public PopupPanelCustomizer getCustomizer() {
+ return this.customizer;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/PopupPanelCallback.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/PopupPanelCallback.java
new file mode 100644
index 0000000..279b8a3
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/PopupPanelCallback.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.popup;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntrySecondary;
+
+/**
+ * An application hook that allows associating a custom popup panel with a popup
+ * area of the specific command button.
+ *
+ * @author Kirill Grouchnikov
+ * @see JCommandButton#setPopupCallback(PopupPanelCallback)
+ * @see RibbonApplicationMenuEntrySecondary#setPopupCallback(PopupPanelCallback)
+ */
+public interface PopupPanelCallback {
+ /**
+ * Returns the popup panel to be shown when the popup area of the specified
+ * command button is activated.
+ *
+ * @param commandButton
+ * Command button.
+ * @return The popup panel to be shown when the popup area of the specified
+ * command button is activated.
+ */
+ public JPopupPanel getPopupPanel(JCommandButton commandButton);
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/PopupPanelManager.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/PopupPanelManager.java
new file mode 100644
index 0000000..7a5b0e2
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/common/popup/PopupPanelManager.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.common.popup;
+
+import java.awt.Component;
+import java.awt.event.ComponentEvent;
+import java.util.*;
+
+import javax.swing.JComponent;
+import javax.swing.Popup;
+import javax.swing.event.EventListenerList;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+
+/**
+ * Manager for showing and hiding {@link JPopupPanel}s.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class PopupPanelManager {
+ /**
+ * Listener on showing and hiding the popup panels.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static interface PopupListener extends EventListener {
+ /**
+ * Fired when a popup panel has been shown.
+ *
+ * @param event
+ * Popup event.
+ */
+ void popupShown(PopupEvent event);
+
+ /**
+ * Fired when a popup panel has been hidden.
+ *
+ * @param event
+ * Popup event.
+ */
+ void popupHidden(PopupEvent event);
+ }
+
+ /**
+ * Popup event.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class PopupEvent extends ComponentEvent {
+ /**
+ * ID of "popup shown" event.
+ */
+ public static final int POPUP_SHOWN = 100;
+
+ /**
+ * ID of "popup hidden" event.
+ */
+ public static final int POPUP_HIDDEN = 101;
+
+ /**
+ * The popup originator component.
+ */
+ private JComponent popupOriginator;
+
+ /**
+ * Creates a new popup event.
+ *
+ * @param source
+ * Event source.
+ * @param id
+ * Event ID.
+ * @param popupOriginator
+ * Popup originator component.
+ */
+ public PopupEvent(JPopupPanel source, int id, JComponent popupOriginator) {
+ super(source, id);
+ this.popupOriginator = popupOriginator;
+ }
+
+ /**
+ * Returns the popup originator component.
+ *
+ * @return Popup originator component.
+ */
+ public JComponent getPopupOriginator() {
+ return this.popupOriginator;
+ }
+ }
+
+ /**
+ * List of all registered listeners.
+ */
+ protected EventListenerList listenerList = new EventListenerList();
+
+ /**
+ * The singleton instance of popup panel manager.
+ */
+ private static final PopupPanelManager instance = new PopupPanelManager();
+
+ /**
+ * Information on a single showing popup.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class PopupInfo {
+ /**
+ * The popup panel.
+ */
+ private JPopupPanel popupPanel;
+
+ /**
+ * The originating component.
+ */
+ private JComponent popupOriginator;
+
+ /**
+ * Creates a new information object.
+ *
+ * @param popupOriginator
+ * The originating component.
+ * @param popupPanel
+ * The popup panel.
+ */
+ public PopupInfo(JComponent popupOriginator, JPopupPanel popupPanel) {
+ this.popupOriginator = popupOriginator;
+ this.popupPanel = popupPanel;
+ }
+
+ /**
+ * Returns the popup panel.
+ *
+ * @return The popup panel.
+ */
+ public JPopupPanel getPopupPanel() {
+ return this.popupPanel;
+ }
+
+ /**
+ * Returns the originating component.
+ *
+ * @return The originating component.
+ */
+ public JComponent getPopupOriginator() {
+ return this.popupOriginator;
+ }
+ }
+
+ /**
+ * Returns the default popup panel manager.
+ *
+ * @return a PopupPanelManager object
+ */
+ public static PopupPanelManager defaultManager() {
+ return instance;
+ }
+
+ /**
+ * All currently shown popup panels.
+ */
+ protected LinkedList<PopupInfo> shownPath = new LinkedList<PopupInfo>();
+
+ /**
+ * Map of all popup panels and associated {@link Popup} objects.
+ */
+ protected Map<JPopupPanel, Popup> popupPanels = new HashMap<JPopupPanel, Popup>();
+
+ /**
+ * Adds new popup to the tracking structures.
+ *
+ * @param popupOriginator
+ * The originating component.
+ * @param popup
+ * The new popup.
+ * @param popupInitiator
+ * The initiator of the popup.
+ */
+ public void addPopup(JComponent popupOriginator, Popup popup,
+ JPopupPanel popupInitiator) {
+ popupPanels.put(popupInitiator, popup);
+ shownPath.addLast(new PopupInfo(popupOriginator, popupInitiator));
+ popup.show();
+ if (popupOriginator instanceof JCommandButton) {
+ ((JCommandButton) popupOriginator).getPopupModel().setPopupShowing(
+ true);
+ }
+ this.firePopupShown(popupInitiator, popupOriginator);
+ }
+
+ /**
+ * Hides the last shown popup panel.
+ */
+ public void hideLastPopup() {
+ if (shownPath.size() == 0)
+ return;
+ PopupInfo last = shownPath.removeLast();
+ Popup popup = popupPanels.get(last.popupPanel);
+ popup.hide();
+ popupPanels.remove(last.popupPanel);
+ if (last.popupOriginator instanceof JCommandButton) {
+ ((JCommandButton) last.popupOriginator).getPopupModel()
+ .setPopupShowing(false);
+ }
+
+ // KeyTipManager.defaultManager().showChainBefore(last.popupPanel);
+ this.firePopupHidden(last.popupPanel, last.popupOriginator);
+ }
+
+ /**
+ * Hides all popup panels based on the specified component. We find the
+ * first ancestor of the specified component that is popup panel, and close
+ * all popup panels that were open from that popup panel. If the specified
+ * component is <code>null</code>, all popup panels are closed.
+ *
+ * @param comp
+ * Component.
+ */
+ public void hidePopups(Component comp) {
+ // System.out.println("Hiding all popups");
+ // try {
+ // throw new Exception();
+ // }
+ // catch (Exception exc) {
+ // exc.printStackTrace(System.out);
+ // System.out.println("At " + System.currentTimeMillis() + "\n");
+ // }
+ boolean foundAndDismissed = false;
+ if (comp != null) {
+ Component c = comp;
+ // find JPopupGallery parent of the component
+ while (c != null) {
+ if (c instanceof JPopupPanel) {
+ foundAndDismissed = true;
+ // And close all popups that were opened
+ // from the found popup panel
+ while (shownPath.size() > 0) {
+ if (shownPath.getLast().popupPanel == c)
+ return;
+ PopupInfo last = shownPath.removeLast();
+ Popup popup = popupPanels.get(last.popupPanel);
+ popup.hide();
+ if (last.popupOriginator instanceof JCommandButton) {
+ ((JCommandButton) last.popupOriginator)
+ .getPopupModel().setPopupShowing(false);
+ }
+ this.firePopupHidden(last.popupPanel,
+ last.popupOriginator);
+ popupPanels.remove(last.popupPanel);
+ }
+ }
+ c = c.getParent();
+ }
+ }
+ if (!foundAndDismissed || (comp == null)) {
+ while (shownPath.size() > 0) {
+ PopupInfo last = shownPath.removeLast();
+ Popup popup = popupPanels.get(last.popupPanel);
+ popup.hide();
+ if (last.popupOriginator instanceof JCommandButton) {
+ ((JCommandButton) last.popupOriginator).getPopupModel()
+ .setPopupShowing(false);
+ }
+ this.firePopupHidden(last.popupPanel, last.popupOriginator);
+ popupPanels.remove(last.popupPanel);
+ }
+ }
+ }
+
+ /**
+ * Returns all currently shown popup panels.
+ *
+ * @return All currently shown popup panels.
+ */
+ public List<PopupInfo> getShownPath() {
+ List<PopupInfo> toReturn = new ArrayList<PopupInfo>();
+ for (PopupInfo pInfo : this.shownPath)
+ toReturn.add(pInfo);
+ return toReturn;
+ }
+
+ /**
+ * Adds the specified popup listener.
+ *
+ * @param l
+ * Listener to add.
+ */
+ public void addPopupListener(PopupListener l) {
+ this.listenerList.add(PopupListener.class, l);
+ }
+
+ /**
+ * Removes the specified popup listener.
+ *
+ * @param l
+ * Listener to remove.
+ */
+ public void removePopupListener(PopupListener l) {
+ this.listenerList.remove(PopupListener.class, l);
+ }
+
+ /**
+ * Fires an event on showing the specified popup panel.
+ *
+ * @param panel
+ * Popup panel that was shown.
+ * @param popupOriginator
+ * The originating component.
+ */
+ protected void firePopupShown(JPopupPanel panel, JComponent popupOriginator) {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ PopupEvent popupEvent = new PopupEvent(panel, PopupEvent.POPUP_SHOWN,
+ popupOriginator);
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == PopupListener.class) {
+ ((PopupListener) listeners[i + 1]).popupShown(popupEvent);
+ }
+ }
+ }
+
+ /**
+ * Fires an event on hiding the specified popup panel.
+ *
+ * @param panel
+ * Popup panel that was hidden.
+ * @param popupOriginator
+ * The originating component.
+ */
+ protected void firePopupHidden(JPopupPanel panel, JComponent popupOriginator) {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ PopupEvent popupEvent = new PopupEvent(panel, PopupEvent.POPUP_HIDDEN,
+ popupOriginator);
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == PopupListener.class) {
+ ((PopupListener) listeners[i + 1]).popupHidden(popupEvent);
+ }
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/AbstractRibbonBand.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/AbstractRibbonBand.java
new file mode 100644
index 0000000..47b88ed
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/AbstractRibbonBand.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.awt.event.ActionListener;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.JComponent;
+import javax.swing.UIManager;
+
+import org.pushingpixels.flamingo.api.common.RichTooltip;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.ribbon.resize.*;
+import org.pushingpixels.flamingo.internal.ui.ribbon.*;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+/**
+ * Ribbon band. Is part of a logical {@link RibbonTask}. This is an abstract
+ * base class for two types of ribbon bands - flow in {@link JFlowRibbonBand}
+ * and general in {@link JRibbonBand}.
+ *
+ * <p>
+ * This class provides the following common functionality:
+ * </p>
+ * <ul>
+ * <li>Tracking the available and current resize policies.</li>
+ * <li>Tracking the collapsed state of the ribbon band - when there is not
+ * enough horizontal space to show this panel under the smallest resize setting
+ * (see {@link RibbonBandResizePolicy} and {@link CoreRibbonResizePolicies}) -
+ * the band content is replaced by one collapsed button. When that button is
+ * activated, the original ribbon band content is shown in a popup panel.</li>
+ * <li>Associating key tip and rich tooltip with the expand button of the ribbon
+ * band.</li>
+ * <li>Associating key tip with the collapsed button of the ribbon band.</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @param <T>
+ * Class parameter that specifies the type of band control panel
+ * implementation.
+ */
+public abstract class AbstractRibbonBand<T extends AbstractBandControlPanel>
+ extends JComponent {
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "RibbonBandUI";
+
+ /**
+ * The ribbon task of this ribbon band.
+ */
+ RibbonTask ribbonTask;
+
+ /**
+ * Band title.
+ *
+ * @see #getTitle()
+ * @see #setTitle(String)
+ */
+ private String title;
+
+ /**
+ * Optional <code>expand</code> action listener. If present, the title pane
+ * shows button with plus sign. The action listener on the button will be
+ * <code>this</code> listener.
+ *
+ * @see #getExpandActionListener()
+ * @see #AbstractRibbonBand(String, ResizableIcon, ActionListener,
+ * AbstractBandControlPanel)
+ */
+ private ActionListener expandActionListener;
+
+ /**
+ * Band control panel. When there is not enough horizontal space to show
+ * this panel under the smallest resize setting, the control panel is hidden
+ * and a collapsed button is shown. When this collapsed button is activated,
+ * it shows the {@link #popupRibbonBand} in a popup panel. The collapsed
+ * button itself is implemented as a part of the UI delegate in
+ * {@link BasicRibbonBandUI}.
+ *
+ * @see #popupRibbonBand
+ * @see #icon
+ */
+ protected T controlPanel;
+
+ /**
+ * Ribbon band shown in a popup panel when this ribbon band is in a
+ * collapsed state.
+ *
+ * @see #controlPanel
+ * @see #getPopupRibbonBand()
+ * @see #setPopupRibbonBand(AbstractRibbonBand)
+ */
+ private AbstractRibbonBand popupRibbonBand;
+
+ /**
+ * Icon for the collapsed state. Is set on the button that represents the
+ * collapsed state of this band. The collapsed button itself is implemented
+ * as a part of the UI delegate in {@link BasicRibbonBandUI}.
+ *
+ * @see #getIcon()
+ * @see #setIcon(ResizableIcon)
+ */
+ private ResizableIcon icon;
+
+ /**
+ * The current resize policy for this band. Must be one of the policies in
+ * the {@link #resizePolicies} list.
+ *
+ * @see #resizePolicies
+ * @see #setCurrentResizePolicy(RibbonBandResizePolicy)
+ * @see #getCurrentResizePolicy()
+ */
+ private RibbonBandResizePolicy currResizePolicy;
+
+ /**
+ * The list of available resize policies.
+ *
+ * @see #currResizePolicy
+ * @see #setResizePolicies(List)
+ * @see #getResizePolicies()
+ * @see #getCurrentResizePolicy()
+ */
+ protected List<RibbonBandResizePolicy> resizePolicies;
+
+ /**
+ * The key tip for the ribbon band expand button. Is relevant only when
+ * {@link #expandActionListener} is not <code>null</code>.
+ *
+ * @see #setExpandButtonKeyTip(String)
+ * @see #getExpandButtonKeyTip()
+ */
+ private String expandButtonKeyTip;
+
+ /**
+ * The rich tooltip for the ribbon band expand button. Is relevant only when
+ * {@link #expandActionListener} is not <code>null</code>.
+ *
+ * @see #setExpandButtonRichTooltip(RichTooltip)
+ * @see #getExpandButtonRichTooltip()
+ */
+ private RichTooltip expandButtonRichTooltip;
+
+ /**
+ * The key tip for the collapsed button which is shown when there is not
+ * enough horizontal space to show the ribbon band content under the most
+ * restrictive resize policy. The collapsed button itself is implemented as
+ * a part of the UI delegate in {@link BasicRibbonBandUI}.
+ *
+ * @see #setCollapsedStateKeyTip(String)
+ * @see #getCollapsedStateKeyTip()
+ */
+ private String collapsedStateKeyTip;
+
+ /**
+ * Creates a new ribbon band.
+ *
+ * @param title
+ * Band title.
+ * @param icon
+ * Associated icon (for collapsed state).
+ * @param expandActionListener
+ * Expand action listener (can be <code>null</code>).
+ * @param controlPanel
+ * The control panel of this ribbon band.
+ */
+ public AbstractRibbonBand(String title, ResizableIcon icon,
+ ActionListener expandActionListener, T controlPanel) {
+ super();
+ this.title = title;
+ this.icon = icon;
+ this.expandActionListener = expandActionListener;
+
+ this.controlPanel = controlPanel;
+ this.controlPanel.setRibbonBand(this);
+ this.add(this.controlPanel);
+
+ updateUI();
+ }
+
+ /**
+ * Returns a clone of this ribbon band.
+ *
+ * @return A clone of this ribbon band.
+ */
+ public abstract AbstractRibbonBand<T> cloneBand();
+
+ /**
+ * Returns the UI object which implements the L&F for this component.
+ *
+ * @return a <code>RibbonBandUI</code> object
+ * @see #setUI(RibbonBandUI)
+ */
+ public RibbonBandUI getUI() {
+ return (RibbonBandUI) ui;
+ }
+
+ /**
+ * Sets the new UI delegate.
+ *
+ * @param ui
+ * New UI delegate.
+ */
+ public void setUI(RibbonBandUI ui) {
+ super.setUI(ui);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((RibbonBandUI) UIManager.getUI(this));
+ } else {
+ setUI(new BasicRibbonBandUI());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /**
+ * Returns the icon for the collapsed state.
+ *
+ * @return The icon for the collapsed state.
+ * @see #AbstractRibbonBand(String, ResizableIcon, ActionListener,
+ * AbstractBandControlPanel)
+ */
+ public ResizableIcon getIcon() {
+ return this.icon;
+ }
+
+ /**
+ * Changes the icon for the collapsed state of this ribbon band. Fires a <code>icon</code>
+ * property change event.
+ *
+ * @param icon
+ * The new icon for the collapsed state.
+ * @see #AbstractRibbonBand(String, ResizableIcon, ActionListener,
+ * AbstractBandControlPanel)
+ * @see #getIcon()
+ */
+ public void setIcon(ResizableIcon icon) {
+ ResizableIcon old = this.icon;
+ this.icon = icon;
+ this.firePropertyChange("icon", old, this.icon);
+ }
+
+ /**
+ * Returns the title of <code>this</code> band.
+ *
+ * @return Title of <code>this</code> band.
+ * @see #setTitle(String)
+ */
+ public String getTitle() {
+ return this.title;
+ }
+
+ /**
+ * Changes the title of this ribbon band. Fires a <code>title</code>
+ * property change event.
+ *
+ * @param title
+ * The new title for this ribbon band.
+ * @see #AbstractRibbonBand(String, ResizableIcon, ActionListener,
+ * AbstractBandControlPanel)
+ * @see #getTitle()
+ */
+ public void setTitle(String title) {
+ String old = this.title;
+ this.title = title;
+ this.firePropertyChange("title", old, this.title);
+ }
+
+ /**
+ * Returns the expand action listener of <code>this</code> ribbon band. The
+ * result may be <code>null</code>.
+ *
+ * @return Expand action listener of <code>this</code> ribbon band.
+ * @see #AbstractRibbonBand(String, ResizableIcon, ActionListener,
+ * AbstractBandControlPanel)
+ * @see #setExpandActionListener(ActionListener)
+ */
+ public ActionListener getExpandActionListener() {
+ return this.expandActionListener;
+ }
+
+ /**
+ * Sets the specified action listener to be activated when the user clicks
+ * the expand button on this ribbon band. Passing <code>null</code> will
+ * remove the expand button from this ribbon band.
+ *
+ * @param expandActionListener
+ * Expand action listener for this ribbon band.
+ * @see #getExpandActionListener()
+ */
+ public void setExpandActionListener(ActionListener expandActionListener) {
+ ActionListener old = this.expandActionListener;
+ this.expandActionListener = expandActionListener;
+ this.firePropertyChange("expandActionListener", old,
+ this.expandActionListener);
+ }
+
+ /**
+ * Returns the control panel of <code>this</code> ribbon band. The result
+ * may be <code>null</code>.
+ *
+ * @return Control panel of <code>this</code> ribbon band.
+ * @see #AbstractRibbonBand(String, ResizableIcon, ActionListener,
+ * AbstractBandControlPanel)
+ * @see #setControlPanel(AbstractBandControlPanel)
+ */
+ public T getControlPanel() {
+ return this.controlPanel;
+ }
+
+ /**
+ * Sets the control panel of <code>this</code> ribbon band. The parameter
+ * may be <code>null</code>. This method is for internal use only.
+ *
+ * @param controlPanel
+ * The new control panel for <code>this</code> ribbon band. May
+ * be <code>null</code>.
+ * @see #AbstractRibbonBand(String, ResizableIcon, ActionListener,
+ * AbstractBandControlPanel)
+ * @see #getControlPanel()
+ */
+ public void setControlPanel(T controlPanel) {
+ if (controlPanel == null) {
+ this.remove(this.controlPanel);
+ } else {
+ this.add(controlPanel);
+ controlPanel.applyComponentOrientation(this
+ .getComponentOrientation());
+ }
+ this.controlPanel = controlPanel;
+ }
+
+ /**
+ * Returns the ribbon band shown in a popup panel when this ribbon band is
+ * in a collapsed state. This method is for internal use only and should not
+ * be called by the application code.
+ *
+ * @return The ribbon band shown in a popup panel when this ribbon band is
+ * in a collapsed state.
+ * @see #setPopupRibbonBand(AbstractRibbonBand)
+ */
+ public AbstractRibbonBand getPopupRibbonBand() {
+ return this.popupRibbonBand;
+ }
+
+ /**
+ * Sets the specified parameter to be the ribbon band shown in a popup panel
+ * when this ribbon band is in a collapsed state. This method is for
+ * internal use only and should not be called by the application code.
+ *
+ * @param popupRibbonBand
+ * The ribbon band to be shown in a popup panel when this ribbon
+ * band is in a collapsed state.
+ */
+ public void setPopupRibbonBand(AbstractRibbonBand popupRibbonBand) {
+ this.popupRibbonBand = popupRibbonBand;
+ if (this.popupRibbonBand != null) {
+ popupRibbonBand.applyComponentOrientation(this
+ .getComponentOrientation());
+ }
+ }
+
+ /**
+ * Returns the current resize policy of this ribbon band.
+ *
+ * @return The current resize policy of this ribbon band.
+ */
+ public RibbonBandResizePolicy getCurrentResizePolicy() {
+ return currResizePolicy;
+ }
+
+ /**
+ * Sets the specified parameter to be the current resize policy of this
+ * ribbon band. This method is for internal use only and should not be
+ * called by the application code.
+ *
+ * @param resizePolicy
+ * The new resize policy for this ribbon band.
+ * @see #getCurrentResizePolicy()
+ * @see #getResizePolicies()
+ */
+ public void setCurrentResizePolicy(RibbonBandResizePolicy resizePolicy) {
+ this.currResizePolicy = resizePolicy;
+ }
+
+ /**
+ * Returns an unmodifiable list of available resize policies of this ribbon
+ * band.
+ *
+ * @return An unmodifiable list of available resize policies of this ribbon
+ * band.
+ */
+ public List<RibbonBandResizePolicy> getResizePolicies() {
+ return Collections.unmodifiableList(this.resizePolicies);
+ }
+
+ /**
+ * Sets the specified parameter as the available resize policies of this
+ * ribbon band. The order of the resize policies in this list is important.
+ * The first entry in the list must be the most permissive policies that
+ * returns the largest value from its
+ * {@link RibbonBandResizePolicy#getPreferredWidth(int, int)}. Each
+ * successive entry in the list must return the value smaller than its
+ * predecessors. If {@link IconRibbonBandResizePolicy} is in the list, it
+ * <strong>must</strong> be the last entry.
+ *
+ * @param resizePolicies
+ * The new available resize policies of this ribbon band.
+ */
+ public void setResizePolicies(List<RibbonBandResizePolicy> resizePolicies) {
+ this.resizePolicies = Collections.unmodifiableList(resizePolicies);
+ if (this.ribbonTask != null) {
+ FlamingoUtilities.checkResizePoliciesConsistency(this);
+ }
+ }
+
+ /**
+ * Returns the key tip for the expand button of this ribbon band.
+ *
+ * @return The key tip for the expand button of this ribbon band.
+ * @see #setExpandButtonKeyTip(String)
+ */
+ public String getExpandButtonKeyTip() {
+ return this.expandButtonKeyTip;
+ }
+
+ /**
+ * Changes the key tip for the expand button of this ribbon band. Fires an
+ * <code>expandButtonKeyTip</code> property change event.
+ *
+ * @param expandButtonKeyTip
+ * The new key tip for the expand button of this ribbon band.
+ * @see #getExpandButtonKeyTip()
+ */
+ public void setExpandButtonKeyTip(String expandButtonKeyTip) {
+ String old = this.expandButtonKeyTip;
+ this.expandButtonKeyTip = expandButtonKeyTip;
+ this.firePropertyChange("expandButtonKeyTip", old,
+ this.expandButtonKeyTip);
+ }
+
+ /**
+ * Returns the rich tooltip for the expand button of this ribbon band.
+ *
+ * @return The rich tooltip for the expand button of this ribbon band.
+ * @see #setExpandButtonRichTooltip(RichTooltip)
+ */
+ public RichTooltip getExpandButtonRichTooltip() {
+ return this.expandButtonRichTooltip;
+ }
+
+ /**
+ * Changes the rich tooltip for the expand button of this ribbon band. Fires
+ * an <code>expandButtonRichTooltip</code> property change event.
+ *
+ * @param expandButtonRichTooltip
+ * The new rich tooltip for the expand button of this ribbon
+ * band.
+ * @see #getExpandButtonRichTooltip()
+ */
+ public void setExpandButtonRichTooltip(RichTooltip expandButtonRichTooltip) {
+ RichTooltip old = this.expandButtonRichTooltip;
+ this.expandButtonRichTooltip = expandButtonRichTooltip;
+ this.firePropertyChange("expandButtonRichTooltip", old,
+ this.expandButtonRichTooltip);
+ }
+
+ /**
+ * Returns the key tip for the collapsed button which is shown when there is
+ * not enough horizontal space to show the ribbon band content under the
+ * most restrictive resize policy.
+ *
+ * @return The key tip for the collapsed button of this ribbon band.
+ * @see #setCollapsedStateKeyTip(String)
+ */
+ public String getCollapsedStateKeyTip() {
+ return this.collapsedStateKeyTip;
+ }
+
+ /**
+ * Changes the key tip for the collapsed button which is shown when there is
+ * not enough horizontal space to show the ribbon band content under the
+ * most restrictive resize policy. Fires a <code>collapsedStateKeyTip</code>
+ * property change event.
+ *
+ * @param collapsedStateKeyTip
+ * The new key tip for the collapsed button of this ribbon band.
+ * @see #getCollapsedStateKeyTip()
+ */
+ public void setCollapsedStateKeyTip(String collapsedStateKeyTip) {
+ String old = this.collapsedStateKeyTip;
+ this.collapsedStateKeyTip = collapsedStateKeyTip;
+ this.firePropertyChange("collapsedStateKeyTip", old,
+ this.collapsedStateKeyTip);
+ }
+
+ /**
+ * Associates this ribbon band with the specified ribbon task.
+ *
+ * @param ribbonTask
+ * Ribbon task.
+ * @throws IllegalArgumentException
+ * When this ribbon band has already been associated with a
+ * ribbon task.
+ */
+ void setRibbonTask(RibbonTask ribbonTask) {
+ if (this.ribbonTask != null) {
+ throw new IllegalArgumentException(
+ "Ribbon band cannot be added to more than one ribbon task");
+ }
+ this.ribbonTask = ribbonTask;
+ FlamingoUtilities.checkResizePoliciesConsistency(this);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JFlowRibbonBand.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JFlowRibbonBand.java
new file mode 100644
index 0000000..600be47
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JFlowRibbonBand.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.awt.event.ActionListener;
+
+import javax.swing.JComponent;
+
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.ribbon.resize.CoreRibbonResizePolicies;
+import org.pushingpixels.flamingo.internal.ui.ribbon.JFlowBandControlPanel;
+
+/**
+ * Flow ribbon band component. Hosts components added with
+ * {@link #addFlowComponent(JComponent)} in flowing rows. Depending on the
+ * current resize policy, the content is shown in either two or three rows.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JFlowRibbonBand extends AbstractRibbonBand<JFlowBandControlPanel> {
+ /**
+ * Creates a new flow ribbon band.
+ *
+ * @param title
+ * Band title.
+ * @param icon
+ * Associated icon (for collapsed state).
+ */
+ public JFlowRibbonBand(String title, ResizableIcon icon) {
+ this(title, icon, null);
+ }
+
+ /**
+ * Creates a new flow ribbon band.
+ *
+ * @param title
+ * Band title.
+ * @param icon
+ * Associated icon (for collapsed state).
+ * @param expandActionListener
+ * Expand action listener (can be <code>null</code>).
+ */
+ public JFlowRibbonBand(String title, ResizableIcon icon,
+ ActionListener expandActionListener) {
+ super(title, icon, expandActionListener, new JFlowBandControlPanel());
+ this.resizePolicies = CoreRibbonResizePolicies
+ .getCoreFlowPoliciesRestrictive(this, 3);
+ updateUI();
+ }
+
+ /**
+ * Adds the specified component to this flow ribbon band.
+ *
+ * @param comp
+ * Component to add.
+ */
+ public void addFlowComponent(JComponent comp) {
+ this.controlPanel.addFlowComponent(comp);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.AbstractRibbonBand#cloneBand()
+ */
+ @Override
+ public AbstractRibbonBand<JFlowBandControlPanel> cloneBand() {
+ AbstractRibbonBand<JFlowBandControlPanel> result = new JFlowRibbonBand(
+ this.getTitle(), this.getIcon(), this.getExpandActionListener());
+ result.applyComponentOrientation(this.getComponentOrientation());
+ return result;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JRibbon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JRibbon.java
new file mode 100755
index 0000000..18d903c
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JRibbon.java
@@ -0,0 +1,1057 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.awt.Component;
+import java.awt.event.ActionListener;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonUI;
+import org.pushingpixels.flamingo.internal.ui.ribbon.RibbonUI;
+
+/**
+ * The ribbon component.
+ *
+ * <p>
+ * The ribbon has the following major parts:
+ * </p>
+ * <ul>
+ * <li>Ribbon tasks added with {@link #addTask(RibbonTask)}</li>
+ * <li>Contextual ribbon task groups added with
+ * {@link #addContextualTaskGroup(RibbonContextualTaskGroup)}</li>
+ * <li>Application menu button set by
+ * {@link #setApplicationMenu(RibbonApplicationMenu)}</li>
+ * <li>Taskbar panel populated by {@link #addTaskbarComponent(Component)}</li>
+ * <li>Help button set by {@link #configureHelp(ResizableIcon, ActionListener)}</li>
+ * </ul>
+ *
+ * <p>
+ * While multiple ribbon tasks can be added to the ribbon, only one is visible
+ * at any given time. This task is called the <strong>selected</strong> task.
+ * Tasks can be switched with the task buttons placed along the top part of the
+ * ribbon. Once a task has been added to the ribbon, it cannot be removed.
+ * </p>
+ *
+ * <p>
+ * The contextual ribbon task groups allow showing and hiding ribbon tasks based
+ * on the current selection in the application. For example, Word only shows the
+ * table tasks when a table is selected in the document. By default, tasks
+ * belonging to the groups adde by
+ * {@link #addContextualTaskGroup(RibbonContextualTaskGroup)} are not visible.
+ * To show the tasks belonging to the specific group, call
+ * {@link #setVisible(RibbonContextualTaskGroup, boolean)} API. Note that you
+ * can have multiple task groups visible at the same time.
+ * </p>
+ *
+ * <p>
+ * The application menu button is a big round button shown in the top left
+ * corner of the ribbon. If the
+ * {@link #setApplicationMenu(RibbonApplicationMenu)} is not called, or called
+ * with the <code>null</code> value, the application menu button is not shown,
+ * and ribbon task buttons are shifted to the left.
+ * </p>
+ *
+ * <p>
+ * The taskbar panel allows showing controls that are visible no matter what
+ * ribbon task is selected. To add a taskbar component use the
+ * {@link #addTaskbarComponent(Component)} API. The taskbar panel lives to the
+ * right of the application menu button. Taskbar components can be removed with
+ * the {@link #removeTaskbarComponent(Component)} API.
+ * </p>
+ *
+ * <p>
+ * The ribbon can be minimized in one of the following ways:
+ * </p>
+ * <ul>
+ * <li>Calling {@link #setMinimized(boolean)} with <code>true</code>.</li>
+ * <li>User double-clicking on a task button.</li>
+ * <li>User pressing <code>Ctrl+F1</code> key combination.</li>
+ * </ul>
+ *
+ * <p>
+ * A minimized ribbon shows the application menu button, taskbar panel, task
+ * buttons and help button, but not the ribbon bands of the selected task.
+ * Clicking a task button shows the ribbon bands of that task in a popup
+ * <strong>without</strong> shifting the application content down.
+ * </p>
+ *
+ * @author Kirill Grouchnikov
+ */
+ at SuppressWarnings("serial")
+public class JRibbon extends JComponent {
+
+ /**
+ * The property string used when the {@link #applicationIcon} changes. The
+ * value is {@value #PROPERTY_APPLICATION_ICON}.
+ */
+ public static final String PROPERTY_APPLICATION_ICON = "ribbon.icon";
+
+ /**
+ * The property string used when the {@link #applicationMenuRichTooltip}
+ * changes. The value is {@value #PROPERTY_APPLICATION_MENU_RICH_TOOLTIP}.
+ */
+ public static final String PROPERTY_APPLICATION_MENU_RICH_TOOLTIP = "applicationMenuRichTooltip";
+
+ /**
+ * The property string used when the {@link #applicationMenu} changes. The
+ * value is {@value #PROPERTY_APPLICATION_MENU}.
+ */
+ public static final String PROPERTY_APPLICATION_MENU = "applicationMenu";
+
+ /**
+ * The property string used when the {@link #applicationMenuKeyTip} changes.
+ * The value is {@value #PROPERTY_APPLICATION_MENU_KEY_TIP}.
+ */
+ public static final String PROPERTY_APPLICATION_MENU_KEY_TIP = "applicationMenuKeyTip";
+
+ /**
+ * The property string used when the {@link #currentlySelectedTask} changes.
+ * The value is {@value #PROPERTY_SELECTED_TASK}.
+ */
+ public static final String PROPERTY_SELECTED_TASK = "selectedTask";
+
+ /**
+ * The property string used when the {@link #isMinimized} changes. The value
+ * is {@value #PROPERTY_MINIMIZED}.
+ */
+ public static final String PROPERTY_MINIMIZED = "minimized";
+
+ /**
+ * The general tasks for this ribbon.
+ * <p>
+ * Tasks that get displayed based on a specific context are contextual
+ * tasks. See {@link #contextualTaskGroups} for more information about
+ * contextual tasks.
+ *
+ * @see #addTask(RibbonTask)
+ * @see #getTaskCount()
+ * @see #getTask(int)
+ */
+ private List<RibbonTask> tasks;
+
+ /**
+ * The contextual task groups.
+ *
+ * @see #addContextualTaskGroup(RibbonContextualTaskGroup)
+ * @see #setVisible(RibbonContextualTaskGroup, boolean)
+ * @see #isVisible(RibbonContextualTaskGroup)
+ * @see #getContextualTaskGroupCount()
+ * @see #getContextualTaskGroup(int)
+ */
+ private List<RibbonContextualTaskGroup> contextualTaskGroups;
+
+ /**
+ * The taskbar components (to the right of the application menu button).
+ *
+ * @see #addTaskbarComponent(Component)
+ * @see #getTaskbarComponents()
+ * @see #removeTaskbarComponent(Component)
+ */
+ private List<Component> taskbarComponents;
+
+ /**
+ * Currently selected (shown) task.
+ */
+ private RibbonTask currentlySelectedTask;
+
+ /**
+ * Help icon. When not <code>null</code>, the ribbon will display a help
+ * button at the far right of the tab area.
+ *
+ * @see #helpActionListener
+ * @see #configureHelp(ResizableIcon, ActionListener)
+ * @see #getHelpIcon()
+ */
+ private ResizableIcon helpIcon;
+
+ /**
+ * When the {@link #helpIcon} is not <code>null</code>, this listener will
+ * be invoked when the user activates the help button.
+ *
+ * @see #configureHelp(ResizableIcon, ActionListener)
+ * @see #getHelpActionListener()
+ */
+ private ActionListener helpActionListener;
+
+ /**
+ * When the {@link #helpIcon} is not <code>null</code>, this rich tooltip
+ * will be shown when the user mouses over the icon.
+ *
+ * @see #setHelpRichTooltip(org.pushingpixels.flamingo.api.common.RichTooltip)
+ * @see #getHelpRichTooltip()
+ */
+ private RichTooltip helpRichTooltip;
+
+ /**
+ * Visibility status of the contextual task group. Must contain a value for
+ * each group in {@link #contextualTaskGroups}.
+ *
+ * @see #setVisible(RibbonContextualTaskGroup, boolean)
+ * @see #isVisible(RibbonContextualTaskGroup)
+ */
+ private Map<RibbonContextualTaskGroup, Boolean> groupVisibilityMap;
+
+ /**
+ * The application menu.
+ *
+ * @see #setApplicationMenu(RibbonApplicationMenu)
+ * @see #getApplicationMenu()
+ */
+ private RibbonApplicationMenu applicationMenu;
+
+ /**
+ * The rich tooltip of {@link #applicationMenu} button.
+ *
+ * @see #applicationMenu
+ * @see #setApplicationMenuRichTooltip(RichTooltip)
+ * @see #getApplicationMenuRichTooltip()
+ */
+ private RichTooltip applicationMenuRichTooltip;
+
+ /**
+ * The key tip of {@link #applicationMenu} button.
+ *
+ * @see #applicationMenu
+ * @see #setApplicationMenuKeyTip(String)
+ * @see #getApplicationMenuKeyTip()
+ */
+ private String applicationMenuKeyTip;
+
+ /**
+ * Indicates whether the ribbon is currently minimized.
+ *
+ * @see #setMinimized(boolean)
+ * @see #isMinimized()
+ */
+ private boolean isMinimized;
+
+ /**
+ * The host ribbon frame. Is <code>null</code> when the ribbon is not hosted
+ * in a {@link JRibbonFrame}.
+ *
+ * @deprecated Dropped support in order to decouple the <code>JRibbon</code>
+ * from the <code>JRibbonFrame</code>
+ */
+ @Deprecated
+ private JRibbonFrame ribbonFrame;
+
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "RibbonUI";
+
+ /**
+ * The application icon. This is displayed in the application menu button.
+ */
+ public ResizableIcon applicationIcon;
+
+ /**
+ * Constructs an empty default <code>JRibbon</code>. Applications are highly
+ * encouraged to use {@link JRibbonFrame} and access the ribbon with
+ * {@link JRibbonFrame#getRibbon()} API.
+ */
+ public JRibbon() {
+ this((ResizableIcon) null);
+ }
+
+ /**
+ * Constructs a <code>JRibbon</code> specifying the application icon. The
+ * application icon is displayed in the application menu button.
+ * Applications are highly encouraged to use {@link JRibbonFrame} and access
+ * the ribbon with {@link JRibbonFrame#getRibbon()} API.
+ *
+ * @param appIcon
+ * the application icon
+ */
+ public JRibbon(ResizableIcon appIcon) {
+ this.tasks = new LinkedList<RibbonTask>();
+ this.contextualTaskGroups = new ArrayList<RibbonContextualTaskGroup>();
+ this.taskbarComponents = new ArrayList<Component>();
+ this.currentlySelectedTask = null;
+ this.groupVisibilityMap = new HashMap<RibbonContextualTaskGroup, Boolean>();
+
+ updateUI();
+ getUI().setApplicationIcon(appIcon);
+ }
+
+ /**
+ * Creates an empty ribbon for the specified ribbon frame.
+ *
+ * @param ribbonFrame
+ * Host ribbon frame.
+ * @deprecated Dropped support in order to decouple the <code>JRibbon</code>
+ * from the <code>JRibbonFrame</code>
+ */
+ @Deprecated
+ JRibbon(JRibbonFrame ribbonFrame) {
+ this();
+ this.ribbonFrame = ribbonFrame;
+ }
+
+ /**
+ * Adds the specified taskbar component to this ribbon.
+ * <p>
+ * Taskbar components are small components placed to the right of the
+ * application menu. These components usually perform an action common among
+ * the entire application.
+ *
+ * @param comp
+ * the taskbar component to add
+ * @see #removeTaskbarComponent(Component)
+ * @see #removeAllTaskbarComponents()
+ * @see #getTaskbarComponents()
+ */
+ public synchronized void addTaskbarComponent(Component comp) {
+ if (comp instanceof AbstractCommandButton) {
+ AbstractCommandButton button = (AbstractCommandButton) comp;
+ button.setDisplayState(CommandButtonDisplayState.SMALL);
+ button.setGapScaleFactor(0.5);
+ button.setFocusable(false);
+ }
+ taskbarComponents.add(comp);
+ fireStateChanged();
+ }
+
+ /*
+ * Added Remove Tasks from patch provided by Jonathan Giles Jan 2009
+ * http://markmail.org/message/vzw3hrntr6qsdlu3
+ */
+
+ /**
+ * Removes the specified taskbar component from this ribbon.
+ *
+ * @param comp
+ * The taskbar component to remove.
+ * @see #addTaskbarComponent(Component)
+ * @see #getTaskbarComponents()
+ * @see #removeAllTaskbarComponents()
+ */
+ public synchronized void removeTaskbarComponent(Component comp) {
+ taskbarComponents.remove(comp);
+ fireStateChanged();
+ }
+
+ /**
+ * Removes all components added to the taskbar of the ribbon.
+ *
+ * @see #addTaskbarComponent(Component)
+ * @see #getTaskbarComponents()
+ * @see #removeTaskbarComponent(Component)
+ */
+ public void removeAllTaskbarComponents() {
+ taskbarComponents.clear();
+ fireStateChanged();
+ }
+
+ /**
+ * Adds the specified task to this ribbon.
+ *
+ * @param task
+ * The ribbon task to add.
+ * @see #addContextualTaskGroup(RibbonContextualTaskGroup)
+ * @see #getTaskCount()
+ * @see #getTask(int)
+ */
+ public synchronized void addTask(RibbonTask task) {
+ task.setRibbon(this);
+
+ tasks.add(task);
+
+ if (tasks.size() == 1) {
+ setSelectedTask(task);
+ }
+
+ fireStateChanged();
+ }
+
+ /**
+ * Removes the task at the specified position, if it represents a valid
+ * task. Throws an {@link IndexOutOfBoundsException} if not.
+ *
+ * @param pos
+ * The position of the task to remove.
+ */
+ public void removeTask(int pos) {
+ if (pos >= getTaskCount()) {
+ throw new IndexOutOfBoundsException("task position '" + pos
+ + "' exceeds number of tasks in ribbon ('" + getTaskCount()
+ + "')");
+ }
+
+ removeTask(getTask(pos));
+ }
+
+ /**
+ * Removes the given task from the ribbon. If this is the currently visible
+ * task, the ribbon will move to the task to its left, unless the removed
+ * task is the left-most, in which case it will move to the next task to the
+ * right.
+ *
+ * @param task
+ * The ribbon task to be removed from the panel.
+ * @exception IllegalArgumentException
+ * if <code>task</code> is <code>null</code>
+ */
+ public void removeTask(RibbonTask task) {
+ if (task == null) {
+ throw new IllegalArgumentException("RibbonTask can not be null");
+ }
+
+ int posOfTask = this.tasks.indexOf(task);
+ this.tasks.remove(task);
+
+ if (getSelectedTask().equals(task) && tasks.size() > 0) {
+ RibbonTask newTask = getTask(posOfTask == 0 ? 1 : posOfTask - 1);
+ setSelectedTask(newTask);
+ }
+
+ this.fireStateChanged();
+ }
+
+ /**
+ * Removes all tasks from the ribbon.
+ */
+ public void removeAllTasks() {
+ this.tasks.clear();
+ this.contextualTaskGroups.clear();
+ this.fireStateChanged();
+ }
+
+ /**
+ * Configures the help button of this ribbon.
+ *
+ * @param helpIcon
+ * The icon for the help button.
+ * @param helpActionListener
+ * The action listener for the help button.
+ * @see #getHelpIcon()
+ * @see #getHelpActionListener()
+ */
+ public synchronized void configureHelp(ResizableIcon helpIcon,
+ ActionListener helpActionListener) {
+ this.helpIcon = helpIcon;
+ this.helpActionListener = helpActionListener;
+ this.fireStateChanged();
+ }
+
+ /**
+ * Returns the icon for the help button. Will return <code>null</code> if
+ * the help button has not been configured with the
+ * {@link #configureHelp(ResizableIcon, ActionListener)} API.
+ *
+ * @return The icon for the help button.
+ * @see #configureHelp(ResizableIcon, ActionListener)
+ * @see #getHelpActionListener()
+ */
+ public ResizableIcon getHelpIcon() {
+ return this.helpIcon;
+ }
+
+ /**
+ * Returns the action listener for the help button. Will return
+ * <code>null</code> if the help button has not been configured with the
+ * {@link #configureHelp(ResizableIcon, ActionListener)} API.
+ *
+ * @return The action listener for the help button.
+ * @see #configureHelp(ResizableIcon, ActionListener)
+ * @see #getHelpIcon()
+ */
+ public ActionListener getHelpActionListener() {
+ return this.helpActionListener;
+ }
+
+ /**
+ * Sets the rich tooltip of the help button. Fires an
+ * stateChanged event.
+ *
+ * @param tooltip
+ * The rich tooltip of the help button.
+ * @see #getHelpRichTooltip()
+ * @see #configureHelp(org.pushingpixels.flamingo.api.common.icon.ResizableIcon, java.awt.event.ActionListener)
+ */
+ public synchronized void setHelpRichTooltip(RichTooltip tooltip) {
+ RichTooltip old = this.helpRichTooltip;
+ this.helpRichTooltip = tooltip;
+ this.fireStateChanged();
+ }
+
+ /**
+ * Returns the rich tooltip of the help button.
+ *
+ * @return The rich tooltip of the help button.
+ * @see #setHelpRichTooltip(org.pushingpixels.flamingo.api.common.RichTooltip)
+ * @see #configureHelp(org.pushingpixels.flamingo.api.common.icon.ResizableIcon, java.awt.event.ActionListener)
+ */
+ public synchronized RichTooltip getHelpRichTooltip() {
+ return this.helpRichTooltip;
+ }
+ /**
+ * Adds a component to the 'Help Panel.' This is the area where the
+ * help button lives. and is the far right area of the main tab area.
+ *
+ * Components will be added in left to right fashion, Also, if a
+ * help listener is specified then the help button will be the rightmost
+ * component on the list.
+ *
+ * Generally speaking this area should not be abused, as any large amount
+ * of components will cause the space available for the task tabs to shrink.
+ *
+ * This is the area where you would add a "collapse" button like found in
+ * Office 2010, or the min/max/close buttons of an integrated desktop area.
+ *
+ * @param comp the component to be added
+ */
+ public void addHelpPanelComponent(Component comp) {
+ if (comp == null) return;
+
+ try {
+ List<Component> existingHelpPanelComponents = (List<Component>) getClientProperty(BasicRibbonUI.HELP_PANEL_COMPONENTS);
+ if (existingHelpPanelComponents != null) {
+ if (!existingHelpPanelComponents.contains(comp)) {
+ existingHelpPanelComponents.add(comp);
+ }
+ } else {
+ List<Component> helpComps = new ArrayList<Component>();
+ helpComps.add(comp);
+ putClientProperty(BasicRibbonUI.HELP_PANEL_COMPONENTS, helpComps);
+ }
+ } catch (RuntimeException re) {
+ //re-write on any error
+ List<Component> helpComps = new ArrayList<Component>();
+ helpComps.add(comp);
+ putClientProperty(BasicRibbonUI.HELP_PANEL_COMPONENTS, helpComps);
+ }
+ fireStateChanged();
+ }
+
+ /**
+ * Removes a component from the 'Help Panel'.
+ *
+ * @param comp The component to remove. If the component is not currently
+ * on the help panel this call will be a no-op.
+ */
+ public void removeHelpPanelComponent(Component comp) {
+ try {
+ List<Component> existingHelpPanelComponents = (List<Component>) getClientProperty(BasicRibbonUI.HELP_PANEL_COMPONENTS);
+ if (existingHelpPanelComponents != null) {
+ if (existingHelpPanelComponents.remove(comp)) {
+ fireStateChanged();
+ }
+ }
+ } catch (RuntimeException ignore) {
+ }
+ }
+
+ /**
+ * Removes al the components from the 'Help Panel'.
+ */
+ public void removeAllHelpPanelComponents() {
+ try {
+ List<Component> existingHelpPanelComponents = (List<Component>) getClientProperty(BasicRibbonUI.HELP_PANEL_COMPONENTS);
+ if (existingHelpPanelComponents != null) {
+ existingHelpPanelComponents.clear();
+ fireStateChanged();
+ }
+ } catch (RuntimeException ignore) {
+ }
+ }
+
+ /**
+ * Adds the specified contextual task group to this ribbon.
+ *
+ * @param group
+ * Task group to add.
+ * @see #addTask(RibbonTask)
+ * @see #setVisible(RibbonContextualTaskGroup, boolean)
+ * @see #isVisible(RibbonContextualTaskGroup)
+ */
+ public synchronized void addContextualTaskGroup(
+ RibbonContextualTaskGroup group) {
+ group.setRibbon(this);
+
+ this.contextualTaskGroups.add(group);
+ this.groupVisibilityMap.put(group, false);
+
+ this.fireStateChanged();
+ }
+
+ /**
+ * Returns the number of regular tasks in <code>this</code> ribbon. This
+ * does not include the contextual ribbon tasks.
+ * <p>
+ * To find the total number of ribbon tasks (including contextual ribbon
+ * tasks) you will have to iterate through the contextual task groups.
+ *
+ * @return Number of regular tasks in <code>this</code> ribbon.
+ * @see #getTask(int)
+ * @see #addTask(RibbonTask)
+ */
+ public synchronized int getTaskCount() {
+ return this.tasks.size();
+ }
+
+ /**
+ * Retrieves the regular task at specified index.
+ *
+ * @param index
+ * task index
+ * @return the task that matches the specified index
+ * @see #getTaskCount()
+ * @see #addTask(RibbonTask)
+ */
+ public synchronized RibbonTask getTask(int index) {
+ return this.tasks.get(index);
+ }
+
+ /**
+ * Returns the number of contextual task groups in <code>this</code> ribbon.
+ *
+ * @return number of contextual task groups in <code>this</code> ribbon
+ * @see #addContextualTaskGroup(RibbonContextualTaskGroup)
+ * @see #getContextualTaskGroup(int)
+ */
+ public synchronized int getContextualTaskGroupCount() {
+ return this.contextualTaskGroups.size();
+ }
+
+ /**
+ * Retrieves contextual task group at specified index.
+ *
+ * @param index
+ * group index
+ * @return group that matches the specified index
+ * @see #addContextualTaskGroup(RibbonContextualTaskGroup)
+ * @see #getContextualTaskGroupCount()
+ */
+ public synchronized RibbonContextualTaskGroup getContextualTaskGroup(
+ int index) {
+ return this.contextualTaskGroups.get(index);
+ }
+
+ /**
+ * Selects the specified task. The task can be either regular (added with
+ * {@link #addTask(RibbonTask)}) or a task in a visible contextual task
+ * group (added with
+ * {@link #addContextualTaskGroup(RibbonContextualTaskGroup)}. Fires a
+ * <code>selectedTask</code> property change event.
+ *
+ * @param task
+ * task to select
+ * @throws IllegalArgumentException
+ * if <code>task</code> is not in the ribbon, is
+ * <code>null</code>, or not visible.
+ * @see #getSelectedTask()
+ */
+ public synchronized void setSelectedTask(RibbonTask task) {
+ // check for task in general tasks
+ boolean valid = tasks.contains(task);
+ // if not a general task, then check contextual tasks
+ if (!valid) {
+ for (int i = 0; i < getContextualTaskGroupCount(); i++) {
+ RibbonContextualTaskGroup group = getContextualTaskGroup(i);
+ if (!this.isVisible(group))
+ continue;
+ for (int j = 0; j < group.getTaskCount(); j++) {
+ if (group.getTask(j) == task) {
+ valid = true;
+ break;
+ }
+ }
+ if (valid)
+ break;
+ }
+ }
+ if (!valid) {
+ throw new IllegalArgumentException(
+ "The specified task to be selected is either not "
+ + "part of this ribbon or not marked as visible");
+ }
+
+ if (currentlySelectedTask != null) {
+ for (AbstractRibbonBand<?> ribbonBand : currentlySelectedTask
+ .getBands()) {
+ ribbonBand.setVisible(false);
+ }
+ }
+
+ for (int i = 0; i < task.getBandCount(); i++) {
+ AbstractRibbonBand<?> ribbonBand = task.getBand(i);
+ ribbonBand.setVisible(true);
+ }
+
+ RibbonTask old = currentlySelectedTask;
+ currentlySelectedTask = task;
+
+ revalidate();
+ repaint();
+
+ firePropertyChange(PROPERTY_SELECTED_TASK, old,
+ this.currentlySelectedTask);
+ }
+
+ /**
+ * Returns the currently selected task.
+ *
+ * @return The currently selected task.
+ * @see #setSelectedTask(RibbonTask)
+ */
+ public synchronized RibbonTask getSelectedTask() {
+ return this.currentlySelectedTask;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI(UIManager.getUI(this));
+ } else {
+ setUI(new BasicRibbonUI());
+ }
+ for (Component comp : this.taskbarComponents) {
+ SwingUtilities.updateComponentTreeUI(comp);
+ }
+ }
+
+ /**
+ * Returns the UI object which implements the L&F for this component.
+ *
+ * @return a <code>RibbonUI</code> object
+ * @see #setUI(javax.swing.plaf.ComponentUI)
+ */
+ public RibbonUI getUI() {
+ return (RibbonUI) ui;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /**
+ * Gets an unmodifiable list of all taskbar components of <code>this</code>
+ * ribbon.
+ *
+ * @return All taskbar components of <code>this</code> ribbon.
+ * @see #addTaskbarComponent(Component)
+ * @see #removeTaskbarComponent(Component)
+ */
+ public synchronized List<Component> getTaskbarComponents() {
+ return Collections.unmodifiableList(this.taskbarComponents);
+ }
+
+ /**
+ * Adds the specified change listener to track changes to this ribbon.
+ *
+ * @param l
+ * Change listener to add.
+ * @see #removeChangeListener(ChangeListener)
+ */
+ public void addChangeListener(ChangeListener l) {
+ this.listenerList.add(ChangeListener.class, l);
+ }
+
+ /**
+ * Removes the specified change listener from tracking changes to this
+ * ribbon.
+ *
+ * @param l
+ * Change listener to remove.
+ * @see #addChangeListener(ChangeListener)
+ */
+ public void removeChangeListener(ChangeListener l) {
+ this.listenerList.remove(ChangeListener.class, l);
+ }
+
+ /**
+ * Notifies all registered listeners that the state of this ribbon has
+ * changed.
+ */
+ protected void fireStateChanged() {
+ // Guaranteed to return a non-null array
+ Object[] listeners = this.listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ ChangeEvent event = new ChangeEvent(this);
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ChangeListener.class) {
+ ((ChangeListener) listeners[i + 1]).stateChanged(event);
+ }
+ }
+ }
+
+ /**
+ * Sets the visibility of ribbon tasks in the specified contextual task
+ * group. Visibility of all ribbon tasks in the specified group is affected.
+ * Note that the ribbon can show ribbon tasks of multiple groups at the same
+ * time.
+ *
+ * @param group
+ * Contextual task group.
+ * @param isVisible
+ * If <code>true</code>, all ribbon tasks in the specified group
+ * will be visible. If <code>false</code>, all ribbon tasks in
+ * the specified group will be hidden.
+ * @see #isVisible(RibbonContextualTaskGroup)
+ */
+ public synchronized void setVisible(RibbonContextualTaskGroup group,
+ boolean isVisible) {
+ this.groupVisibilityMap.put(group, isVisible);
+
+ // special handling of selected tab
+ if (!isVisible) {
+ boolean isSelectedBeingHidden = false;
+ for (int i = 0; i < group.getTaskCount(); i++) {
+ if (this.getSelectedTask() == group.getTask(i)) {
+ isSelectedBeingHidden = true;
+ break;
+ }
+ }
+ if (isSelectedBeingHidden) {
+ this.setSelectedTask(this.getTask(0));
+ }
+ }
+
+ this.fireStateChanged();
+ this.revalidate();
+ SwingUtilities.getWindowAncestor(this).repaint();
+ }
+
+ /**
+ * Returns the visibility of ribbon tasks in the specified contextual task
+ * group.
+ *
+ * @param group
+ * Contextual task group.
+ * @return <code>true</code> if the ribbon tasks in the specified group are
+ * visible, <code>false</code> otherwise.
+ */
+ public synchronized boolean isVisible(RibbonContextualTaskGroup group) {
+ return this.groupVisibilityMap.get(group);
+ }
+
+ /**
+ * Sets the application menu for this ribbon. If <code>null</code> is
+ * passed, the application menu button is hidden. Fires an
+ * <code>applicationMenu</code> property change event.
+ *
+ * @param applicationMenu
+ * The new application menu. Can be <code>null</code>.
+ * @see #getApplicationMenu()
+ */
+ public synchronized void setApplicationMenu(
+ RibbonApplicationMenu applicationMenu) {
+ RibbonApplicationMenu old = this.applicationMenu;
+ if (old != applicationMenu) {
+ this.applicationMenu = applicationMenu;
+ if (this.applicationMenu != null) {
+ this.applicationMenu.setFrozen();
+ }
+ this.firePropertyChange(PROPERTY_APPLICATION_MENU, old,
+ this.applicationMenu);
+ }
+ }
+
+ /**
+ * Returns the application menu of this ribbon.
+ *
+ * @return The application menu of this ribbon.
+ * @see #setApplicationMenu(RibbonApplicationMenu)
+ */
+ public synchronized RibbonApplicationMenu getApplicationMenu() {
+ return this.applicationMenu;
+ }
+
+ /**
+ * Sets the rich tooltip of the application menu button. Fires an
+ * <code>applicationMenuRichTooltip</code> property change event.
+ *
+ * @param tooltip
+ * The rich tooltip of the application menu button.
+ * @see #getApplicationMenuRichTooltip()
+ * @see #setApplicationMenu(RibbonApplicationMenu)
+ */
+ public synchronized void setApplicationMenuRichTooltip(RichTooltip tooltip) {
+ RichTooltip old = this.applicationMenuRichTooltip;
+ this.applicationMenuRichTooltip = tooltip;
+ this.firePropertyChange(PROPERTY_APPLICATION_MENU_RICH_TOOLTIP, old,
+ this.applicationMenuRichTooltip);
+ }
+
+ /**
+ * Returns the rich tooltip of the application menu button.
+ *
+ * @return The rich tooltip of the application menu button.
+ * @see #setApplicationMenuRichTooltip(RichTooltip)
+ * @see #setApplicationMenu(RibbonApplicationMenu)
+ */
+ public synchronized RichTooltip getApplicationMenuRichTooltip() {
+ return this.applicationMenuRichTooltip;
+ }
+
+ /**
+ * Sets the key tip of the application menu button. Fires an
+ * <code>applicationMenuKeyTip</code> property change event.
+ *
+ * @param keyTip
+ * The new key tip for the application menu button.
+ * @see #setApplicationMenu(RibbonApplicationMenu)
+ * @see #getApplicationMenuKeyTip()
+ */
+ public synchronized void setApplicationMenuKeyTip(String keyTip) {
+ String old = this.applicationMenuKeyTip;
+ this.applicationMenuKeyTip = keyTip;
+ this.firePropertyChange(PROPERTY_APPLICATION_MENU_KEY_TIP, old,
+ this.applicationMenuKeyTip);
+ }
+
+ /**
+ * Returns the key tip of the application menu button.
+ *
+ * @return The key tip of the application menu button.
+ * @see #setApplicationMenuKeyTip(String)
+ * @see #setApplicationMenu(RibbonApplicationMenu)
+ */
+ public synchronized String getApplicationMenuKeyTip() {
+ return this.applicationMenuKeyTip;
+ }
+
+ /**
+ * Returns the indication whether this ribbon is minimized.
+ *
+ * @return <code>true</code> if this ribbon is minimized, <code>false</code>
+ * otherwise.
+ * @see #setMinimized(boolean)
+ */
+ public synchronized boolean isMinimized() {
+ return this.isMinimized;
+ }
+
+ /**
+ * Changes the minimized state of this ribbon. Fires a
+ * <code>minimized</code> property change event.
+ *
+ * @param isMinimized
+ * if <code>true</code>, this ribbon becomes minimized, otherwise
+ * it is unminimized.
+ */
+ public synchronized void setMinimized(boolean isMinimized) {
+ // System.out.println("Ribbon minimized -> " + isMinimized);
+ boolean old = this.isMinimized;
+ if (old != isMinimized) {
+ this.isMinimized = isMinimized;
+ this.firePropertyChange(PROPERTY_MINIMIZED, old, this.isMinimized);
+ }
+ }
+
+ /**
+ * Returns the ribbon frame that hosts this ribbon. The result can be
+ * <code>null</code>.
+ *
+ * @return The ribbon frame that hosts this ribbon.
+ * @deprecated Dropped support in order to decouple the <code>JRibbon</code>
+ * from the <code>JRibbonFrame</code>
+ */
+ @Deprecated
+ public JRibbonFrame getRibbonFrame() {
+ return this.ribbonFrame;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#setVisible(boolean)
+ */
+ @Override
+ public void setVisible(boolean flag) {
+ if (!flag && (getRibbonFrame() != null))
+ throw new IllegalArgumentException(
+ "Can't hide ribbon on JRibbonFrame");
+ super.setVisible(flag);
+ }
+
+ /**
+ * Returns the application icon. The application icon is displayed on the
+ * application menu button.
+ * <p>
+ * This is a convenience method and is equivalent to
+ * <code>getUI().getApplicationIcon()</code>.
+ *
+ * @see #getUI()
+ * @see RibbonUI#getApplicationIcon()
+ * @see #setApplicationIcon(ResizableIcon)
+ * @return the application icon
+ */
+ public synchronized ResizableIcon getApplicationIcon() {
+ return getUI().getApplicationIcon();
+ }
+
+ /**
+ * Sets the application icon. This is displayed on the application menu
+ * button.
+ * <p>
+ * There is no check performed to see if <code>applicationIcon</code> is
+ * <code>null</code>.
+ * <p>
+ * A <code>PropertyChangeEvent</code> is fired for the
+ * {@link #PROPERTY_APPLICATION_ICON} property.
+ *
+ * @see #getUI()
+ * @see RibbonUI#setApplicationIcon(ResizableIcon)
+ * @see #getApplicationIcon()
+ * @param applicationIcon
+ * the application icon to set
+ */
+ public synchronized void setApplicationIcon(ResizableIcon applicationIcon) {
+ ResizableIcon old = getUI().getApplicationIcon();
+ getUI().setApplicationIcon(applicationIcon);
+ firePropertyChange(PROPERTY_APPLICATION_ICON, old, this.applicationIcon);
+ // TODO set the application menu button icon
+ // JRibbonApplicationMenuButton button = getApplicationMenuButton();
+ // if (button != null) {
+ // button.setIcon(this.applicationIcon);
+ // }
+ }
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JRibbonBand.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JRibbonBand.java
new file mode 100644
index 0000000..1bc28ba
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JRibbonBand.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.awt.event.ActionListener;
+import java.util.*;
+
+import javax.swing.SwingConstants;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.popup.JCommandPopupMenu;
+import org.pushingpixels.flamingo.api.ribbon.resize.CoreRibbonResizePolicies;
+import org.pushingpixels.flamingo.internal.ui.ribbon.*;
+
+/**
+ * Ribbon band component. Can host three types of content:
+ *
+ * <ul>
+ * <li>Command buttons added with
+ * {@link #addCommandButton(AbstractCommandButton, RibbonElementPriority)}.</li>
+ * <li>Wrapped core / 3rd party components added with
+ * {@link #addRibbonComponent(JRibbonComponent)} or
+ * {@link #addRibbonComponent(JRibbonComponent, int)}.</li>
+ * <li>Ribbon galleries added with
+ * {@link #addRibbonGallery(String, List, Map, int, int, RibbonElementPriority)}
+ * .</li>
+ * </ul>
+ *
+ * <p>
+ * Command buttons are added with associated {@link RibbonElementPriority}. The
+ * higher the priority, the longer the button "stays" in the
+ * {@link CommandButtonDisplayState#BIG} or
+ * {@link CommandButtonDisplayState#MEDIUM} state - depending on the available
+ * resize policies.
+ * </p>
+ *
+ * <p>
+ * Wrapped components can span one or multiple rows. Use the
+ * {@link #addRibbonComponent(JRibbonComponent, int)} API to add a wrapped
+ * component that spans more than one row.
+ * </p>
+ *
+ * <p>
+ * Once a ribbon gallery is added with
+ * {@link #addRibbonGallery(String, List, Map, int, int, RibbonElementPriority)}
+ * , you can use the following APIs to configure the content and behavior of
+ * that gallery:
+ * </p>
+ *
+ * <ul>
+ * <li>{@link #addRibbonGalleryButtons(String, String, JCommandToggleButton...)}
+ * </li>
+ * <li>{@link #removeRibbonGalleryButtons(String, JCommandToggleButton...)}</li>
+ * <li>{@link #setSelectedRibbonGalleryButton(String, JCommandToggleButton)}</li>
+ * <li>{@link #setRibbonGalleryExpandKeyTip(String, String)}</li>
+ * <li>
+ * {@link #setRibbonGalleryPopupCallback(String, RibbonGalleryPopupCallback)}</li>
+ * </ul>
+ *
+ * <p>
+ * A ribbon band can have multiple visual groups separated with vertical
+ * separator lines. To start a new unnamed group use the {@link #startGroup()}
+ * API. To start a new named group use the {@link #startGroup(String)} API.
+ * Unnamed groups will have three rows of controls. Named groups will have two
+ * rows of controls, with the top row showing the group title.
+ * </p>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JRibbonBand extends AbstractRibbonBand<JBandControlPanel> {
+ /**
+ * This callback allows application code to place additional menu entries in
+ * the popup menu shown when the ribbon gallery expand button is clicked.
+ * Application code should use
+ * {@link JCommandPopupMenu#addMenuButton(JCommandMenuButton)} and
+ * {@link JCommandPopupMenu#addMenuSeparator()} APIs on the passed menu
+ * parameter.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static interface RibbonGalleryPopupCallback {
+ /**
+ * Called just before the popup menu is about to be shown.
+ *
+ * @param menu
+ * The popup menu that will be shown.
+ */
+ public void popupToBeShown(JCommandPopupMenu menu);
+ }
+
+ /**
+ * Big size with landscape orientation. Used for buttons in in-ribbon
+ * galleries.
+ */
+ public static final CommandButtonDisplayState BIG_FIXED_LANDSCAPE = new CommandButtonDisplayState(
+ "Big Fixed Landscape", 32) {
+ @Override
+ public CommandButtonLayoutManager createLayoutManager(
+ AbstractCommandButton button) {
+ return new CommandButtonLayoutManagerBigFixedLandscape();
+ }
+ };
+
+ /**
+ * Big size with landscape orientation. Used for buttons in in-ribbon
+ * galleries.
+ */
+ public static final CommandButtonDisplayState BIG_FIXED = new CommandButtonDisplayState(
+ "Big Fixed", 32) {
+ @Override
+ public CommandButtonLayoutManager createLayoutManager(
+ AbstractCommandButton button) {
+ return new CommandButtonLayoutManagerBigFixed();
+ }
+ };
+
+ /**
+ * Constructs a <code>JRibbonBand</code> specifying the title and the icon
+ * when in the collapsed state.
+ * <p>
+ * This is equivalent to <code>JRibbonBand(title, icon, null)</code>.
+ *
+ * @see #JRibbonBand(String, ResizableIcon, ActionListener)
+ * @param title
+ * band title
+ * @param icon
+ * the icon displayed when the band collapses
+ */
+ public JRibbonBand(String title, ResizableIcon icon) {
+ this(title, icon, null);
+ }
+
+ /**
+ * Constructs a <code>JRibbonBand</code> specifying the title, the icon when
+ * in the collapsed state, and the action listener for when the band is
+ * expanded.
+ *
+ * @param title
+ * band title
+ * @param icon
+ * the icon displayed when the band collapses
+ * @param expandActionListener
+ * Expand action listener (can be <code>null</code>).
+ */
+ public JRibbonBand(String title, ResizableIcon icon,
+ ActionListener expandActionListener) {
+ super(title, icon, expandActionListener, new JBandControlPanel());
+ this.resizePolicies = Collections
+ .unmodifiableList(CoreRibbonResizePolicies
+ .getCorePoliciesPermissive(this));
+ updateUI();
+ }
+
+ /**
+ * Adds the specified command button to <code>this</code> band.
+ *
+ * @param commandButton
+ * the command button to add
+ * @param priority
+ * priority of the button
+ */
+ public void addCommandButton(AbstractCommandButton commandButton,
+ RibbonElementPriority priority) {
+ commandButton.setHorizontalAlignment(SwingConstants.LEFT);
+ this.controlPanel.addCommandButton(commandButton, priority);
+ }
+
+ public void addRibbonGallery(String galleryName,
+ List<StringValuePair<List<JCommandToggleButton>>> buttons,
+ Map<RibbonElementPriority, Integer> preferredVisibleButtonCounts,
+ int preferredPopupMaxButtonColumns,
+ int preferredPopupMaxVisibleButtonRows,
+ RibbonElementPriority priority) {
+ this.addRibbonGallery(galleryName, buttons,
+ preferredVisibleButtonCounts, preferredPopupMaxButtonColumns,
+ preferredPopupMaxVisibleButtonRows,
+ JRibbonBand.BIG_FIXED_LANDSCAPE, priority);
+ }
+
+ /**
+ * Adds a new ribbon gallery to <code>this</code> band.
+ *
+ * @param galleryName
+ * Gallery name.
+ * @param buttons
+ * Button groups.
+ * @param preferredVisibleButtonCounts
+ * Preferred count of visible buttons of the ribbon gallery under
+ * different states.
+ * @param preferredPopupMaxButtonColumns
+ * Preferred maximum columns in the popup gallery associated with
+ * the ribbon gallery.
+ * @param preferredPopupMaxVisibleButtonRows
+ * Preferred maximum visible rows in the popup gallery associated
+ * with the ribbon gallery.
+ * @param priority
+ * The initial ribbon gallery priority.
+ * @see #addRibbonGalleryButtons(String, String, JCommandToggleButton...)
+ * @see #removeRibbonGalleryButtons(String, JCommandToggleButton...)
+ * @see #setSelectedRibbonGalleryButton(String, JCommandToggleButton)
+ */
+ public void addRibbonGallery(String galleryName,
+ List<StringValuePair<List<JCommandToggleButton>>> buttons,
+ Map<RibbonElementPriority, Integer> preferredVisibleButtonCounts,
+ int preferredPopupMaxButtonColumns,
+ int preferredPopupMaxVisibleButtonRows,
+ CommandButtonDisplayState ribbonButtonDisplayState,
+ RibbonElementPriority priority) {
+ JRibbonGallery gallery = new JRibbonGallery();
+ gallery.setButtonDisplayState(ribbonButtonDisplayState);
+ gallery.setName(galleryName);
+ for (Map.Entry<RibbonElementPriority, Integer> prefCountEntry : preferredVisibleButtonCounts
+ .entrySet()) {
+ gallery.setPreferredVisibleButtonCount(prefCountEntry.getKey(),
+ prefCountEntry.getValue());
+ }
+ gallery.setGroupMapping(buttons);
+ gallery.setPreferredPopupPanelDimension(preferredPopupMaxButtonColumns,
+ preferredPopupMaxVisibleButtonRows);
+
+// this.controlPanel.addRibbonGallery(gallery, priority);
+ addRibbonGallery(gallery, priority);
+ }
+
+ /**
+ * Adds the specified command toggle buttons to a button group in the
+ * specified ribbon gallery.
+ *
+ * @param galleryName
+ * Ribbon gallery name.
+ * @param buttonGroupName
+ * Button group name.
+ * @param buttons
+ * Buttons to add.
+ * @see #addRibbonGallery(String, List, Map, int, int,
+ * RibbonElementPriority)
+ * @see #removeRibbonGalleryButtons(String, JCommandToggleButton...)
+ * @see #setSelectedRibbonGalleryButton(String, JCommandToggleButton)
+ */
+ public void addRibbonGalleryButtons(String galleryName,
+ String buttonGroupName, JCommandToggleButton... buttons) {
+ JRibbonGallery gallery = this.controlPanel
+ .getRibbonGallery(galleryName);
+ if (gallery == null)
+ return;
+ gallery.addRibbonGalleryButtons(buttonGroupName, buttons);
+ }
+
+ /**
+ * Adds the <code>gallery</code> to the ribbon band with a priority of
+ * {@link RibbonElementPriority#TOP}.
+ *
+ * @param gallery
+ * the ribbon gallery
+ */
+ public void addRibbonGallery(JRibbonGallery gallery) {
+ controlPanel.addRibbonGallery(gallery, RibbonElementPriority.TOP);
+ }
+
+ /**
+ * Adds the <code>gallery</code> to the ribbon band with the specified
+ * <code>priority</code>.
+ *
+ * @param gallery
+ * the ribbon gallery
+ * @param priority
+ * the gallery priority
+ */
+ public void addRibbonGallery(JRibbonGallery gallery,
+ RibbonElementPriority priority) {
+ controlPanel.addRibbonGallery(gallery, priority);
+ }
+
+ /**
+ * Removes command toggle buttons from the specified ribbon gallery.
+ *
+ * @param galleryName
+ * Ribbon gallery name.
+ * @param buttons
+ * Buttons to remove.
+ * @see #addRibbonGallery(String, List, Map, int, int,
+ * RibbonElementPriority)
+ * @see #addRibbonGalleryButtons(String, String, JCommandToggleButton...)
+ * @see #setSelectedRibbonGalleryButton(String, JCommandToggleButton)
+ */
+ public void removeRibbonGalleryButtons(String galleryName,
+ JCommandToggleButton... buttons) {
+ JRibbonGallery gallery = this.controlPanel
+ .getRibbonGallery(galleryName);
+ if (gallery == null)
+ return;
+ gallery.removeRibbonGalleryButtons(buttons);
+ }
+
+ /**
+ * Selects the specified command toggle button in the specified ribbon
+ * gallery.
+ *
+ * @param galleryName
+ * Ribbon gallery name.
+ * @param buttonToSelect
+ * Button to select.
+ * @see #addRibbonGallery(String, List, Map, int, int,
+ * RibbonElementPriority)
+ * @see #addRibbonGalleryButtons(String, String, JCommandToggleButton...)
+ * @see #removeRibbonGalleryButtons(String, JCommandToggleButton...)
+ */
+ public void setSelectedRibbonGalleryButton(String galleryName,
+ JCommandToggleButton buttonToSelect) {
+ JRibbonGallery gallery = this.controlPanel
+ .getRibbonGallery(galleryName);
+ if (gallery == null)
+ return;
+ gallery.setSelectedButton(buttonToSelect);
+ }
+
+ /**
+ * Sets the display state for the buttons of the specified ribbon gallery.
+ *
+ * @param galleryName
+ * Ribbon gallery name.
+ * @param displayState
+ * Display state for the buttons of the matching ribbon gallery.
+ */
+ public void setRibbonGalleryButtonDisplayState(String galleryName,
+ CommandButtonDisplayState displayState) {
+ JRibbonGallery gallery = this.controlPanel
+ .getRibbonGallery(galleryName);
+ if (gallery == null)
+ return;
+ gallery.setButtonDisplayState(displayState);
+ }
+
+ /**
+ * Sets the application callback to place additional entries in the popup
+ * menu shown when the specified ribbon gallery is expanded.
+ *
+ * @param galleryName
+ * Gallery name.
+ * @param popupCallback
+ * Application callback.
+ * @see RibbonGalleryPopupCallback
+ */
+ public void setRibbonGalleryPopupCallback(String galleryName,
+ RibbonGalleryPopupCallback popupCallback) {
+ JRibbonGallery gallery = this.controlPanel
+ .getRibbonGallery(galleryName);
+ if (gallery == null)
+ return;
+ gallery.setPopupCallback(popupCallback);
+ }
+
+ /**
+ * Sets the key tip on the expand button of the specified ribbon gallery.
+ *
+ * @param galleryName
+ * Gallery name.
+ * @param expandKeyTip
+ * The key tip on the expand button of the specified ribbon
+ * gallery.
+ */
+ public void setRibbonGalleryExpandKeyTip(String galleryName,
+ String expandKeyTip) {
+ JRibbonGallery gallery = this.controlPanel
+ .getRibbonGallery(galleryName);
+ if (gallery == null)
+ return;
+ gallery.setExpandKeyTip(expandKeyTip);
+ }
+
+ /**
+ * Adds the specified ribbon component to this ribbon band.
+ *
+ * @param comp
+ * The ribbon component to add.
+ */
+ public void addRibbonComponent(JRibbonComponent comp) {
+ this.controlPanel.addRibbonComponent(comp);
+ }
+
+ /**
+ * Adds the specified ribbon component to this ribbon band.
+ *
+ * @param comp
+ * The ribbon component to add.
+ * @param rowSpan
+ * Row span of the ribbon component.
+ * @throws IllegalArgumentException
+ * if the row span is not legal. Legal row span is 1..3 for
+ * unnamed groups and 1..2 for named groups.
+ * @see #startGroup()
+ * @see #startGroup(String)
+ */
+ public void addRibbonComponent(JRibbonComponent comp, int rowSpan) {
+ int groupCount = this.controlPanel.getControlPanelGroupCount();
+ String groupTitle = (groupCount > 0) ? this.controlPanel
+ .getControlPanelGroupTitle(groupCount - 1) : null;
+ int availableRows = (groupTitle == null) ? 3 : 2;
+ if ((rowSpan <= 0) || (rowSpan > availableRows)) {
+ throw new IllegalArgumentException(
+ "Row span value not supported. Should be in 1.."
+ + availableRows + " range");
+ }
+ this.controlPanel.addRibbonComponent(comp, rowSpan);
+ }
+
+ /**
+ * Starts a new unnamed group.
+ *
+ * @return The index of the new group.
+ */
+ public int startGroup() {
+ return this.controlPanel.startGroup();
+ }
+
+ /**
+ * Starts a new named group.
+ *
+ * @param groupTitle
+ * The group title.
+ * @return The index of the new group.
+ */
+ public int startGroup(String groupTitle) {
+ return this.controlPanel.startGroup(groupTitle);
+ }
+
+ /**
+ * Changes the title of the specified group.
+ *
+ * @param groupIndex
+ * Group index.
+ * @param groupTitle
+ * The new title for this group.
+ */
+ public void setGroupTitle(int groupIndex, String groupTitle) {
+ this.controlPanel.setGroupTitle(groupIndex, groupTitle);
+ }
+
+ public List<JRibbonComponent> getRibbonComponents(int groupIndex) {
+ return this.controlPanel.getRibbonComponents(groupIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.AbstractRibbonBand#cloneBand()
+ */
+ @Override
+ public AbstractRibbonBand<JBandControlPanel> cloneBand() {
+ AbstractRibbonBand<JBandControlPanel> result = new JRibbonBand(
+ this.getTitle(), this.getIcon(), this.getExpandActionListener());
+ result.applyComponentOrientation(this.getComponentOrientation());
+ return result;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JRibbonComponent.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JRibbonComponent.java
new file mode 100644
index 0000000..0575f32
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JRibbonComponent.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.awt.event.MouseEvent;
+
+import javax.swing.JComponent;
+import javax.swing.UIManager;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonComponentUI;
+import org.pushingpixels.flamingo.internal.ui.ribbon.RibbonComponentUI;
+
+/**
+ * Wrapper around core and 3rd party Swing controls to allow placing them in the
+ * {@link JRibbonBand}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JRibbonComponent extends RichToolTipManager.JTrackableComponent {
+ /**
+ * Wrapper icon. Can be <code>null</code>.
+ *
+ * @see #JRibbonComponent(ResizableIcon, String, JComponent)
+ */
+ private ResizableIcon icon;
+
+ /**
+ * Wrapper caption. Can be <code>null</code>.
+ *
+ * @see #JRibbonComponent(ResizableIcon, String, JComponent)
+ */
+ private String caption;
+
+ /**
+ * The wrapped component. Is guaranteed to be non <code>null</code>.
+ */
+ private JComponent mainComponent;
+
+ /**
+ * Indication whether this wrapper is simple. A simple wrapper has
+ * <code>null</code> {@link #icon} and <code>null</code> {@link #caption}.
+ */
+ private boolean isSimpleWrapper;
+
+ /**
+ * The key tip for this wrapper component.
+ *
+ * @see #setKeyTip(String)
+ * @see #getKeyTip()
+ */
+ private String keyTip;
+
+ /**
+ * The rich tooltip for this wrapper component.
+ *
+ * @see #setRichTooltip(RichTooltip)
+ * @see #getRichTooltip(MouseEvent)
+ */
+ private RichTooltip richTooltip;
+
+ /**
+ * The horizontal alignment for this wrapper component.
+ *
+ * @see #getHorizontalAlignment()
+ * @see #setHorizontalAlignment(HorizontalAlignment)
+ */
+ private HorizontalAlignment horizontalAlignment;
+
+ private RibbonElementPriority displayPriority;
+
+ private boolean isResizingAware;
+
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "RibbonComponentUI";
+
+ /**
+ * Creates a simple wrapper with no icon and no caption.
+ *
+ * @param mainComponent
+ * Wrapped component. Can not be <code>null</code>.
+ * @throws IllegalArgumentException
+ * if <code>mainComponent</code> is <code>null</code>.
+ */
+ public JRibbonComponent(JComponent mainComponent) {
+ if (mainComponent == null)
+ throw new IllegalArgumentException(
+ "All parameters must be non-null");
+ this.mainComponent = mainComponent;
+ this.isSimpleWrapper = true;
+ this.horizontalAlignment = HorizontalAlignment.LEADING;
+ this.isResizingAware = false;
+ this.displayPriority = RibbonElementPriority.TOP;
+
+ this.updateUI();
+ }
+
+ /**
+ * Creates a wrapper with an icon and a caption.
+ *
+ * @param icon
+ * Wrapper icon. Can be <code>null</code>.
+ * @param caption
+ * Wrapper caption. Can not be <code>null</code>.
+ * @param mainComponent
+ * Wrapped component. Can not be <code>null</code>.
+ * @throws IllegalArgumentException
+ * if <code>caption</code> or <code>mainComponent</code> is
+ * <code>null</code>.
+ */
+ public JRibbonComponent(ResizableIcon icon, String caption,
+ JComponent mainComponent) {
+ if (caption == null)
+ throw new IllegalArgumentException("Caption must be non-null");
+ if (mainComponent == null)
+ throw new IllegalArgumentException(
+ "Main component must be non-null");
+ this.icon = icon;
+ this.caption = caption;
+ this.mainComponent = mainComponent;
+ this.isSimpleWrapper = false;
+ this.horizontalAlignment = HorizontalAlignment.TRAILING;
+
+ this.updateUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI(UIManager.getUI(this));
+ } else {
+ setUI(BasicRibbonComponentUI.createUI(this));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /**
+ * Returns the UI object which implements the L&F for this component.
+ *
+ * @return a <code>RibbonUI</code> object
+ * @see #setUI(javax.swing.plaf.ComponentUI)
+ */
+ public RibbonComponentUI getUI() {
+ return (RibbonComponentUI) ui;
+ }
+
+ /**
+ * Returns the wrapper icon of this wrapper component. Can return
+ * <code>null</code>.
+ *
+ * @return The wrapper icon of this wrapper component.
+ * @see #JRibbonComponent(ResizableIcon, String, JComponent)
+ */
+ public ResizableIcon getIcon() {
+ return this.icon;
+ }
+
+ /**
+ * Returns the caption of this wrapper component. Can return
+ * <code>null</code>.
+ *
+ * @return The caption of this wrapper component.
+ * @see #JRibbonComponent(ResizableIcon, String, JComponent)
+ */
+ public String getCaption() {
+ return this.caption;
+ }
+
+ /**
+ * Sets new value for the caption of this wrapper component.
+ *
+ * @param caption
+ * The new caption.
+ */
+ public void setCaption(String caption) {
+ if (this.isSimpleWrapper) {
+ throw new IllegalArgumentException(
+ "Cannot set caption on a simple component");
+ }
+ if (caption == null) {
+ throw new IllegalArgumentException("Caption must be non-null");
+ }
+
+ String old = this.caption;
+ this.caption = caption;
+ this.firePropertyChange("caption", old, this.caption);
+ }
+
+ /**
+ * Returns the wrapped component of this wrapper component. The result is
+ * guaranteed to be non <code>null</code>.
+ *
+ * @return The wrapped component of this wrapper component.
+ */
+ public JComponent getMainComponent() {
+ return this.mainComponent;
+ }
+
+ /**
+ * Returns indication whether this wrapper is simple.
+ *
+ * @return <code>true</code> if both {@link #getIcon()} and
+ * {@link #getCaption()} return <code>null</code>,
+ * <code>false</code> otherwise.
+ */
+ public boolean isSimpleWrapper() {
+ return this.isSimpleWrapper;
+ }
+
+ /**
+ * Returns the key tip for this wrapper component.
+ *
+ * @return The key tip for this wrapper component.
+ * @see #setKeyTip(String)
+ */
+ public String getKeyTip() {
+ return this.keyTip;
+ }
+
+ /**
+ * Sets the specified string to be the key tip for this wrapper component.
+ * Fires a <code>keyTip</code> property change event.
+ *
+ * @param keyTip
+ * The new key tip for this wrapper component.
+ */
+ public void setKeyTip(String keyTip) {
+ String old = this.keyTip;
+ this.keyTip = keyTip;
+ this.firePropertyChange("keyTip", old, this.keyTip);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.RichToolTipManager.JTrackableComponent#
+ * getRichTooltip(java.awt.event.MouseEvent)
+ */
+ @Override
+ public RichTooltip getRichTooltip(MouseEvent mouseEvent) {
+ return this.richTooltip;
+ }
+
+ /**
+ * Sets the rich tooltip for this wrapper component.
+ *
+ * @param richTooltip
+ * @see #getRichTooltip(MouseEvent)
+ */
+ public void setRichTooltip(RichTooltip richTooltip) {
+ this.richTooltip = richTooltip;
+ RichToolTipManager richToolTipManager = RichToolTipManager
+ .sharedInstance();
+ if (richTooltip != null) {
+ richToolTipManager.registerComponent(this);
+ } else {
+ richToolTipManager.unregisterComponent(this);
+ }
+ }
+
+ /**
+ * Returns the horizontal alignment for this wrapper component.
+ *
+ * @return The horizontal alignment for this wrapper component.
+ * @see #setHorizontalAlignment(HorizontalAlignment)
+ */
+ public HorizontalAlignment getHorizontalAlignment() {
+ return this.horizontalAlignment;
+ }
+
+ /**
+ * Sets the specified parameter to be the horizontal alignment for this
+ * wrapper component.
+ *
+ * @param horizontalAlignment
+ * The new horizontal alignment for this wrapper component.
+ * @see #getHorizontalAlignment()
+ */
+ public void setHorizontalAlignment(HorizontalAlignment horizontalAlignment) {
+ this.horizontalAlignment = horizontalAlignment;
+ }
+
+ public RibbonElementPriority getDisplayPriority() {
+ return this.displayPriority;
+ }
+
+ public void setDisplayPriority(RibbonElementPriority displayPriority) {
+ RibbonElementPriority old = this.displayPriority;
+ this.displayPriority = displayPriority;
+ if (old != displayPriority) {
+ this.firePropertyChange("displayPriority", old,
+ this.displayPriority);
+ }
+ }
+
+ public boolean isResizingAware() {
+ return this.isResizingAware;
+ }
+
+ public void setResizingAware(boolean isResizingAware) {
+ this.isResizingAware = isResizingAware;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JRibbonFrame.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JRibbonFrame.java
new file mode 100755
index 0000000..fcbb14c
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/JRibbonFrame.java
@@ -0,0 +1,716 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.swing.*;
+
+import org.pushingpixels.flamingo.api.common.AsynchronousLoadListener;
+import org.pushingpixels.flamingo.api.common.AsynchronousLoading;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager;
+import org.pushingpixels.flamingo.internal.ui.ribbon.*;
+import org.pushingpixels.flamingo.internal.utils.*;
+import org.pushingpixels.flamingo.internal.utils.KeyTipManager.KeyTipEvent;
+
+/**
+ * <p>
+ * Ribbon frame. Provides the same functionality as a regular {@link JFrame},
+ * but with a {@link JRibbon} component in the top location.
+ * </p>
+ *
+ * <p>
+ * This is the only officially supported way to use the {@link JRibbon}
+ * container. While {@link JRibbon#JRibbon()} constructor is public, it is
+ * provided only for the applications that are absolutely prevented from using
+ * {@link JRibbonFrame} class.
+ * </p>
+ *
+ * <p>
+ * The implementation enforces that a {@link JRibbon} component is always at the
+ * {@link BorderLayout#NORTH} location, throwing
+ * {@link IllegalArgumentException} on attempts to set a custom layout manager,
+ * add another component at {@link BorderLayout#NORTH}, remove the
+ * {@link JRibbon} component, set a custom menu bar, content pane or any other
+ * operation that inteferes with the intended hierarchy of this frame.
+ * </p>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JRibbonFrame extends JFrame {
+ /**
+ * The ribbon component.
+ */
+ private JRibbon ribbon;
+
+ private ResizableIcon appIcon;
+
+ private boolean wasSetIconImagesCalled;
+
+ protected ExecutorService setAppIconExecutor = Executors.newSingleThreadExecutor();
+ /**
+ * Custom layout manager that enforces the {@link JRibbon} location at
+ * {@link BorderLayout#NORTH}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class RibbonFrameLayout extends BorderLayout {
+ @Override
+ public void addLayoutComponent(Component comp, Object constraints) {
+ if ((constraints != null) && constraints.equals(BorderLayout.NORTH)) {
+ if (getLayoutComponent(BorderLayout.NORTH) != null) {
+ throw new IllegalArgumentException(
+ "Already has a NORTH JRibbon component");
+ }
+ if (!(comp instanceof JRibbon)) {
+ throw new IllegalArgumentException(
+ "Can't add non-JRibbon component to NORTH location");
+ }
+ }
+ super.addLayoutComponent(comp, constraints);
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ if (comp instanceof JRibbon) {
+ throw new IllegalArgumentException(
+ "Can't remove JRibbon component");
+ }
+ super.removeLayoutComponent(comp);
+ }
+ }
+
+ /**
+ * A custom layer that shows the currently visible key tip chain.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class KeyTipLayer extends JComponent {
+ /**
+ * Creates a new key tip layer.
+ */
+ public KeyTipLayer() {
+ this.setOpaque(false);
+
+ // Support placing heavyweight components in the ribbon frame. See
+ // http://today.java.net/article/2009/11/02/transparent-panel-mixing-heavyweight-and-lightweight-components.
+ try {
+ Class awtUtilitiesClass = Class
+ .forName("com.sun.awt.AWTUtilities");
+ Method mSetComponentMixing = awtUtilitiesClass.getMethod(
+ "setComponentMixingCutoutShape", Component.class,
+ Shape.class);
+ mSetComponentMixing.invoke(null, this, new Rectangle());
+ } catch (Throwable ignored) {
+ }
+ }
+
+ @Override
+ public synchronized void addMouseListener(MouseListener l) {
+ }
+
+ @Override
+ public synchronized void addMouseMotionListener(MouseMotionListener l) {
+ }
+
+ @Override
+ public synchronized void addMouseWheelListener(MouseWheelListener l) {
+ }
+
+ @Override
+ public synchronized void addKeyListener(KeyListener l) {
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ JRibbonFrame ribbonFrame = (JRibbonFrame) SwingUtilities
+ .getWindowAncestor(this);
+ if (!ribbonFrame.isShowingKeyTips())
+ return;
+
+ // don't show keytips on inactive windows
+ if (!ribbonFrame.isActive())
+ return;
+
+ Collection<KeyTipManager.KeyTipLink> keyTips = KeyTipManager
+ .defaultManager().getCurrentlyShownKeyTips();
+ if (keyTips != null) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ RenderingUtils.installDesktopHints(g2d);
+
+ for (KeyTipManager.KeyTipLink keyTip : keyTips) {
+ // don't display keytips on components in popup panels
+ if (SwingUtilities.getAncestorOfClass(JPopupPanel.class,
+ keyTip.comp) != null)
+ continue;
+
+ // don't display key tips on hidden components
+ Rectangle compBounds = keyTip.comp.getBounds();
+ if (!keyTip.comp.isShowing()
+ || (compBounds.getWidth() == 0)
+ || (compBounds.getHeight() == 0))
+ continue;
+
+ Dimension pref = KeyTipRenderingUtilities.getPrefSize(g2d
+ .getFontMetrics(), keyTip.keyTipString);
+
+ Point prefCenter = keyTip.prefAnchorPoint;
+ Point loc = SwingUtilities.convertPoint(keyTip.comp,
+ prefCenter, this);
+ Container bandControlPanel = SwingUtilities
+ .getAncestorOfClass(AbstractBandControlPanel.class,
+ keyTip.comp);
+ if (bandControlPanel != null) {
+ // special case for controls in threesome
+ // ribbon band rows
+ if (hasClientPropertySetToTrue(keyTip.comp,
+ BasicBandControlPanelUI.TOP_ROW)) {
+ loc = SwingUtilities.convertPoint(keyTip.comp,
+ prefCenter, bandControlPanel);
+ loc.y = 0;
+ loc = SwingUtilities.convertPoint(bandControlPanel,
+ loc, this);
+ // prefCenter.y = 0;
+ }
+ if (hasClientPropertySetToTrue(keyTip.comp,
+ BasicBandControlPanelUI.MID_ROW)) {
+ loc = SwingUtilities.convertPoint(keyTip.comp,
+ prefCenter, bandControlPanel);
+ loc.y = bandControlPanel.getHeight() / 2;
+ loc = SwingUtilities.convertPoint(bandControlPanel,
+ loc, this);
+ // prefCenter.y = keyTip.comp.getHeight() / 2;
+ }
+ if (hasClientPropertySetToTrue(keyTip.comp,
+ BasicBandControlPanelUI.BOTTOM_ROW)) {
+ loc = SwingUtilities.convertPoint(keyTip.comp,
+ prefCenter, bandControlPanel);
+ loc.y = bandControlPanel.getHeight();
+ loc = SwingUtilities.convertPoint(bandControlPanel,
+ loc, this);
+ // prefCenter.y = keyTip.comp.getHeight();
+ }
+ }
+
+ KeyTipRenderingUtilities
+ .renderKeyTip(g2d, this, new Rectangle(loc.x
+ - pref.width / 2, loc.y - pref.height / 2,
+ pref.width, pref.height),
+ keyTip.keyTipString, keyTip.enabled);
+ }
+
+ g2d.dispose();
+ }
+ }
+
+ /**
+ * Checks whether the specified component or one of its ancestors has
+ * the specified client property set to {@link Boolean#TRUE}.
+ *
+ * @param c
+ * Component.
+ * @param clientPropName
+ * Client property name.
+ * @return <code>true</code> if the specified component or one of its
+ * ancestors has the specified client property set to
+ * {@link Boolean#TRUE}, <code>false</code> otherwise.
+ */
+ private boolean hasClientPropertySetToTrue(Component c,
+ String clientPropName) {
+ while (c != null) {
+ if (c instanceof JComponent) {
+ JComponent jc = (JComponent) c;
+ if (Boolean.TRUE.equals(jc
+ .getClientProperty(clientPropName)))
+ return true;
+ }
+ c = c.getParent();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean contains(int x, int y) {
+ // pass the mouse events to the underlying layers for
+ // showing the correct cursor. See
+ // http://weblogs.java.net/blog/alexfromsun/archive/2006/09/
+ // a_wellbehaved_g.html
+ return false;
+ }
+ }
+
+ /**
+ * Creates a new ribbon frame with no title.
+ *
+ * @throws HeadlessException
+ * If GraphicsEnvironment.isHeadless() returns true.
+ */
+ public JRibbonFrame() throws HeadlessException {
+ super();
+ this.initRibbon();
+ }
+
+ /**
+ * Creates a new ribbon frame with no title.
+ *
+ * @param gc
+ * Graphics configuration to use.
+ */
+ public JRibbonFrame(GraphicsConfiguration gc) {
+ super(gc);
+ this.initRibbon();
+ }
+
+ /**
+ * Creates a new ribbon frame with the specified title.
+ *
+ * @param title
+ * Ribbon frame title.
+ * @throws HeadlessException
+ * If GraphicsEnvironment.isHeadless() returns true.
+ */
+ public JRibbonFrame(String title) throws HeadlessException {
+ super(title);
+ this.initRibbon();
+ }
+
+ /**
+ * Creates a new ribbon frame with the specified title.
+ *
+ * @param title
+ * Ribbon frame title.
+ * @param gc
+ * Graphics configuration to use.
+ * @throws HeadlessException
+ * If GraphicsEnvironment.isHeadless() returns true.
+ */
+ public JRibbonFrame(String title, GraphicsConfiguration gc) {
+ super(title, gc);
+ this.initRibbon();
+ }
+
+ @Override
+ public void dispose() {
+ setAppIconExecutor.shutdownNow();
+ super.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JFrame#setLayout(java.awt.LayoutManager)
+ */
+ @Override
+ public void setLayout(LayoutManager manager) {
+ if (manager.getClass() != RibbonFrameLayout.class) {
+ LayoutManager currManager = getLayout();
+ if (currManager != null) {
+ throw new IllegalArgumentException(
+ "Can't set a custom layout manager on JRibbonFrame");
+ }
+ }
+ super.setLayout(manager);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JFrame#setJMenuBar(javax.swing.JMenuBar)
+ */
+ @Override
+ public void setJMenuBar(JMenuBar menubar) {
+ throw new IllegalArgumentException(
+ "Can't set a menu bar on JRibbonFrame");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JFrame#setContentPane(java.awt.Container)
+ */
+ @Override
+ public void setContentPane(Container contentPane) {
+ throw new IllegalArgumentException(
+ "Can't set the content pane on JRibbonFrame");
+ }
+
+ /**
+ * Initializes the layout and the ribbon.
+ */
+ private void initRibbon() {
+ this.setLayout(new RibbonFrameLayout());
+ this.ribbon = new JRibbon(this);
+ this.add(this.ribbon, BorderLayout.NORTH);
+
+ // this.keyTipManager = new KeyTipManager(this);
+ Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
+ private boolean prevAltModif = false;
+
+ @Override
+ public void eventDispatched(AWTEvent event) {
+ Object src = event.getSource();
+ if (src instanceof Component) {
+ Component c = (Component) src;
+ if ((c == JRibbonFrame.this)
+ || (SwingUtilities.getWindowAncestor(c) == JRibbonFrame.this)) {
+ if (event instanceof KeyEvent) {
+ KeyEvent keyEvent = (KeyEvent) event;
+ // System.out.println(keyEvent.getID() + ":"
+ // + keyEvent.getKeyCode());
+ switch (keyEvent.getID()) {
+ case KeyEvent.KEY_PRESSED:
+ // if (keyEvent.getKeyCode() ==
+ // KeyEvent.VK_ESCAPE) {
+ // keyTipManager.showPreviousChain();
+ // }
+
+ break;
+ case KeyEvent.KEY_RELEASED:
+ boolean wasAltModif = prevAltModif;
+ prevAltModif = keyEvent.getModifiersEx() == InputEvent.ALT_DOWN_MASK;
+ if (wasAltModif
+ && keyEvent.getKeyCode() == KeyEvent.VK_ALT)
+ break;
+ char keyChar = keyEvent.getKeyChar();
+ if (Character.isLetter(keyChar)
+ || Character.isDigit(keyChar)) {
+ // System.out.println("Will handle key press "
+ // + keyChar);
+ KeyTipManager.defaultManager()
+ .handleKeyPress(keyChar);
+ }
+ if ((keyEvent.getKeyCode() == KeyEvent.VK_ALT)
+ || (keyEvent.getKeyCode() == KeyEvent.VK_F10)) {
+ if (keyEvent.getModifiers() != 0
+ || keyEvent.getModifiersEx() != 0)
+ break;
+ boolean hadPopups = !PopupPanelManager
+ .defaultManager().getShownPath()
+ .isEmpty();
+ PopupPanelManager.defaultManager()
+ .hidePopups(null);
+ if (hadPopups
+ || KeyTipManager.defaultManager()
+ .isShowingKeyTips()) {
+ KeyTipManager.defaultManager()
+ .hideAllKeyTips();
+ } else {
+ KeyTipManager.defaultManager()
+ .showRootKeyTipChain(
+ JRibbonFrame.this);
+ }
+ }
+ if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ // System.out.println("In KTM");
+ KeyTipManager.defaultManager()
+ .showPreviousChain();
+ }
+ break;
+ }
+ }
+ if (event instanceof MouseEvent) {
+ MouseEvent mouseEvent = (MouseEvent) event;
+ switch (mouseEvent.getID()) {
+ case MouseEvent.MOUSE_CLICKED:
+ case MouseEvent.MOUSE_DRAGGED:
+ case MouseEvent.MOUSE_PRESSED:
+ case MouseEvent.MOUSE_RELEASED:
+ KeyTipManager.defaultManager().hideAllKeyTips();
+ }
+ }
+ }
+ }
+ }
+ }, AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);
+
+ final KeyTipLayer keyTipLayer = new KeyTipLayer();
+ JRootPane rootPane = this.getRootPane();
+ JLayeredPane layeredPane = rootPane.getLayeredPane();
+ final LayoutManager currLM = rootPane.getLayout();
+ rootPane.setLayout(new LayoutManager() {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ currLM.addLayoutComponent(name, comp);
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ currLM.layoutContainer(parent);
+ JRibbonFrame ribbonFrame = JRibbonFrame.this;
+ if (ribbonFrame.getRootPane().getWindowDecorationStyle() != JRootPane.NONE)
+ keyTipLayer
+ .setBounds(ribbonFrame.getRootPane().getBounds());
+ else
+ keyTipLayer.setBounds(ribbonFrame.getRootPane()
+ .getContentPane().getBounds());
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return currLM.minimumLayoutSize(parent);
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ return currLM.preferredLayoutSize(parent);
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ currLM.removeLayoutComponent(comp);
+ }
+ });
+ // layeredPane.setLayout(new OverlayLayout(layeredPane));
+ layeredPane.add(keyTipLayer,
+ (Integer) (JLayeredPane.DEFAULT_LAYER + 60));
+
+ this.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowDeactivated(WindowEvent e) {
+ // hide all key tips on window deactivation
+ KeyTipManager keyTipManager = KeyTipManager.defaultManager();
+ if (keyTipManager.isShowingKeyTips()) {
+ keyTipManager.hideAllKeyTips();
+ }
+ }
+ });
+
+ KeyTipManager.defaultManager().addKeyTipListener(
+ new KeyTipManager.KeyTipListener() {
+ @Override
+ public void keyTipsHidden(KeyTipEvent event) {
+ if (event.getSource() == JRibbonFrame.this)
+ keyTipLayer.setVisible(false);
+ }
+
+ @Override
+ public void keyTipsShown(KeyTipEvent event) {
+ if (event.getSource() == JRibbonFrame.this)
+ keyTipLayer.setVisible(true);
+ }
+ });
+
+ ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false);
+ JPopupMenu.setDefaultLightWeightPopupEnabled(false);
+
+ super.setIconImages(Arrays.asList(FlamingoUtilities.getBlankImage(16,
+ 16)));
+ }
+
+ /**
+ * Returns the ribbon component.
+ *
+ * @return Ribbon component.
+ */
+ public JRibbon getRibbon() {
+ return this.ribbon;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JFrame#createRootPane()
+ */
+ @Override
+ protected JRootPane createRootPane() {
+ JRootPane rp = new JRibbonRootPane();
+ rp.setOpaque(true);
+ return rp;
+ }
+
+ @Override
+ public synchronized void setIconImages(List<? extends Image> icons) {
+ super.setIconImages(icons);
+ this.wasSetIconImagesCalled = true;
+ }
+
+ public synchronized void setApplicationIcon(final ResizableIcon icon) {
+ setAppIconExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ // still loading?
+ if (icon instanceof AsynchronousLoading) {
+ AsynchronousLoading async = (AsynchronousLoading) icon;
+ final CountDownLatch latch = new CountDownLatch(1);
+ AsynchronousLoadListener asyncListener = new AsynchronousLoadListener() {
+ @Override
+ public void completed(boolean success) {
+ latch.countDown();
+ }
+ };
+ async.addAsynchronousLoadListener(asyncListener);
+ if (async.isLoading()) {
+ try {
+ latch.await();
+ } catch (InterruptedException ignored) {
+ }
+ }
+ async.removeAsynchronousLoadListener(asyncListener);
+ }
+ setApplicationAndMenuButtonIcon(icon);
+ }
+ });
+ }
+
+ private void setApplicationAndMenuButtonIcon(final ResizableIcon icon) {
+ if (System.getProperty("os.name").startsWith("Mac")) {
+ class MacImages {
+ Image icon16;
+
+ Image icon128;
+
+ public MacImages(Image icon16, Image icon128) {
+ this.icon16 = icon16;
+ this.icon128 = icon128;
+ }
+ }
+
+ final Image image16 = getImage(icon, 16);
+ final Image image128 = getImage(icon, 128);
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (image16 != null) {
+ setLegacyIconImages(Arrays.asList(image16));
+ }
+ if (image128 != null) {
+ try {
+ Class appClass = Class
+ .forName("com.apple.eawt.Application");
+ if (appClass != null) {
+ Object appInstance = appClass.newInstance();
+ Method setDockImageMethod = appClass
+ .getDeclaredMethod("setDockIconImage",
+ Image.class);
+ if (setDockImageMethod != null) {
+ setDockImageMethod.invoke(appInstance,
+ image128);
+ }
+ }
+ } catch (Throwable t) {
+ t.printStackTrace();
+ // give up
+ }
+ }
+ setMainAppIcon(icon);
+ }
+ });
+ } else {
+ final List<Image> images = new ArrayList<Image>();
+ Image icon16 = getImage(icon, 16);
+ if (icon16 != null)
+ images.add(icon16);
+ Image icon32 = getImage(icon, 32);
+ if (icon32 != null)
+ images.add(icon32);
+ Image icon64 = getImage(icon, 64);
+ if (icon64 != null)
+ images.add(icon64);
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (!images.isEmpty())
+ setLegacyIconImages(images);
+ setMainAppIcon(icon);
+ }
+ });
+ }
+ }
+
+ private void setMainAppIcon(ResizableIcon icon) {
+ this.appIcon = icon;
+ FlamingoUtilities.updateRibbonFrameIconImages(this);
+ }
+
+ private void setLegacyIconImages(List<Image> images) {
+ if (this.wasSetIconImagesCalled) {
+ return;
+ }
+ super.setIconImages(images);
+ }
+
+ private static Image getImage(ResizableIcon icon, int size) {
+ icon.setDimension(new Dimension(size, size));
+ if (icon instanceof AsynchronousLoading) {
+ AsynchronousLoading async = (AsynchronousLoading) icon;
+ if (async.isLoading()) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final boolean[] status = new boolean[1];
+ AsynchronousLoadListener all = new AsynchronousLoadListener() {
+ @Override
+ public void completed(boolean success) {
+ status[0] = success;
+ latch.countDown();
+ }
+ };
+ async.addAsynchronousLoadListener(all);
+ try {
+ latch.await();
+ } catch (InterruptedException ignored) {
+ }
+ async.removeAsynchronousLoadListener(all);
+ if (!status[0]) {
+ return null;
+ }
+ if (async.isLoading()) {
+ return null;
+ }
+ }
+ }
+ Image result = FlamingoUtilities.getBlankImage(size, size);
+ Graphics2D g2d = (Graphics2D) result.getGraphics().create();
+ icon.paintIcon(null, g2d, 0, 0);
+ g2d.dispose();
+ return result;
+ }
+
+ public synchronized ResizableIcon getApplicationIcon() {
+ return this.appIcon;
+ }
+
+ /**
+ * Returns indication whether this ribbon frame is showing the key tips.
+ *
+ * @return <code>true</code> if this ribbon frame is showing the key tips,
+ * <code>false</code> otherwise.
+ */
+ public boolean isShowingKeyTips() {
+ return KeyTipManager.defaultManager().isShowingKeyTips();
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenu.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenu.java
new file mode 100644
index 0000000..9474cdb
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenu.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.awt.event.ActionListener;
+import java.util.*;
+
+import org.pushingpixels.flamingo.api.common.JCommandMenuButton;
+import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntryPrimary.PrimaryRolloverCallback;
+
+/**
+ * Metadata description of the application menu of the {@link JRibbon}
+ * component. The ribbon application menu has three parts:
+ *
+ * <pre>
+ * +-------------------------------------+
+ * | | |
+ * | | |
+ * | primary | secondary |
+ * | area | area |
+ * | | |
+ * | | |
+ * |-------------------------------------|
+ * | footer area |
+ * +-------------------------------------+
+ * </pre>
+ *
+ * <p>
+ * The entries in the primary area are always visible. The secondary area
+ * entries are shown based on the currently active element in the primary area.
+ * There are three different types of primary entries:
+ * </p>
+ *
+ * <ul>
+ * <li>Associated {@link ActionListener} passed to the constructor of the
+ * {@link RibbonApplicationMenuEntryPrimary}. When this entry is armed (with
+ * mouse rollover or via keyboard navigation), the contents of the secondary
+ * area are cleared. The <code>Quit</code> menu item is an example of such a
+ * primary menu entry.</li>
+ * <li>Associated {@link PrimaryRolloverCallback} set by the
+ * {@link RibbonApplicationMenuEntryPrimary#setRolloverCallback(PrimaryRolloverCallback)}
+ * . When this entry is armed (with mouse rollover or via keyboard navigation),
+ * the contents of the secondary area are populated by the application callback
+ * implementation of
+ * {@link PrimaryRolloverCallback#menuEntryActivated(javax.swing.JPanel)}. The
+ * <code>Open</code> menu item is an example of such a primary menu entry,
+ * showing a list of recently opened files.</li>
+ * <li>Associated list of {@link RibbonApplicationMenuEntrySecondary}s added
+ * with the
+ * {@link RibbonApplicationMenuEntryPrimary#addSecondaryMenuGroup(String, RibbonApplicationMenuEntrySecondary...)}
+ * API. When this entry is armed (with mouse rollover or via keyboard
+ * navigation), the secondary area shows menu buttons for the registered
+ * secondary menu entries. The <code>Save As</code> menu item is an example of
+ * such a primary menu item, showing a list of default save formats.</li>
+ * </ul>
+ *
+ * <p>
+ * At runtime, the application menu entries are implemented as
+ * {@link JCommandMenuButton}, but the application code does not operate on that
+ * level. Instead, the application code creates metadata-driven description of
+ * the ribbon application menu, and that description is used to create and
+ * populate the "real" controls of the application menu popup.
+ * </p>
+ *
+ * <p>
+ * Note that once a {@link RibbonApplicationMenu} is set on the {@link JRibbon}
+ * with the {@link JRibbon#setApplicationMenu(RibbonApplicationMenu)}, its
+ * contents cannot be changed. An {@link IllegalStateException} will be thrown
+ * from {@link #addMenuEntry(RibbonApplicationMenuEntryPrimary)} and
+ * {@link #addFooterEntry(RibbonApplicationMenuEntryFooter)}.
+ * </p>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RibbonApplicationMenu {
+ /**
+ * Indicates whether this ribbon application menu has been set on the
+ * {@link JRibbon} with the
+ * {@link JRibbon#setApplicationMenu(RibbonApplicationMenu)}. Once that API
+ * is called, the contents of this menu cannot be changed. An
+ * {@link IllegalStateException} will be thrown from
+ * {@link #addMenuEntry(RibbonApplicationMenuEntryPrimary)} and
+ * {@link #addFooterEntry(RibbonApplicationMenuEntryFooter)}.
+ *
+ * @see #setFrozen()
+ * @see #addMenuEntry(RibbonApplicationMenuEntryPrimary)
+ * @see #addFooterEntry(RibbonApplicationMenuEntryFooter)
+ */
+ private boolean isFrozen;
+
+ /**
+ * Primary menu entries.
+ */
+ private List<List<RibbonApplicationMenuEntryPrimary>> primaryEntries;
+
+ /**
+ * Footer menu entries.
+ */
+ private List<RibbonApplicationMenuEntryFooter> footerEntries;
+
+ /**
+ * The default callback to be called when:
+ *
+ * <ul>
+ * <li>The ribbon application menu is first shown.</li>
+ * <li>The currently active (rollover) primary application menu entry has no
+ * secondary menu entries and no associated rollover callback.
+ * </ul>
+ */
+ private PrimaryRolloverCallback defaultCallback;
+
+ /**
+ * Creates an empty ribbon application menu.
+ */
+ public RibbonApplicationMenu() {
+ this.primaryEntries = new ArrayList<List<RibbonApplicationMenuEntryPrimary>>();
+ this.primaryEntries
+ .add(new ArrayList<RibbonApplicationMenuEntryPrimary>());
+ this.footerEntries = new ArrayList<RibbonApplicationMenuEntryFooter>();
+ }
+
+ /**
+ * Adds the specified primary menu entry.
+ *
+ * @param entry
+ * Primary menu entry to add.
+ * @throws IllegalStateException
+ * if this ribbon application menu has already been set on the
+ * {@link JRibbon} with the
+ * {@link JRibbon#setApplicationMenu(RibbonApplicationMenu)}.
+ * @see #getPrimaryEntries()
+ * @see #addFooterEntry(RibbonApplicationMenuEntryFooter)
+ */
+ public synchronized void addMenuEntry(
+ RibbonApplicationMenuEntryPrimary entry) {
+ if (this.isFrozen) {
+ throw new IllegalStateException(
+ "Cannot add menu entries after the menu has been set on the ribbon");
+ }
+ this.primaryEntries.get(this.primaryEntries.size() - 1).add(entry);
+ }
+
+ public synchronized void addMenuSeparator() {
+ if (this.isFrozen) {
+ throw new IllegalStateException(
+ "Cannot add menu entries after the menu has been set on the ribbon");
+ }
+ this.primaryEntries
+ .add(new ArrayList<RibbonApplicationMenuEntryPrimary>());
+ }
+
+ /**
+ * Returns an unmodifiable list of all primary menu entries of this
+ * application menu. The result is guaranteed to be non-<code>null</code>.
+ *
+ * @return An unmodifiable list of all primary menu entries of this
+ * application menu.
+ * @see #addMenuEntry(RibbonApplicationMenuEntryPrimary)
+ * @see #getFooterEntries()
+ */
+ public List<List<RibbonApplicationMenuEntryPrimary>> getPrimaryEntries() {
+ return Collections.unmodifiableList(this.primaryEntries);
+ }
+
+ /**
+ * Adds the specified footer menu entry.
+ *
+ * @param entry
+ * Footer menu entry to add.
+ * @throws IllegalStateException
+ * if this ribbon application menu has already been set on the
+ * {@link JRibbon} with the
+ * {@link JRibbon#setApplicationMenu(RibbonApplicationMenu)}.
+ * @see #getFooterEntries()
+ * @see #addMenuEntry(RibbonApplicationMenuEntryPrimary)
+ */
+ public synchronized void addFooterEntry(
+ RibbonApplicationMenuEntryFooter entry) {
+ if (this.isFrozen) {
+ throw new IllegalStateException(
+ "Cannot add footer entries after the menu has been set on the ribbon");
+ }
+ this.footerEntries.add(entry);
+ }
+
+ /**
+ * Returns an unmodifiable list of all footer menu entries of this
+ * application menu. The result is guaranteed to be non-<code>null</code>.
+ *
+ * @return An unmodifiable list of all footer menu entries of this
+ * application menu.
+ * @see #addFooterEntry(RibbonApplicationMenuEntryFooter)
+ * @see #getPrimaryEntries()
+ */
+ public List<RibbonApplicationMenuEntryFooter> getFooterEntries() {
+ return Collections.unmodifiableList(this.footerEntries);
+ }
+
+ /**
+ * Sets the default callback to be called when:
+ *
+ * <ul>
+ * <li>The ribbon application menu is first shown.</li>
+ * <li>The currently active (rollover) primary application menu entry has no
+ * secondary menu entries and no associated rollover callback.
+ * </ul>
+ *
+ * @param defaultCallback
+ * Default callback.
+ */
+ public void setDefaultCallback(PrimaryRolloverCallback defaultCallback) {
+ this.defaultCallback = defaultCallback;
+ }
+
+ /**
+ * Returns the default callback of this ribbon application menu.
+ *
+ * @return The default callback of this ribbon application menu.
+ * @see #setDefaultCallback(org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntryPrimary.PrimaryRolloverCallback)
+ */
+ public PrimaryRolloverCallback getDefaultCallback() {
+ return defaultCallback;
+ }
+
+ /**
+ * Marks this application menu as frozen. Subsequent calls to
+ * {@link #addMenuEntry(RibbonApplicationMenuEntryPrimary)} and
+ * {@link #addFooterEntry(RibbonApplicationMenuEntryFooter)} will throw an
+ * {@link IllegalStateException}.
+ *
+ * @see #addMenuEntry(RibbonApplicationMenuEntryPrimary)
+ * @see #addFooterEntry(RibbonApplicationMenuEntryFooter)
+ * @see JRibbon#setApplicationMenu(RibbonApplicationMenu)
+ */
+ synchronized void setFrozen() {
+ this.isFrozen = true;
+ if (this.primaryEntries.get(this.primaryEntries.size() - 1).isEmpty()) {
+ this.primaryEntries.remove(this.primaryEntries.size() - 1);
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenuEntry.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenuEntry.java
new file mode 100644
index 0000000..feaadc6
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenuEntry.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.awt.event.ActionListener;
+
+import org.pushingpixels.flamingo.api.common.JCommandMenuButton;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.RichTooltip;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+
+/**
+ * Basic metadata for entries in the ribbon application menu.
+ *
+ * <p>
+ * At runtime, the application menu entries are implemented as
+ * {@link JCommandMenuButton}, but the application code does not operate on that
+ * level. Instead, the application code creates metadata-driven description of
+ * the ribbon application menu, and that description is used to create and
+ * populate the "real" controls of the application menu popup.
+ * </p>
+ *
+ * @author Kirill Grouchnikov
+ * @see RibbonApplicationMenu
+ * @see JRibbon#setApplicationMenu(RibbonApplicationMenu)
+ */
+abstract class RibbonApplicationMenuEntry {
+ /**
+ * The menu icon.
+ */
+ protected ResizableIcon icon;
+
+ /**
+ * The menu icon for disabled state. Optional, can be <code>null</code>.
+ */
+ protected ResizableIcon disabledIcon;
+
+ /**
+ * The menu text.
+ */
+ protected String text;
+
+ /**
+ * The main action listener for this menu entry.
+ */
+ protected ActionListener mainActionListener;
+
+ /**
+ * The kind of the command button that represents this menu entry.
+ */
+ protected CommandButtonKind entryKind;
+
+ /**
+ * Enabled state of this menu.
+ */
+ protected boolean isEnabled;
+
+ /**
+ * Optional key tip for the action area of the command button that
+ * represents this menu entry.
+ */
+ protected String actionKeyTip;
+
+ /**
+ * Optional key tip for the popup area of the command button that represents
+ * this menu entry.
+ */
+ protected String popupKeyTip;
+
+ /**
+ * Optional tooltip for the action area of the command button that
+ * represents this menu entry.
+ */
+ protected RichTooltip actionRichTooltip;
+
+ /**
+ * Optional tooltip for the popup area of the command button that represents
+ * this menu entry.
+ */
+ protected RichTooltip popupRichTooltip;
+
+ /**
+ * Creates the basic metadata description of a {@link RibbonApplicationMenu}
+ * menu entry.
+ *
+ * @param icon
+ * The icon of this menu entry. Must be non-<code>null</code>.
+ * @param text
+ * The text of this menu entry. Must be non-<code>null</code>.
+ * @param mainActionListener
+ * The main action listener for this menu entry. If the entry
+ * kind is {@link CommandButtonKind#POPUP_ONLY}, this listener
+ * will be ignored.
+ * @param entryKind
+ * The kind of the command button that will represent this menu
+ * entry. Must be non- <code>null</code>.
+ */
+ public RibbonApplicationMenuEntry(ResizableIcon icon, String text,
+ ActionListener mainActionListener, CommandButtonKind entryKind) {
+ super();
+ this.icon = icon;
+ this.text = text;
+ this.mainActionListener = mainActionListener;
+ this.entryKind = entryKind;
+ this.isEnabled = true;
+ }
+
+ /**
+ * Returns the icon of this application menu entry.
+ *
+ * @return The icon of this application menu entry.
+ */
+ public ResizableIcon getIcon() {
+ return this.icon;
+ }
+
+ /**
+ * Returns the text of this application menu entry.
+ *
+ * @return The text of this application menu entry.
+ * @see #setText(String)
+ */
+ public String getText() {
+ return this.text;
+ }
+
+ /**
+ * Sets the new text for this application menu entry.
+ *
+ * @param text
+ * The new text for this application menu entry.
+ * @see #getText()
+ */
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ /**
+ * Returns the main action listener associated with this application menu
+ * entry.
+ *
+ * @return The main action listener associated with this application menu
+ * entry.
+ */
+ public ActionListener getMainActionListener() {
+ return this.mainActionListener;
+ }
+
+ /**
+ * Returns the kind of the command button that represents this menu entry.
+ *
+ * @return The kind of the command button that represents this menu entry.
+ */
+ public CommandButtonKind getEntryKind() {
+ return this.entryKind;
+ }
+
+ /**
+ * Sets the enabled state of the command button that represents this menu
+ * entry.
+ *
+ * @param isEnabled
+ * If <code>true</code>, the command button that represents this
+ * menu entry will be enabled, if <code>false</code>, the command
+ * button will be disabled.
+ * @see #isEnabled
+ */
+ public void setEnabled(boolean isEnabled) {
+ this.isEnabled = isEnabled;
+ }
+
+ /**
+ * Returns the enabled state of the command button that represents this menu
+ * entry.
+ *
+ * @return <code>true</code> if the command button that represents this menu
+ * entry is enabled, <code>false</code> otherwise.
+ */
+ public boolean isEnabled() {
+ return this.isEnabled;
+ }
+
+ /**
+ * Returns the key tip for the action area of the command button that
+ * represents this menu entry.
+ *
+ * @return The key tip for the action area of the command button that
+ * represents this menu entry.
+ * @see #setActionKeyTip(String)
+ * @see #getPopupKeyTip()
+ */
+ public String getActionKeyTip() {
+ return this.actionKeyTip;
+ }
+
+ /**
+ * Sets the new value for the key tip for the action area of the command
+ * button that represents this menu entry.
+ *
+ * @param actionKeyTip
+ * The new value for the key tip for the action area of the
+ * command button that represents this menu entry.
+ * @see #getActionKeyTip()
+ * @see #setPopupKeyTip(String)
+ */
+ public void setActionKeyTip(String actionKeyTip) {
+ this.actionKeyTip = actionKeyTip;
+ }
+
+ /**
+ * Returns the key tip for the popup area of the command button that
+ * represents this menu entry.
+ *
+ * @return The key tip for the popup area of the command button that
+ * represents this menu entry.
+ * @see #setPopupKeyTip(String)
+ * @see #getActionKeyTip()
+ */
+ public String getPopupKeyTip() {
+ return this.popupKeyTip;
+ }
+
+ /**
+ * Sets the new value for the key tip for the popup area of the command
+ * button that represents this menu entry.
+ *
+ * @param popupKeyTip
+ * The new value for the key tip for the popup area of the
+ * command button that represents this menu entry.
+ * @see #getPopupKeyTip()
+ * @see #setActionKeyTip(String)
+ */
+ public void setPopupKeyTip(String popupKeyTip) {
+ this.popupKeyTip = popupKeyTip;
+ }
+
+ /**
+ * Returns the rich tooltip for the action area of the command
+ * button that represents this menu entry.
+ *
+ * @return The rich tooltip for the action area of the command
+ * button that represents this menu entry.
+ * @see #setPopupTooltip(RichTooltip)
+ * @see #getActionRichTooltip()
+ */
+ public RichTooltip getActionRichTooltip() {
+ return this.actionRichTooltip;
+ }
+
+ /**
+ * Sets the rich tooltip for the action area of the command
+ * button that represents this menu entry.
+ *
+ * @param actionRichTooltip
+ * The rich tooltip for the action area of the command
+ * button that represents this menu entry.
+ * @see #getActionRichTooltip()
+ * @see #setActionTooltip(RichTooltip)
+ */
+ public void setActionRichTooltip(RichTooltip actionRichTooltip) {
+ this.actionRichTooltip = actionRichTooltip;
+ }
+
+ /**
+ * Returns the rich tooltip for the popup area of the command
+ * button that represents this menu entry.
+ *
+ * @return The rich tooltip for the popup area of the command
+ * button that represents this menu entry.
+ * @see #setPopupTooltip(RichTooltip)
+ * @see #getActionRichTooltip()
+ */
+ public RichTooltip getPopupRichTooltip() {
+ return this.popupRichTooltip;
+ }
+
+ /**
+ * Sets the rich tooltip for the popup area of the command
+ * button that represents this menu entry.
+ *
+ * @param popupRichTooltip
+ * The rich tooltip for the popup area of the command
+ * button that represents this menu entry.
+ * @see #getPopupRichTooltip()
+ * @see #setActionTooltip(RichTooltip)
+ */
+ public void setPopupRichTooltip(RichTooltip popupRichTooltip) {
+ this.popupRichTooltip = popupRichTooltip;
+ }
+
+ /**
+ * Returns the disabled icon for the command button that represents this
+ * menu entry.
+ *
+ * @return The disabled icon for the command button that represents this
+ * menu entry.
+ * @see #setDisabledIcon(ResizableIcon)
+ */
+ public ResizableIcon getDisabledIcon() {
+ return this.disabledIcon;
+ }
+
+ /**
+ * Sets the disabled icon for the command button that represents this menu
+ * entry.
+ *
+ * @param disabledIcon
+ * The disabled icon for the command button that represents this
+ * menu entry.
+ * @see #getDisabledIcon()
+ */
+ public void setDisabledIcon(ResizableIcon disabledIcon) {
+ this.disabledIcon = disabledIcon;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenuEntryFooter.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenuEntryFooter.java
new file mode 100644
index 0000000..c73cf2e
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenuEntryFooter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.awt.event.ActionListener;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+
+/**
+ * Metadata description for the footer entries of the
+ * {@link RibbonApplicationMenu}. The footer entries at runtime are represented
+ * by {@link CommandButtonKind#ACTION_ONLY} command buttons placed in a
+ * right-aligned row along the bottom edge of the ribbon application menu.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RibbonApplicationMenuEntryFooter extends
+ RibbonApplicationMenuEntry {
+ /**
+ * Creates the metadata description of a {@link RibbonApplicationMenu}
+ * footer menu entry.
+ *
+ * @param icon
+ * The icon of this footer menu entry. Must be non-
+ * <code>null</code>.
+ * @param text
+ * The text of this footer menu entry. Must be non-
+ * <code>null</code>.
+ * @param mainActionListener
+ * The main action listener for this footer menu entry. While
+ * this can be <code>null</code>, clicking on the matching button
+ * will have no effect.
+ */
+ public RibbonApplicationMenuEntryFooter(ResizableIcon icon, String text,
+ ActionListener mainActionListener) {
+ super(icon, text, mainActionListener, CommandButtonKind.ACTION_ONLY);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenuEntryPrimary.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenuEntryPrimary.java
new file mode 100644
index 0000000..dc574db
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenuEntryPrimary.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.awt.event.ActionListener;
+import java.util.*;
+
+import javax.swing.JPanel;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+
+/**
+ * Metadata description for the primary menu entries of the
+ * {@link RibbonApplicationMenu}. The primary menu entries at runtime are
+ * represented by command menu buttons placed in the left panel of the
+ * application menu.
+ *
+ * <p>
+ * There are three different types of primary entries:
+ * </p>
+ *
+ * <ul>
+ * <li>Associated {@link ActionListener} passed to the
+ * {@link RibbonApplicationMenuEntryPrimary#RibbonApplicationMenuEntryPrimary(org.pushingpixels.flamingo.api.common.icon.ResizableIcon, String, java.awt.event.ActionListener, org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind)}
+ * . When this entry is armed (with mouse rollover or via keyboard navigation),
+ * the contents of the secondary area are cleared. The <code>Quit</code> menu
+ * item is an example of such a primary menu entry.</li>
+ * <li>Associated {@link PrimaryRolloverCallback} set by the
+ * {@link #setRolloverCallback(PrimaryRolloverCallback)} . When this entry is
+ * armed (with mouse rollover or via keyboard navigation), the contents of the
+ * secondary area are populated by the application callback implementation of
+ * {@link PrimaryRolloverCallback#menuEntryActivated(javax.swing.JPanel)}. The
+ * <code>Open</code> menu item is an example of such a primary menu entry,
+ * showing a list of recently opened files.</li>
+ * <li>Associated list of {@link RibbonApplicationMenuEntrySecondary}s added
+ * with the
+ * {@link #addSecondaryMenuGroup(String, RibbonApplicationMenuEntrySecondary...)}
+ * API. When this entry is armed (with mouse rollover or via keyboard
+ * navigation), the secondary area shows menu buttons for the registered
+ * secondary menu entries. The <code>Save As</code> menu item is an example of
+ * such a primary menu item, showing a list of default save formats.</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RibbonApplicationMenuEntryPrimary extends
+ RibbonApplicationMenuEntry {
+ /**
+ * An optional rollover callback. It allows the application to place custom
+ * content in the secondary panel of the {@link RibbonApplicationMenu} when
+ * this primary menu entry is activated.
+ *
+ * @see #setRolloverCallback(PrimaryRolloverCallback)
+ * @see #getRolloverCallback()
+ */
+ protected PrimaryRolloverCallback rolloverCallback;
+
+ /**
+ * Callback that allows application code to provide custom content on the
+ * secondary panel of the {@link RibbonApplicationMenu}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static interface PrimaryRolloverCallback {
+ /**
+ * Called when the matching primary menu item is activated.
+ *
+ * @param targetPanel
+ * The secondary panel of the {@link RibbonApplicationMenu}.
+ * Note that the application code <strong>must not</strong>
+ * change the parent hierarchy of this panel.
+ */
+ public void menuEntryActivated(JPanel targetPanel);
+ }
+
+ /**
+ * List of titles for all menu groups.
+ */
+ protected List<String> groupTitles;
+
+ /**
+ * List of all menu groups.
+ */
+ protected List<List<RibbonApplicationMenuEntrySecondary>> groupEntries;
+
+ /**
+ * Creates the metadata description of a {@link RibbonApplicationMenu}
+ * primary menu entry.
+ *
+ * @param icon
+ * The icon of this menu entry. Must be non-<code>null</code>.
+ * @param text
+ * The text of this menu entry. Must be non-<code>null</code>.
+ * @param mainActionListener
+ * The main action listener for this menu entry. If the entry
+ * kind is {@link CommandButtonKind#POPUP_ONLY}, this listener
+ * will be ignored.
+ * @param entryKind
+ * The kind of the command button that will represent this menu
+ * entry. Must be non- <code>null</code>.
+ */
+ public RibbonApplicationMenuEntryPrimary(ResizableIcon icon, String text,
+ ActionListener mainActionListener, CommandButtonKind entryKind) {
+ super(icon, text, mainActionListener, entryKind);
+ this.groupTitles = new ArrayList<String>();
+ this.groupEntries = new ArrayList<List<RibbonApplicationMenuEntrySecondary>>();
+ }
+
+ /**
+ * Adds a titled group of secondary menu entries.
+ *
+ * @param groupTitle
+ * The title of the group.
+ * @param entries
+ * The secondary menu entries belonging to this group.
+ * @return The index of the newly added menu group.
+ * @see #getSecondaryGroupCount()
+ * @see #getSecondaryGroupTitleAt(int)
+ * @see #getSecondaryGroupEntries(int)
+ */
+ public synchronized int addSecondaryMenuGroup(String groupTitle,
+ RibbonApplicationMenuEntrySecondary... entries) {
+ this.groupTitles.add(groupTitle);
+ List<RibbonApplicationMenuEntrySecondary> entryList = new ArrayList<RibbonApplicationMenuEntrySecondary>();
+ this.groupEntries.add(entryList);
+ for (RibbonApplicationMenuEntrySecondary entry : entries) {
+ entryList.add(entry);
+ }
+ return this.groupTitles.size() - 1;
+ }
+
+ /**
+ * Returns the number of secondary menu groups of this primary menu entry.
+ *
+ * @return The number of secondary menu groups of this primary menu entry.
+ * @see #addSecondaryMenuGroup(String,
+ * RibbonApplicationMenuEntrySecondary...)
+ * @see #getSecondaryGroupTitleAt(int)
+ * @see #getSecondaryGroupEntries(int)
+ */
+ public int getSecondaryGroupCount() {
+ return this.groupTitles.size();
+ }
+
+ /**
+ * Returns the title of the secondary menu group at the specified index.
+ *
+ * @param groupIndex
+ * The index of a secondary menu group.
+ * @return The title of the secondary menu group at the specified index.
+ * @see #addSecondaryMenuGroup(String,
+ * RibbonApplicationMenuEntrySecondary...)
+ * @see #getSecondaryGroupCount()
+ * @see #getSecondaryGroupEntries(int)
+ */
+ public String getSecondaryGroupTitleAt(int groupIndex) {
+ return this.groupTitles.get(groupIndex);
+ }
+
+ /**
+ * Returns an unmodifiable list of menu entries of the secondary menu group
+ * at the specified index.
+ *
+ * @param groupIndex
+ * The index of a secondary menu group.
+ * @return An unmodifiable list of menu entries of the secondary menu group
+ * at the specified index.
+ * @see #addSecondaryMenuGroup(String,
+ * RibbonApplicationMenuEntrySecondary...)
+ * @see #getSecondaryGroupCount()
+ * @see #getSecondaryGroupTitleAt(int)
+ */
+ public List<RibbonApplicationMenuEntrySecondary> getSecondaryGroupEntries(
+ int groupIndex) {
+ return Collections.unmodifiableList(this.groupEntries.get(groupIndex));
+ }
+
+ /**
+ * Sets the rollover callback that allows the application to place custom
+ * content in the secondary panel of the {@link RibbonApplicationMenu} when
+ * this primary menu entry is activated.
+ *
+ * @param rolloverCallback
+ * The new rollover callback for populating the secondary panel
+ * of the {@link RibbonApplicationMenu}.
+ * @see #getRolloverCallback()
+ */
+ public void setRolloverCallback(PrimaryRolloverCallback rolloverCallback) {
+ this.rolloverCallback = rolloverCallback;
+ }
+
+ /**
+ * Returns the current application callback that allows placing custom
+ * content in the secondary panel of the {@link RibbonApplicationMenu} when
+ * this primary menu entry is activated.
+ *
+ * @return The current rollover callback for populating the secondary panel
+ * of the {@link RibbonApplicationMenu}.
+ * @see #setRolloverCallback(PrimaryRolloverCallback)
+ */
+ public PrimaryRolloverCallback getRolloverCallback() {
+ return rolloverCallback;
+ }
+
+ /**
+ * Changes the title of the specified group.
+ *
+ * @param groupIndex
+ * Group index.
+ * @param newTitle
+ * New title for the specified group.
+ */
+ public synchronized void setSecondaryGroupTitle(int groupIndex,
+ String newTitle) {
+ this.groupTitles.set(groupIndex, newTitle);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenuEntrySecondary.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenuEntrySecondary.java
new file mode 100644
index 0000000..e218cbe
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonApplicationMenuEntrySecondary.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.awt.event.ActionListener;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelCallback;
+
+/**
+ * Metadata description for the secondary menu entries of the
+ * {@link RibbonApplicationMenu}. The secondary menu entries at runtime are
+ * represented by command menu buttons placed in the right panel of the
+ * application menu.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RibbonApplicationMenuEntrySecondary extends
+ RibbonApplicationMenuEntry {
+ /**
+ * Extra description text for this secondary menu entry.
+ *
+ * @see #getDescriptionText()
+ * @see #setDescriptionText(String)
+ */
+ protected String descriptionText;
+
+ /**
+ * Popup callback for this menu entry. Must be not <code>null</code> if the
+ * menu entry kind has popup part.
+ *
+ * @see #getPopupCallback()
+ * @see #setPopupCallback(PopupPanelCallback)
+ */
+ protected PopupPanelCallback popupCallback;
+
+ /**
+ * Creates the metadata description of a {@link RibbonApplicationMenu}
+ * secondary menu entry.
+ *
+ * @param icon
+ * The icon of this menu entry. Must be non-<code>null</code>.
+ * @param text
+ * The text of this menu entry. Must be non-<code>null</code>.
+ * @param mainActionListener
+ * The main action listener for this menu entry. If the entry
+ * kind is {@link CommandButtonKind#POPUP_ONLY}, this listener
+ * will be ignored.
+ * @param entryKind
+ * The kind of the command button that will represent this menu
+ * entry. Must be non- <code>null</code>.
+ */
+ public RibbonApplicationMenuEntrySecondary(ResizableIcon icon, String text,
+ ActionListener mainActionListener, CommandButtonKind entryKind) {
+ super(icon, text, mainActionListener, entryKind);
+ }
+
+ /**
+ * Returns the description text of this secondary menu entry.
+ *
+ * @return The description text of this secondary menu entry.
+ * @see #setDescriptionText(String)
+ */
+ public String getDescriptionText() {
+ return this.descriptionText;
+ }
+
+ /**
+ * Sets the new description text for this secondary menu entry.
+ *
+ * @param descriptionText
+ * The new description text for this secondary menu entry.
+ * @see #getDescriptionText()
+ */
+ public void setDescriptionText(String descriptionText) {
+ this.descriptionText = descriptionText;
+ }
+
+ /**
+ * Sets the popup callback for this secondary menu entry.
+ *
+ * @param popupCallback
+ * The popup callback for this secondary menu entry.
+ * @see #getPopupCallback()
+ */
+ public void setPopupCallback(PopupPanelCallback popupCallback) {
+ this.popupCallback = popupCallback;
+ }
+
+ /**
+ * Returns the current popup callback of this secondary menu entry.
+ *
+ * @return The current popup callback of this secondary menu entry.
+ * @see #setPopupCallback(PopupPanelCallback)
+ */
+ public PopupPanelCallback getPopupCallback() {
+ return popupCallback;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonContextualTaskGroup.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonContextualTaskGroup.java
new file mode 100644
index 0000000..9c8eb68
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonContextualTaskGroup.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.awt.Color;
+import java.util.ArrayList;
+
+/**
+ * A contextual group of {@link RibbonTask}s. The contextual ribbon task groups
+ * allow showing and hiding ribbon tasks based on the current selection in the
+ * application. For example, Word only shows the table tasks when a table is
+ * selected in the document. By default, tasks belonging to the groups added by
+ * {@link JRibbon#addContextualTaskGroup(RibbonContextualTaskGroup)} are not
+ * visible. To show the tasks belonging to the specific group, call
+ * {@link JRibbon#setVisible(RibbonContextualTaskGroup, boolean)} API. Note that
+ * you can have multiple task groups visible at the same time. This class is a
+ * logical entity that groups ribbon tasks belonging to the same contextual
+ * group.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RibbonContextualTaskGroup {
+ /**
+ * The ribbon that contains this task group.
+ */
+ private JRibbon ribbon;
+
+ /**
+ * List of all tasks.
+ *
+ * @see #RibbonContextualTaskGroup(String, Color, RibbonTask...)
+ * @see #getTaskCount()
+ * @see #getTask(int)
+ */
+ private ArrayList<RibbonTask> tasks;
+
+ /**
+ * Group title.
+ *
+ * @see #RibbonContextualTaskGroup(String, Color, RibbonTask...)
+ * @see #getTitle()
+ * @see #setTitle(String)
+ */
+ private String title;
+
+ /**
+ * Hue color for this group.
+ *
+ * @see #RibbonContextualTaskGroup(String, Color, RibbonTask...)
+ * @see #getHueColor()
+ */
+ private Color hueColor;
+
+ /**
+ * Alpha factor for colorizing the toggle tab buttons of tasks in contextual
+ * groups.
+ */
+ public static final double HUE_ALPHA = 0.25;
+
+ /**
+ * Creates a task contextual group that contains the specified tasks.
+ *
+ * @param title
+ * Group title.
+ * @param hueColor
+ * Hue color for this group. Should be a saturated non-dark color
+ * for good visuals.
+ * @param tasks
+ * Tasks to add to the group.
+ */
+ public RibbonContextualTaskGroup(String title, Color hueColor,
+ RibbonTask... tasks) {
+ this.title = title;
+ this.hueColor = hueColor;
+ this.tasks = new ArrayList<RibbonTask>();
+ for (RibbonTask ribbonTask : tasks) {
+ ribbonTask.setContextualGroup(this);
+ this.tasks.add(ribbonTask);
+ }
+ }
+
+ /**
+ * Returns the number of tasks in <code>this</code> group.
+ *
+ * @return Number of tasks in <code>this</code> group.
+ * @see #getTask(int)
+ */
+ public int getTaskCount() {
+ return this.tasks.size();
+ }
+
+ /**
+ * Returns task at the specified index from <code>this</code> group.
+ *
+ * @param index
+ * Task index.
+ * @return Task at the specified index.
+ * @see #getTaskCount()
+ */
+ public RibbonTask getTask(int index) {
+ return this.tasks.get(index);
+ }
+
+ /**
+ * Returns the name of this group.
+ *
+ * @return The name of this group.
+ * @see #setTitle(String)
+ */
+ public String getTitle() {
+ return this.title;
+ }
+
+ /**
+ * Returns the hue color for this group.
+ *
+ * @return The hue color for this group.
+ */
+ public Color getHueColor() {
+ return this.hueColor;
+ }
+
+ /**
+ * Changes the title of this ribbon contextual task group.
+ *
+ * @param title
+ * The new title for this ribbon contextual task group.
+ * @see #getTitle()
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ if (this.ribbon != null)
+ this.ribbon.fireStateChanged();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return getTitle() + " (" + getTaskCount() + " tasks)";
+ }
+
+ /**
+ * Associates this ribbon contextual task group with the specified ribbon.
+ * This method is package protected and is for internal use only.
+ *
+ * @param ribbon
+ * The associated ribbon.
+ */
+ void setRibbon(JRibbon ribbon) {
+ if (this.ribbon != null) {
+ throw new IllegalStateException(
+ "The contextual task group already belongs to another ribbon");
+ }
+ this.ribbon = ribbon;
+ }
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonElementPriority.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonElementPriority.java
new file mode 100644
index 0000000..2b43a61
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonElementPriority.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+/**
+ * Priority of ribbon band components.
+ *
+ * @author Kirill Grouchnikov
+ */
+public enum RibbonElementPriority {
+ /**
+ * Top priority.
+ */
+ TOP,
+
+ /**
+ * Medium priority.
+ */
+ MEDIUM,
+
+ /**
+ * Low priority.
+ */
+ LOW;
+}
\ No newline at end of file
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonTask.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonTask.java
new file mode 100644
index 0000000..5c563bc
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/RibbonTask.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon;
+
+import java.util.*;
+
+import org.pushingpixels.flamingo.api.ribbon.resize.CoreRibbonResizeSequencingPolicies;
+import org.pushingpixels.flamingo.api.ribbon.resize.RibbonBandResizeSequencingPolicy;
+
+/**
+ * Single ribbon task in {@link JRibbon}. This is a logical entity that groups
+ * {@link AbstractRibbonBand} components.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RibbonTask {
+ /**
+ * The associated ribbon.
+ */
+ private JRibbon ribbon;
+
+ /**
+ * List of all bands.
+ */
+ private ArrayList<AbstractRibbonBand<?>> bands;
+
+ /**
+ * The title of this task.
+ */
+ private String title;
+
+ /**
+ * The group that this band belongs to. For regular ribbon bands this field
+ * is <code>null</code>.
+ */
+ private RibbonContextualTaskGroup contextualGroup;
+
+ /**
+ * The current resize sequencing policy.
+ */
+ private RibbonBandResizeSequencingPolicy resizeSequencingPolicy;
+
+ /**
+ * The key tip for the task button of this task.
+ */
+ private String keyTip;
+
+ /**
+ * Creates a ribbon task that contains the specified bands.
+ *
+ * @param title
+ * Ribbon task title.
+ * @param bands
+ * Bands to add to the ribbon task.
+ */
+ public RibbonTask(String title, AbstractRibbonBand<?>... bands) {
+ if ((bands == null) || (bands.length == 0)) {
+ throw new IllegalArgumentException("Cannot have empty ribbon task");
+ }
+ this.title = title;
+ this.bands = new ArrayList<AbstractRibbonBand<?>>();
+ for (AbstractRibbonBand<?> band : bands) {
+ band.setRibbonTask(this);
+ this.bands.add(band);
+ }
+ this.resizeSequencingPolicy = new CoreRibbonResizeSequencingPolicies.RoundRobin(
+ this);
+ }
+
+ /**
+ * Returns the number of bands in <code>this</code> task.
+ *
+ * @return Number of bands in <code>this</code> task.
+ * @see #getBand(int)
+ * @see #getBands()
+ */
+ public int getBandCount() {
+ return this.bands.size();
+ }
+
+ /**
+ * Returns band at the specified index from <code>this</code> task.
+ *
+ * @param index
+ * Band index.
+ * @return Band at the specified index.
+ * @see #getBandCount()
+ * @see #getBands()
+ */
+ public AbstractRibbonBand<?> getBand(int index) {
+ return this.bands.get(index);
+ }
+
+ /**
+ * Returns the title of this task.
+ *
+ * @return The title of this task.
+ */
+ public String getTitle() {
+ return this.title;
+ }
+
+ /**
+ * Sets the contextual task group for this ribbon task. This method is
+ * package protected and is for internal use only.
+ *
+ * @param contextualGroup
+ * The contextual task group for this ribbon task.
+ * @see #getContextualGroup()
+ */
+ void setContextualGroup(RibbonContextualTaskGroup contextualGroup) {
+ if (this.contextualGroup != null) {
+ throw new IllegalStateException(
+ "The task already belongs to another contextual task group");
+ }
+ this.contextualGroup = contextualGroup;
+ }
+
+ /**
+ * Returns the contextual task group for this ribbon task. Will return
+ * <code>null</code> for general ribbon tasks.
+ *
+ * @return The contextual task group for this ribbon task.
+ */
+ public RibbonContextualTaskGroup getContextualGroup() {
+ return this.contextualGroup;
+ }
+
+ /**
+ * Returns an unmodifiable view on the ribbon bands of this task.
+ *
+ * @return Unmodifiable view on the ribbon bands of this task.
+ * @see #getBandCount()
+ * @see #getBand(int)
+ */
+ public List<AbstractRibbonBand<?>> getBands() {
+ return Collections.unmodifiableList(this.bands);
+ }
+
+ /**
+ * Changes the title of this ribbon task.
+ *
+ * @param title
+ * The new title for this ribbon task.
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ if (this.ribbon != null)
+ this.ribbon.fireStateChanged();
+ }
+
+ /**
+ * Associates this ribbon task with the specified ribbon. This method is
+ * package protected and is for internal use only.
+ *
+ * @param ribbon
+ * The associated ribbon.
+ */
+ void setRibbon(JRibbon ribbon) {
+ if (this.ribbon != null) {
+ throw new IllegalStateException(
+ "The task already belongs to another ribbon");
+ }
+ this.ribbon = ribbon;
+ }
+
+ /**
+ * Returns the current resize sequencing policy of this ribbon task.
+ *
+ * @return The current resize sequencing policy of this ribbon task.
+ * @see #setResizeSequencingPolicy(RibbonBandResizeSequencingPolicy)
+ */
+ public RibbonBandResizeSequencingPolicy getResizeSequencingPolicy() {
+ return this.resizeSequencingPolicy;
+ }
+
+ /**
+ * Sets the specified parameter as the new resize sequencing policy of this
+ * ribbon task.
+ *
+ * @param resizeSequencingPolicy
+ * The new resize sequencing policy of this ribbon task.
+ * @see #getResizeSequencingPolicy()
+ */
+ public void setResizeSequencingPolicy(
+ RibbonBandResizeSequencingPolicy resizeSequencingPolicy) {
+ this.resizeSequencingPolicy = resizeSequencingPolicy;
+ }
+
+ /**
+ * Returns the key tip for the task button of this task.
+ *
+ * @return The key tip for the task button of this task.
+ * @see #setKeyTip(String)
+ */
+ public String getKeyTip() {
+ return this.keyTip;
+ }
+
+ /**
+ * Sets the specified parameter to be the new key tip for the task button of
+ * this task.
+ *
+ * @param keyTip
+ * The new key tip for the task button of this task.
+ */
+ public void setKeyTip(String keyTip) {
+ this.keyTip = keyTip;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/BaseRibbonBandResizePolicy.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/BaseRibbonBandResizePolicy.java
new file mode 100644
index 0000000..bc4e915
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/BaseRibbonBandResizePolicy.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon.resize;
+
+import org.pushingpixels.flamingo.internal.ui.ribbon.AbstractBandControlPanel;
+
+/**
+ * Base class for the core ribbon band resize policies.
+ *
+ * @author Kirill Grouchnikov
+ * @param <T>
+ * Class parameter that specifies the type of band control panel
+ * implementation.
+ */
+public abstract class BaseRibbonBandResizePolicy<T extends AbstractBandControlPanel>
+ implements RibbonBandResizePolicy {
+ /**
+ * The control panel of the associated ribbon band.
+ */
+ protected T controlPanel;
+
+ /**
+ * Creates a new resize policy.
+ *
+ * @param controlPanel
+ * The control panel of the associated ribbon band.
+ */
+ protected BaseRibbonBandResizePolicy(T controlPanel) {
+ this.controlPanel = controlPanel;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/BaseRibbonBandResizeSequencingPolicy.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/BaseRibbonBandResizeSequencingPolicy.java
new file mode 100644
index 0000000..f7a804b
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/BaseRibbonBandResizeSequencingPolicy.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon.resize;
+
+import org.pushingpixels.flamingo.api.ribbon.RibbonTask;
+
+/**
+ * Base class for the core ribbon band resize sequencing policies.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class BaseRibbonBandResizeSequencingPolicy implements
+ RibbonBandResizeSequencingPolicy {
+ /**
+ * The associated ribbon task.
+ */
+ protected RibbonTask ribbonTask;
+
+ /**
+ * Creates a new resize sequencing policy.
+ *
+ * @param ribbonTask
+ * The associated ribbon task.
+ */
+ protected BaseRibbonBandResizeSequencingPolicy(RibbonTask ribbonTask) {
+ this.ribbonTask = ribbonTask;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/CoreRibbonResizePolicies.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/CoreRibbonResizePolicies.java
new file mode 100644
index 0000000..81b4eeb
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/CoreRibbonResizePolicies.java
@@ -0,0 +1,966 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon.resize;
+
+import java.awt.Insets;
+import java.util.*;
+
+import javax.swing.JComponent;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.common.CommandButtonDisplayState;
+import org.pushingpixels.flamingo.api.ribbon.*;
+import org.pushingpixels.flamingo.internal.ui.ribbon.*;
+
+/**
+ * The core resize policies. Provides a number of built in resize policies that
+ * respect the application element priorities passed to
+ * {@link JRibbonBand#addCommandButton(org.pushingpixels.flamingo.api.common.AbstractCommandButton, org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority)}
+ * and
+ * {@link JRibbonBand#addRibbonGallery(String, java.util.List, java.util.Map, int, int, org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority)}
+ * APIs. There are three types of built in resize policies: </p>
+ *
+ * <ul>
+ * <li>Resize policies for the {@link JFlowRibbonBand}s. The {@link FlowTwoRows}
+ * and {@link FlowThreeRows} allow placing the flow ribbon band content in two
+ * and three rows respectively.</li>
+ * <li>Resize policies for the {@link JRibbonBand}s. The
+ * {@link BaseCoreRibbonBandResizePolicy} is the base class for these policies.
+ * These policies respect the {@link RibbonElementPriority} associated on
+ * command buttons and ribbon galleries in
+ * {@link RibbonBandResizePolicy#getPreferredWidth(int, int)} and
+ * {@link RibbonBandResizePolicy#install(int, int)}. While
+ * {@link RibbonBandResizePolicy#install(int, int)} call on a
+ * {@link JFlowRibbonBand} only changes the bounds of the flow components, this
+ * call on a {@link JRibbonBand} can also change the display state of the
+ * command buttons (with
+ * {@link AbstractCommandButton#setDisplayState(org.pushingpixels.flamingo.api.common.CommandButtonDisplayState)}
+ * ) and the number of visible buttons in the ribbon galleries.</li>
+ * <li>The collapsed policy that replaces the entire content of the ribbon band
+ * with a single popup button. This is done when there is not enough horizontal
+ * space to show the content of the ribbon band under the most restrictive
+ * resize policy. Activating the popup button will show the original content
+ * under the most permissive resize policy in a popup. This policy is
+ * implemented in the {@link IconRibbonBandResizePolicy}.</li>
+ * </ul>
+ *
+ * <p>
+ * In addition to the specific resize policies, this class provides three core
+ * resize policies lists for {@link JRibbonBand}s:
+ * </p>
+ *
+ * <ul>
+ * <li>{@link #getCorePoliciesPermissive(JRibbonBand)} returns a list that
+ * starts with a resize policy that shows all command buttons in the
+ * {@link CommandButtonDisplayState#BIG} and ribbon galleries with the largest
+ * number of visible buttons, fully utilizing the available screen space.</li>
+ * <li>{@link #getCorePoliciesRestrictive(JRibbonBand)} returns a list that
+ * starts with a resize policy that respects the associated ribbon element
+ * priority set on the specific components.</li>
+ * <li> {@link #getCorePoliciesNone(JRibbonBand)} returns a list that only has a
+ * <code>mirror</code> resize policy that respects the associated ribbon element
+ * priority set on the specific components.</li>
+ * </ul>
+ *
+ * <p>
+ * Note that as mentioned above, all the three lists above have the
+ * <code>collapsed</code> policy as their last element.
+ * </p>
+ *
+ * <p>
+ * In addition, the
+ * {@link #getCoreFlowPoliciesRestrictive(JFlowRibbonBand, int)} returns a
+ * restrictive resize policy for {@link JFlowRibbonBand}s. The list starts with
+ * the two-row policy, goes to the three-row policy and then finally to the
+ * collapsed policy.
+ * </p>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class CoreRibbonResizePolicies {
+ /**
+ * Maps the element priority associated with a ribbon band component to the
+ * element priority assigned by the specific resize policy.
+ *
+ * @author Kirill Grouchnikov
+ */
+ static interface Mapping {
+ /**
+ * Maps the element priority associated with a ribbon band component to
+ * the element priority assigned by the specific resize policy.
+ *
+ * @param priority
+ * The element priority associated with a ribbon band
+ * component
+ * @return The element priority assigned by the specific resize policy.
+ */
+ RibbonElementPriority map(RibbonElementPriority priority);
+ }
+
+ /**
+ * Returns a list that starts with a resize policy that shows all command
+ * buttons in the {@link CommandButtonDisplayState#BIG} and ribbon galleries
+ * with the largest number of visible buttons. The last entry is the
+ * {@link IconRibbonBandResizePolicy}.
+ *
+ * @param ribbonBand
+ * Ribbon band.
+ * @return The permissive list of core ribbon band resize policies.
+ */
+ public static List<RibbonBandResizePolicy> getCorePoliciesPermissive(
+ JRibbonBand ribbonBand) {
+ List<RibbonBandResizePolicy> result = new ArrayList<RibbonBandResizePolicy>();
+ result.add(new CoreRibbonResizePolicies.None(ribbonBand
+ .getControlPanel()));
+ result.add(new CoreRibbonResizePolicies.Low2Mid(ribbonBand
+ .getControlPanel()));
+ result.add(new CoreRibbonResizePolicies.Mid2Mid(ribbonBand
+ .getControlPanel()));
+ result.add(new CoreRibbonResizePolicies.Mirror(ribbonBand
+ .getControlPanel()));
+ result.add(new CoreRibbonResizePolicies.Mid2Low(ribbonBand
+ .getControlPanel()));
+ result.add(new CoreRibbonResizePolicies.High2Mid(ribbonBand
+ .getControlPanel()));
+ result.add(new CoreRibbonResizePolicies.High2Low(ribbonBand
+ .getControlPanel()));
+ result
+ .add(new IconRibbonBandResizePolicy(ribbonBand
+ .getControlPanel()));
+ return result;
+ }
+
+ /**
+ * Returns a list that starts with a resize policy that respects the
+ * associated ribbon element priority set on the specific components. The
+ * last entry is the {@link IconRibbonBandResizePolicy}.
+ *
+ * @param ribbonBand
+ * Ribbon band.
+ * @return The restrictive list of core ribbon band resize policies.
+ */
+ public static List<RibbonBandResizePolicy> getCorePoliciesRestrictive(
+ JRibbonBand ribbonBand) {
+ List<RibbonBandResizePolicy> result = new ArrayList<RibbonBandResizePolicy>();
+ result.add(new CoreRibbonResizePolicies.Mirror(ribbonBand
+ .getControlPanel()));
+ result.add(new CoreRibbonResizePolicies.Mid2Low(ribbonBand
+ .getControlPanel()));
+ result.add(new CoreRibbonResizePolicies.High2Mid(ribbonBand
+ .getControlPanel()));
+ result.add(new CoreRibbonResizePolicies.High2Low(ribbonBand
+ .getControlPanel()));
+ result
+ .add(new IconRibbonBandResizePolicy(ribbonBand
+ .getControlPanel()));
+ return result;
+ }
+
+ /**
+ * Returns a list that only has a <code>mirror</code> resize policy that
+ * respects the associated ribbon element priority set on the specific
+ * components. The last entry is the {@link IconRibbonBandResizePolicy}.
+ *
+ * @param ribbonBand
+ * Ribbon band.
+ * @return The mirror list of core ribbon band resize policies.
+ */
+ public static List<RibbonBandResizePolicy> getCorePoliciesNone(
+ JRibbonBand ribbonBand) {
+ List<RibbonBandResizePolicy> result = new ArrayList<RibbonBandResizePolicy>();
+ result.add(new CoreRibbonResizePolicies.Mirror(ribbonBand
+ .getControlPanel()));
+ result
+ .add(new IconRibbonBandResizePolicy(ribbonBand
+ .getControlPanel()));
+ return result;
+ }
+
+ /**
+ * The base class for mapping-based core resize policies.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected static abstract class BaseCoreRibbonBandResizePolicy extends
+ BaseRibbonBandResizePolicy<JBandControlPanel> {
+ /**
+ * The element priority mapping.
+ */
+ protected Mapping mapping;
+
+ /**
+ * Creates a new resize policy.
+ *
+ * @param controlPanel
+ * The control panel of the associated ribbon band.
+ * @param mapping
+ * The element priority mapping.
+ */
+ protected BaseCoreRibbonBandResizePolicy(
+ JBandControlPanel controlPanel, Mapping mapping) {
+ super(controlPanel);
+ this.mapping = mapping;
+ }
+
+ /**
+ * Returns the total width of the specified buttons.
+ *
+ * @param gap
+ * Inter component gap.
+ * @param bigButtons
+ * List of buttons in big display state.
+ * @param mediumButtons
+ * List of buttons in medium display state.
+ * @param smallButtons
+ * List of buttons in small display state.
+ * @return Total width of the specified buttons.
+ */
+ protected int getWidth(int gap,
+ java.util.List<AbstractCommandButton> bigButtons,
+ java.util.List<AbstractCommandButton> mediumButtons,
+ java.util.List<AbstractCommandButton> smallButtons) {
+ int result = 0;
+ boolean hasLeadingContent = false;
+ for (AbstractCommandButton top : bigButtons) {
+ if (hasLeadingContent) {
+ result += gap;
+ }
+ result += getPreferredWidth(top, RibbonElementPriority.TOP);
+ hasLeadingContent = true;
+ }
+
+ int medSize = mediumButtons.size();
+ if (medSize > 0) {
+ // try to move buttons from low to med to make
+ // three-somes.
+ while (((mediumButtons.size() % 3) != 0)
+ && (smallButtons.size() > 0)) {
+ AbstractCommandButton low = smallButtons.remove(0);
+ mediumButtons.add(low);
+ }
+ }
+
+ // at this point, mediumButtons list contains either
+ // threesomes, or there are no buttons in lowButtons.
+ int index3 = 0;
+ int maxWidth3 = 0;
+ for (AbstractCommandButton medium : mediumButtons) {
+ int medWidth = getPreferredWidth(medium,
+ RibbonElementPriority.MEDIUM);
+ maxWidth3 = Math.max(maxWidth3, medWidth);
+ index3++;
+
+ if (index3 == 3) {
+ // last button in threesome
+ index3 = 0;
+ if (hasLeadingContent) {
+ result += gap;
+ }
+ result += maxWidth3;
+ hasLeadingContent = true;
+ maxWidth3 = 0;
+ }
+ }
+ // at this point, maxWidth3 may be non-zero. We can safely
+ // add it, since in this case there will be no buttons
+ // left in mediumButtons
+ if (maxWidth3 > 0) {
+ if (hasLeadingContent) {
+ result += gap;
+ }
+ result += maxWidth3;
+ hasLeadingContent = true;
+ }
+
+ index3 = 0;
+ maxWidth3 = 0;
+ for (AbstractCommandButton low : smallButtons) {
+ int lowWidth = getPreferredWidth(low, RibbonElementPriority.LOW);
+ maxWidth3 = Math.max(maxWidth3, lowWidth);
+ index3++;
+
+ if (index3 == 3) {
+ // last button in threesome
+ index3 = 0;
+ if (hasLeadingContent) {
+ result += gap;
+ }
+ result += maxWidth3;
+ hasLeadingContent = true;
+ maxWidth3 = 0;
+ }
+ }
+ // at this point, maxWidth3 may be non-zero. We can safely
+ // add it, since in this case there will be no buttons left
+ if (maxWidth3 > 0) {
+ if (hasLeadingContent) {
+ result += gap;
+ }
+ result += maxWidth3;
+ hasLeadingContent = true;
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the preferred width of the specified command button under the
+ * specified display priority.
+ *
+ * @param button
+ * Command button.
+ * @param buttonDisplayPriority
+ * Button display priority.
+ * @return The preferred width of the specified command button under the
+ * specified display priority.
+ */
+ private int getPreferredWidth(AbstractCommandButton button,
+ RibbonElementPriority buttonDisplayPriority) {
+ CommandButtonDisplayState displayState = null;
+ switch (buttonDisplayPriority) {
+ case TOP:
+ displayState = CommandButtonDisplayState.BIG;
+ break;
+ case MEDIUM:
+ displayState = CommandButtonDisplayState.MEDIUM;
+ break;
+ case LOW:
+ displayState = CommandButtonDisplayState.SMALL;
+ break;
+ }
+ return displayState.createLayoutManager(button).getPreferredSize(
+ button).width;
+ }
+
+ @Override
+ public int getPreferredWidth(int availableHeight, int gap) {
+ int result = 0;
+
+ Insets ins = this.controlPanel.getInsets();
+
+ for (JBandControlPanel.ControlPanelGroup controlPanelGroup : this.controlPanel
+ .getControlPanelGroups()) {
+ boolean isCoreContent = controlPanelGroup.isCoreContent();
+ if (isCoreContent) {
+ List<JRibbonComponent> ribbonComps = controlPanelGroup
+ .getRibbonComps();
+ Map<JRibbonComponent, Integer> ribbonCompRowSpans = controlPanelGroup
+ .getRibbonCompsRowSpans();
+ // if a group has a title, then the core components in that
+ // group start from the second row
+ int startRowIndex = (controlPanelGroup.getGroupTitle() == null) ? 0
+ : 1;
+ int rowIndex = startRowIndex;
+ int maxWidthInCurrColumn = 0;
+ for (int i = 0; i < ribbonComps.size(); i++) {
+ JRibbonComponent ribbonComp = ribbonComps.get(i);
+ int rowSpan = ribbonCompRowSpans.get(ribbonComp);
+
+ // do we need to start a new column?
+ int nextRowIndex = rowIndex + rowSpan;
+ if (nextRowIndex > 3) {
+ result += maxWidthInCurrColumn;
+ result += gap;
+ maxWidthInCurrColumn = 0;
+ rowIndex = startRowIndex;
+ }
+
+ RibbonElementPriority targetPriority = RibbonElementPriority.TOP;
+ if (ribbonComp.isResizingAware()) {
+ targetPriority = this.mapping
+ .map(RibbonElementPriority.TOP);
+ }
+ int prefWidth = ribbonComp.getUI().getPreferredSize(
+ targetPriority).width;
+ maxWidthInCurrColumn = Math.max(maxWidthInCurrColumn,
+ prefWidth);
+ rowIndex += rowSpan;
+ }
+ if ((rowIndex > 0) && (rowIndex <= 3)) {
+ result += maxWidthInCurrColumn;
+ result += gap;
+ }
+ } else {
+ int galleryAvailableHeight = availableHeight - ins.top
+ - ins.bottom;
+ // ribbon galleries
+ result += this.getPreferredGalleryWidth(controlPanelGroup,
+ galleryAvailableHeight, gap);
+
+ // ribbon buttons
+ result += this.getPreferredButtonWidth(controlPanelGroup,
+ gap);
+ }
+
+ result += gap * 3 / 2;
+ }
+ // no gap after the last group
+ result -= gap * 3 / 2;
+
+ // control panel insets
+ result += ins.left + ins.right;
+ result += gap;
+
+ return result;
+ }
+
+ /**
+ * Returns the preferred width of all the buttons in the specified
+ * control panel group.
+ *
+ * @param controlPanelGroup
+ * A single control panel group in the associated ribbon
+ * band.
+ * @param gap
+ * Inter component gap.
+ * @return The preferred width of all the buttons in the specified
+ * control panel group.
+ */
+ protected int getPreferredButtonWidth(
+ JBandControlPanel.ControlPanelGroup controlPanelGroup, int gap) {
+ Map<RibbonElementPriority, List<AbstractCommandButton>> mapped = new HashMap<RibbonElementPriority, List<AbstractCommandButton>>();
+ for (RibbonElementPriority rep : RibbonElementPriority.values()) {
+ mapped.put(rep, new ArrayList<AbstractCommandButton>());
+ }
+
+ for (RibbonElementPriority elementPriority : RibbonElementPriority
+ .values()) {
+ // map the priority
+ RibbonElementPriority mappedPriority = mapping
+ .map(elementPriority);
+ for (AbstractCommandButton button : controlPanelGroup
+ .getRibbonButtons(elementPriority)) {
+ // and add the button to a list based on the mapped priority
+ mapped.get(mappedPriority).add(button);
+ }
+ }
+
+ // at this point, the lists in the 'mapped' contain the buttons
+ // grouped by the priority computed by the resize policy.
+ return this.getWidth(gap, mapped.get(RibbonElementPriority.TOP),
+ mapped.get(RibbonElementPriority.MEDIUM), mapped
+ .get(RibbonElementPriority.LOW));
+
+ }
+
+ /**
+ * Returns the preferred width of all the ribbon galleries in the
+ * specified control panel group.
+ *
+ * @param controlPanelGroup
+ * A single control panel group in the associated ribbon
+ * band.
+ * @param galleryAvailableHeight
+ * Available height for the ribbon galleries.
+ * @param gap
+ * Inter component gap.
+ * @return The preferred width of all the ribbon galleries in the
+ * specified control panel group.
+ */
+ private int getPreferredGalleryWidth(
+ JBandControlPanel.ControlPanelGroup controlPanelGroup,
+ int galleryAvailableHeight, int gap) {
+ int result = 0;
+ for (RibbonElementPriority elementPriority : RibbonElementPriority
+ .values()) {
+ // map the priority
+ RibbonElementPriority mappedPriority = mapping
+ .map(elementPriority);
+ // go over all galleries registered with the specific priority
+ for (JRibbonGallery gallery : controlPanelGroup
+ .getRibbonGalleries(elementPriority))
+ // and take the preferred width under the mapped priority
+ result += (gallery.getPreferredWidth(mappedPriority,
+ galleryAvailableHeight) + gap);
+ }
+
+ return result;
+ }
+
+ @Override
+ public void install(int availableHeight, int gap) {
+ for (JBandControlPanel.ControlPanelGroup controlPanelGroup : this.controlPanel
+ .getControlPanelGroups()) {
+ boolean isCoreContent = controlPanelGroup.isCoreContent();
+ if (isCoreContent) {
+ List<JRibbonComponent> ribbonComps = controlPanelGroup
+ .getRibbonComps();
+ for (int i = 0; i < ribbonComps.size(); i++) {
+ JRibbonComponent ribbonComp = ribbonComps.get(i);
+ RibbonElementPriority targetPriority = RibbonElementPriority.TOP;
+ if (ribbonComp.isResizingAware()) {
+ targetPriority = this.mapping
+ .map(RibbonElementPriority.TOP);
+ }
+ ribbonComp.setDisplayPriority(targetPriority);
+ }
+ } else {
+ // set the display priority for the galleries
+ for (RibbonElementPriority elementPriority : RibbonElementPriority
+ .values()) {
+ // map the priority
+ RibbonElementPriority mappedPriority = mapping
+ .map(elementPriority);
+ // go over all galleries registered with the specific
+ // priority
+ for (JRibbonGallery gallery : controlPanelGroup
+ .getRibbonGalleries(elementPriority))
+ // and set the display priority based on the
+ // specific
+ // resize policy
+ gallery.setDisplayPriority(mappedPriority);
+ }
+
+ // set the display priority for the buttons
+ Map<RibbonElementPriority, List<AbstractCommandButton>> mapped = new HashMap<RibbonElementPriority, List<AbstractCommandButton>>();
+ for (RibbonElementPriority rep : RibbonElementPriority
+ .values()) {
+ mapped.put(rep, new ArrayList<AbstractCommandButton>());
+ }
+
+ for (RibbonElementPriority elementPriority : RibbonElementPriority
+ .values()) {
+ // map the priority
+ RibbonElementPriority mappedPriority = mapping
+ .map(elementPriority);
+ for (AbstractCommandButton button : controlPanelGroup
+ .getRibbonButtons(elementPriority)) {
+ // and add the button to a list based on the mapped
+ // priority
+ mapped.get(mappedPriority).add(button);
+ }
+ }
+
+ // start from the top priority
+ for (AbstractCommandButton big : mapped
+ .get(RibbonElementPriority.TOP)) {
+ big.setDisplayState(CommandButtonDisplayState.BIG);
+ }
+
+ // next - medium priority
+ if (mapped.get(RibbonElementPriority.MEDIUM).size() > 0) {
+ // try to move buttons from small to medium to make
+ // three-somes.
+ while (((mapped.get(RibbonElementPriority.MEDIUM)
+ .size() % 3) != 0)
+ && (mapped.get(RibbonElementPriority.LOW)
+ .size() > 0)) {
+ AbstractCommandButton low = mapped.get(
+ RibbonElementPriority.LOW).get(0);
+ mapped.get(RibbonElementPriority.LOW).remove(low);
+ mapped.get(RibbonElementPriority.MEDIUM).add(low);
+ }
+ }
+ for (AbstractCommandButton medium : mapped
+ .get(RibbonElementPriority.MEDIUM)) {
+ medium
+ .setDisplayState(CommandButtonDisplayState.MEDIUM);
+ }
+
+ // finally - low priority
+ for (AbstractCommandButton low : mapped
+ .get(RibbonElementPriority.LOW)) {
+ low.setDisplayState(CommandButtonDisplayState.SMALL);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Core resize policy that maps all {@link RibbonElementPriority}s to
+ * {@link RibbonElementPriority#TOP}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static final class None extends BaseCoreRibbonBandResizePolicy {
+ /**
+ * Creates the new resize policy of type <code>NONE</code>.
+ *
+ * @param controlPanel
+ * The control panel of the associated ribbon band.
+ */
+ public None(JBandControlPanel controlPanel) {
+ super(controlPanel, new Mapping() {
+ @Override
+ public RibbonElementPriority map(RibbonElementPriority priority) {
+ return RibbonElementPriority.TOP;
+ }
+ });
+ }
+ }
+
+ /**
+ * Core resize policy that maps:
+ *
+ * <ul>
+ * <li>{@link RibbonElementPriority#TOP} ->
+ * {@link RibbonElementPriority#TOP}</li>
+ * <li>{@link RibbonElementPriority#MEDIUM} ->
+ * {@link RibbonElementPriority#TOP}</li>
+ * <li>{@link RibbonElementPriority#LOW} ->
+ * {@link RibbonElementPriority#MEDIUM}</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static final class Low2Mid extends BaseCoreRibbonBandResizePolicy {
+ /**
+ * Creates the new resize policy of type <code>LOW2MID</code>.
+ *
+ * @param controlPanel
+ * The control panel of the associated ribbon band.
+ */
+ public Low2Mid(JBandControlPanel controlPanel) {
+ super(controlPanel, new Mapping() {
+ @Override
+ public RibbonElementPriority map(RibbonElementPriority priority) {
+ switch (priority) {
+ case TOP:
+ return RibbonElementPriority.TOP;
+ case MEDIUM:
+ return RibbonElementPriority.TOP;
+ case LOW:
+ return RibbonElementPriority.MEDIUM;
+ }
+ return null;
+ }
+ });
+ }
+ }
+
+ /**
+ * Core resize policy that maps:
+ *
+ * <ul>
+ * <li>{@link RibbonElementPriority#TOP} ->
+ * {@link RibbonElementPriority#TOP}</li>
+ * <li>{@link RibbonElementPriority#MEDIUM} ->
+ * {@link RibbonElementPriority#MEDIUM}</li>
+ * <li>{@link RibbonElementPriority#LOW} ->
+ * {@link RibbonElementPriority#MEDIUM}</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static final class Mid2Mid extends BaseCoreRibbonBandResizePolicy {
+ /**
+ * Creates the new resize policy of type <code>MID2MID</code>.
+ *
+ * @param controlPanel
+ * The control panel of the associated ribbon band.
+ */
+ public Mid2Mid(JBandControlPanel controlPanel) {
+ super(controlPanel, new Mapping() {
+ @Override
+ public RibbonElementPriority map(RibbonElementPriority priority) {
+ switch (priority) {
+ case TOP:
+ return RibbonElementPriority.TOP;
+ case MEDIUM:
+ return RibbonElementPriority.MEDIUM;
+ case LOW:
+ return RibbonElementPriority.MEDIUM;
+ }
+ return null;
+ }
+ });
+ }
+ }
+
+ /**
+ * Mirror core resize policy that maps the values of
+ * {@link RibbonElementPriority}s to themselves.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static final class Mirror extends BaseCoreRibbonBandResizePolicy {
+ /**
+ * Creates the new resize policy of type <code>MIRROR</code>.
+ *
+ * @param controlPanel
+ * The control panel of the associated ribbon band.
+ */
+ public Mirror(JBandControlPanel controlPanel) {
+ super(controlPanel, new Mapping() {
+ @Override
+ public RibbonElementPriority map(RibbonElementPriority priority) {
+ return priority;
+ }
+ });
+ }
+ }
+
+ /**
+ * Core resize policy that maps:
+ *
+ * <ul>
+ * <li>{@link RibbonElementPriority#TOP} ->
+ * {@link RibbonElementPriority#TOP}</li>
+ * <li>{@link RibbonElementPriority#MEDIUM} ->
+ * {@link RibbonElementPriority#LOW}</li>
+ * <li>{@link RibbonElementPriority#LOW} ->
+ * {@link RibbonElementPriority#LOW}</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static final class Mid2Low extends BaseCoreRibbonBandResizePolicy {
+ /**
+ * Creates the new resize policy of type <code>MID2LOW</code>.
+ *
+ * @param controlPanel
+ * The control panel of the associated ribbon band.
+ */
+ public Mid2Low(JBandControlPanel controlPanel) {
+ super(controlPanel, new Mapping() {
+ @Override
+ public RibbonElementPriority map(RibbonElementPriority priority) {
+ switch (priority) {
+ case TOP:
+ return RibbonElementPriority.TOP;
+ case MEDIUM:
+ return RibbonElementPriority.LOW;
+ case LOW:
+ return RibbonElementPriority.LOW;
+ }
+ return null;
+ }
+ });
+ }
+ }
+
+ /**
+ * Core resize policy that maps:
+ *
+ * <ul>
+ * <li>{@link RibbonElementPriority#TOP} ->
+ * {@link RibbonElementPriority#MEDIUM}</li>
+ * <li>{@link RibbonElementPriority#MEDIUM} ->
+ * {@link RibbonElementPriority#LOW}</li>
+ * <li>{@link RibbonElementPriority#LOW} ->
+ * {@link RibbonElementPriority#LOW}</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static final class High2Mid extends BaseCoreRibbonBandResizePolicy {
+ /**
+ * Creates the new resize policy of type <code>HIGH2MID</code>.
+ *
+ * @param controlPanel
+ * The control panel of the associated ribbon band.
+ */
+ public High2Mid(JBandControlPanel controlPanel) {
+ super(controlPanel, new Mapping() {
+ @Override
+ public RibbonElementPriority map(RibbonElementPriority priority) {
+ switch (priority) {
+ case TOP:
+ return RibbonElementPriority.MEDIUM;
+ case MEDIUM:
+ return RibbonElementPriority.LOW;
+ case LOW:
+ return RibbonElementPriority.LOW;
+ }
+ return null;
+ }
+ });
+ }
+ }
+
+ /**
+ * Core resize policy that maps all {@link RibbonElementPriority}s to
+ * {@link RibbonElementPriority#LOW}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static final class High2Low extends BaseCoreRibbonBandResizePolicy {
+ /**
+ * Creates the new resize policy of type <code>HIGH2LOW</code>.
+ *
+ * @param controlPanel
+ * The control panel of the associated ribbon band.
+ */
+ public High2Low(JBandControlPanel controlPanel) {
+ super(controlPanel, new Mapping() {
+ @Override
+ public RibbonElementPriority map(RibbonElementPriority priority) {
+ return RibbonElementPriority.LOW;
+ }
+ });
+ }
+ }
+
+ /**
+ * Core resize policy for {@link JFlowRibbonBand} that places the content in
+ * two rows.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class FlowTwoRows extends
+ BaseRibbonBandResizePolicy<JFlowBandControlPanel> {
+ /**
+ * Creates a new two-row resize policy for {@link JFlowRibbonBand}s.
+ *
+ * @param controlPanel
+ * The control panel of the associated ribbon band.
+ */
+ public FlowTwoRows(JFlowBandControlPanel controlPanel) {
+ super(controlPanel);
+ }
+
+ @Override
+ public int getPreferredWidth(int availableHeight, int gap) {
+ int compCount = controlPanel.getFlowComponents().size();
+ int[] widths = new int[compCount];
+ int index = 0;
+ int currBestResult = 0;
+ for (JComponent flowComp : controlPanel.getFlowComponents()) {
+ int pref = flowComp.getPreferredSize().width;
+ widths[index++] = pref;
+ currBestResult += (pref + gap);
+ }
+
+ // need to find the inflection point that results in
+ // lowest value for max length of two sub-sequences
+ for (int inflectionIndex = 0; inflectionIndex < (compCount - 1); inflectionIndex++) {
+ int w1 = 0;
+ for (int index1 = 0; index1 <= inflectionIndex; index1++) {
+ w1 += widths[index1] + gap;
+ }
+ int w2 = 0;
+ for (int index2 = inflectionIndex + 1; index2 < compCount; index2++) {
+ w2 += widths[index2] + gap;
+ }
+
+ int width = Math.max(w1, w2);
+ if (width < currBestResult)
+ currBestResult = width;
+ }
+
+ return currBestResult;
+ }
+
+ @Override
+ public void install(int availableHeight, int gap) {
+ }
+ }
+
+ /**
+ * Core resize policy for {@link JFlowRibbonBand} that places the content in
+ * three rows.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class FlowThreeRows extends
+ BaseRibbonBandResizePolicy<JFlowBandControlPanel> {
+ /**
+ * Creates a new three-row resize policy for {@link JFlowRibbonBand}s.
+ *
+ * @param controlPanel
+ * The control panel of the associated ribbon band.
+ */
+ public FlowThreeRows(JFlowBandControlPanel controlPanel) {
+ super(controlPanel);
+ }
+
+ @Override
+ public int getPreferredWidth(int availableHeight, int gap) {
+ int compCount = controlPanel.getFlowComponents().size();
+ int[] widths = new int[compCount];
+ int index = 0;
+ int currBestResult = 0;
+ for (JComponent flowComp : controlPanel.getFlowComponents()) {
+ int pref = flowComp.getPreferredSize().width;
+ widths[index++] = pref;
+ currBestResult += (pref + gap);
+ }
+
+ // need to find the inflection points that results in
+ // lowest value for max length of three sub-sequences
+ for (int inflectionIndex1 = 0; inflectionIndex1 < (compCount - 2); inflectionIndex1++) {
+ for (int inflectionIndex2 = inflectionIndex1 + 1; inflectionIndex2 < (compCount - 1); inflectionIndex2++) {
+ int w1 = 0;
+ for (int index1 = 0; index1 <= inflectionIndex1; index1++) {
+ w1 += widths[index1] + gap;
+ }
+ int w2 = 0;
+ for (int index2 = inflectionIndex1 + 1; index2 <= inflectionIndex2; index2++) {
+ w2 += widths[index2] + gap;
+ }
+ int w3 = 0;
+ for (int index3 = inflectionIndex2 + 1; index3 < compCount; index3++) {
+ w3 += widths[index3] + gap;
+ }
+
+ int width = Math.max(Math.max(w1, w2), w3);
+ if (width < currBestResult)
+ currBestResult = width;
+ }
+ }
+
+ return currBestResult;
+ }
+
+ @Override
+ public void install(int availableHeight, int gap) {
+ }
+ }
+
+ /**
+ * Returns a list that has {@link FlowTwoRows} policy followed by the
+ * {@link FlowThreeRows} resize policy. The last entry is the
+ * {@link IconRibbonBandResizePolicy}.
+ *
+ * @param ribbonBand
+ * Ribbon band.
+ * @param stepsToRepeat
+ * The number of times each one of the {@link FlowTwoRows} /
+ * {@link FlowThreeRows} should appear consecutively in the
+ * returned list.
+ * @return The restrictive list of core ribbon band resize policies.
+ */
+ public static List<RibbonBandResizePolicy> getCoreFlowPoliciesRestrictive(
+ JFlowRibbonBand ribbonBand, int stepsToRepeat) {
+ List<RibbonBandResizePolicy> result = new ArrayList<RibbonBandResizePolicy>();
+ for (int i = 0; i < stepsToRepeat; i++) {
+ result.add(new FlowTwoRows(ribbonBand.getControlPanel()));
+ }
+ for (int i = 0; i < stepsToRepeat; i++) {
+ result.add(new FlowThreeRows(ribbonBand.getControlPanel()));
+ }
+ result
+ .add(new IconRibbonBandResizePolicy(ribbonBand
+ .getControlPanel()));
+ return result;
+ }
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/CoreRibbonResizeSequencingPolicies.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/CoreRibbonResizeSequencingPolicies.java
new file mode 100644
index 0000000..df83e2f
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/CoreRibbonResizeSequencingPolicies.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon.resize;
+
+import java.util.List;
+
+import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
+import org.pushingpixels.flamingo.api.ribbon.RibbonTask;
+
+/**
+ * The core resize sequencing policies. Provides the following:
+ *
+ * <ul>
+ * <li>{@link RoundRobin} under which the ribbon bands are being collapsed in a
+ * cyclic fashion, distributing the collapsed pixels between the different
+ * bands.</li>
+ * <li>{@link CollapseFromLast} under which the ribbon bands are being collapsed
+ * from right to left.</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class CoreRibbonResizeSequencingPolicies {
+ /**
+ * The round robin resize sequencing policy. Under this policy the ribbon
+ * bands are being collapsed in a cyclic fashion, distributing the collapsed
+ * pixels between the different bands.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class RoundRobin extends BaseRibbonBandResizeSequencingPolicy {
+ /**
+ * The index of the next ribbon task for collapsing.
+ */
+ int nextIndex;
+
+ /**
+ * Creates a new round robin resize sequencing policy for the specified
+ * task.
+ *
+ * @param ribbonTask
+ * Ribbon task.
+ */
+ public RoundRobin(RibbonTask ribbonTask) {
+ super(ribbonTask);
+ }
+
+ @Override
+ public void reset() {
+ this.nextIndex = this.ribbonTask.getBandCount() - 1;
+ }
+
+ @Override
+ public AbstractRibbonBand next() {
+ AbstractRibbonBand result = this.ribbonTask.getBand(this.nextIndex);
+ this.nextIndex--;
+ if (this.nextIndex < 0)
+ this.nextIndex = this.ribbonTask.getBandCount() - 1;
+ return result;
+ }
+ }
+
+ /**
+ * The collapse from last resize sequencing policy. Under this policy the
+ * ribbon bands are being collapsed from right to left.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class CollapseFromLast extends
+ BaseRibbonBandResizeSequencingPolicy {
+ /**
+ * The index of the next ribbon task for collapsing.
+ */
+ int nextIndex;
+
+ /**
+ * Creates a new collapse from last resize sequencing policy for the
+ * specified task.
+ *
+ * @param ribbonTask
+ * Ribbon task.
+ */
+ public CollapseFromLast(RibbonTask ribbonTask) {
+ super(ribbonTask);
+ }
+
+ @Override
+ public void reset() {
+ this.nextIndex = this.ribbonTask.getBandCount() - 1;
+ }
+
+ @Override
+ public AbstractRibbonBand next() {
+ AbstractRibbonBand result = this.ribbonTask.getBand(this.nextIndex);
+
+ // check whether the current resize policy on the returned ribbon
+ // band is the last
+ List<RibbonBandResizePolicy> resizePolicies = result
+ .getResizePolicies();
+ if (result.getCurrentResizePolicy() == resizePolicies
+ .get(resizePolicies.size() - 1)) {
+ this.nextIndex--;
+ if (this.nextIndex < 0)
+ this.nextIndex = 0;
+ }
+ return result;
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/IconRibbonBandResizePolicy.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/IconRibbonBandResizePolicy.java
new file mode 100644
index 0000000..bafe101
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/IconRibbonBandResizePolicy.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon.resize;
+
+import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
+import org.pushingpixels.flamingo.internal.ui.ribbon.AbstractBandControlPanel;
+import org.pushingpixels.flamingo.internal.ui.ribbon.RibbonBandUI;
+
+/**
+ * Special resize policy that is used for collapsed ribbon bands. When there is
+ * not enough horizontal space to show the ribbon band content under the most
+ * restructive {@link RibbonBandResizePolicy}, the entire ribbon band content is
+ * replaced by a single popup button. Activating the popup button will show the
+ * original content under the most permissive resize policy in a popup.
+ *
+ * <p>
+ * An instance of this policy <strong>must</strong> appear exactly once in the
+ * list passed to {@link AbstractRibbonBand#setResizePolicies(java.util.List)},
+ * and it <strong>must</strong> be the last entry in that list.
+ * </p>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class IconRibbonBandResizePolicy extends
+ BaseRibbonBandResizePolicy<AbstractBandControlPanel> {
+ /**
+ * Creates a new collapsed resize policy.
+ *
+ * @param controlPanel
+ * The control panel of the associated ribbon band.
+ */
+ public IconRibbonBandResizePolicy(AbstractBandControlPanel controlPanel) {
+ super(controlPanel);
+ }
+
+ @Override
+ public int getPreferredWidth(int availableHeight, int gap) {
+ AbstractRibbonBand ribbonBand = this.controlPanel.getRibbonBand();
+ RibbonBandUI ui = ribbonBand.getUI();
+ return ui.getPreferredCollapsedWidth();
+ }
+
+ @Override
+ public void install(int availableHeight, int gap) {
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/RibbonBandResizePolicy.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/RibbonBandResizePolicy.java
new file mode 100644
index 0000000..46c26b1
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/RibbonBandResizePolicy.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon.resize;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.common.CommandButtonDisplayState;
+import org.pushingpixels.flamingo.api.ribbon.*;
+import org.pushingpixels.flamingo.api.ribbon.resize.CoreRibbonResizePolicies.*;
+
+/**
+ * Defines the resize policies for the {@link JRibbonBand}s and
+ * {@link JFlowRibbonBand}s.
+ *
+ * <p>
+ * The resize policy defines a single visual state of the given ribbon band. For
+ * every control in the specific ribbon band (command button, gallery etc), the
+ * resize policy defines what is its display state.
+ * </p>
+ *
+ * <p>
+ * The resize policies are installed with
+ * {@link AbstractRibbonBand#setResizePolicies(java.util.List)} API. The order
+ * of the resize policies in this list is important. The first entry in the list
+ * must be the most permissive policies that returns the largest value from its
+ * {@link #getPreferredWidth(int, int)}. Each successive entry in the list must
+ * return the value smaller than its predecessors. The last entry
+ * <strong>must</strong> be {@link IconRibbonBandResizePolicy}.
+ * </p>
+ *
+ * <p>
+ * As the ribbon horizontal size is changed (by the user resizing the
+ * application window), the ribbon task resize sequencing policy set by
+ * {@link RibbonTask#setResizeSequencingPolicy(RibbonBandResizeSequencingPolicy)}
+ * determines the order of ribbon bands to shrink / expand. See more details in
+ * the documentation of the {@link RibbonBandResizeSequencingPolicy}.
+ * </p>
+ *
+ * <p>
+ * The {@link CoreRibbonResizePolicies} provides a number of built in resize
+ * policies that respect the application element priorities passed to
+ * {@link JRibbonBand#addCommandButton(org.pushingpixels.flamingo.api.common.AbstractCommandButton, org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority)}
+ * and
+ * {@link JRibbonBand#addRibbonGallery(String, java.util.List, java.util.Map, int, int, org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority)}
+ * APIs. There are three types of built in resize policies:
+ * </p>
+ *
+ * <ul>
+ * <li>Resize policies for the {@link JFlowRibbonBand}s. The {@link FlowTwoRows}
+ * and {@link FlowThreeRows} allow placing the flow ribbon band content in two
+ * and three rows respectively.</li>
+ * <li>Resize policies for the {@link JRibbonBand}s. The
+ * {@link BaseCoreRibbonBandResizePolicy} is the base class for these policies.
+ * These policies respect the {@link RibbonElementPriority} associated on
+ * command buttons and ribbon galleries in {@link #getPreferredWidth(int, int)}
+ * and {@link #install(int, int)}. While {@link #install(int, int)} call on a
+ * {@link JFlowRibbonBand} only changes the bounds of the flow components, this
+ * call on a {@link JRibbonBand} can also change the display state of the
+ * command buttons (with
+ * {@link AbstractCommandButton#setDisplayState(org.pushingpixels.flamingo.api.common.CommandButtonDisplayState)}
+ * ) and the number of visible buttons in the ribbon galleries.</li>
+ * <li>The collapsed policy that replaces the entire content of the ribbon band
+ * with a single popup button. This is done when there is not enough horizontal
+ * space to show the content of the ribbon band under the most restrictive
+ * resize policy. Activating the popup button will show the original content
+ * under the most permissive resize policy in a popup. This policy is
+ * implemented in the {@link IconRibbonBandResizePolicy}.</li>
+ * </ul>
+ *
+ * <p>
+ * In addition to the specific resize policies, the
+ * {@link CoreRibbonResizePolicies} provides three core resize policies lists
+ * for {@link JRibbonBand}s:
+ * </p>
+ *
+ * <ul>
+ * <li>{@link CoreRibbonResizePolicies#getCorePoliciesPermissive(JRibbonBand)}
+ * returns a list that starts with a resize policy that shows all command
+ * buttons in the {@link CommandButtonDisplayState#BIG} and ribbon galleries
+ * with the largest number of visible buttons, fully utilizing the available
+ * screen space.</li>
+ * <li>{@link CoreRibbonResizePolicies#getCorePoliciesRestrictive(JRibbonBand)}
+ * returns a list that starts with a resize policy that respects the associated
+ * ribbon element priority set on the specific components.</li>
+ * <li> {@link CoreRibbonResizePolicies#getCorePoliciesNone(JRibbonBand)} returns
+ * a list that only has a <code>mirror</code> resize policy that respects the
+ * associated ribbon element priority set on the specific components.</li>
+ * </ul>
+ *
+ * <p>
+ * Note that as mentioned above, all the three lists above have the
+ * <code>collapsed</code> policy as their last element.
+ * </p>
+ *
+ * <p>
+ * In addition, the
+ * {@link CoreRibbonResizePolicies#getCoreFlowPoliciesRestrictive(JFlowRibbonBand, int)}
+ * returns a restrictive resize policy for {@link JFlowRibbonBand}s. The list
+ * starts with the two-row policy, goes to the three-row policy and then finally
+ * to the collapsed policy.
+ * </p>
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface RibbonBandResizePolicy {
+ /**
+ * Returns the preferred width of the associated ribbon band under the
+ * specified dimensions.
+ *
+ * @param availableHeight
+ * The height available for the associated ribbon band.
+ * @param gap
+ * The inter-component gap.
+ * @return The preferred width of the associated ribbon band under the
+ * specified dimensions.
+ */
+ public int getPreferredWidth(int availableHeight, int gap);
+
+ /**
+ * Installs this resize policy on the associated ribbon band. For
+ * {@link JFlowRibbonBand}s only changes the bounds of the flow components.
+ * For {@link JRibbonBand}s can also change the display state of the command
+ * buttons (with
+ * {@link AbstractCommandButton#setDisplayState(org.pushingpixels.flamingo.api.common.CommandButtonDisplayState)}
+ * ) and the number of visible buttons in the ribbon galleries. Note that
+ * this method is for internal use only and should not be called by the
+ * application code.
+ *
+ * @param availableHeight
+ * The height available for the associated ribbon band.
+ * @param gap
+ * The inter-component gap.
+ */
+ public void install(int availableHeight, int gap);
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/RibbonBandResizeSequencingPolicy.java b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/RibbonBandResizeSequencingPolicy.java
new file mode 100644
index 0000000..b4bc716
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/api/ribbon/resize/RibbonBandResizeSequencingPolicy.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.api.ribbon.resize;
+
+import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
+import org.pushingpixels.flamingo.api.ribbon.RibbonTask;
+import org.pushingpixels.flamingo.api.ribbon.resize.CoreRibbonResizeSequencingPolicies.CollapseFromLast;
+import org.pushingpixels.flamingo.api.ribbon.resize.CoreRibbonResizeSequencingPolicies.RoundRobin;
+
+/**
+ * Defines the resize sequencing policies for {@link RibbonTask}s.
+ *
+ * <p>
+ * The resize sequencing policy defines which ribbon band will be chosen next
+ * when the ribbon is shrinked / expanded. It is installed with the
+ * {@link RibbonTask#setResizeSequencingPolicy(RibbonBandResizeSequencingPolicy)}
+ * .
+ * </p>
+ *
+ * <p>
+ * The {@link CoreRibbonResizeSequencingPolicies} provides two built in resize
+ * sequencing policies:
+ * </p>
+ *
+ * <ul>
+ * <li>{@link RoundRobin} under which the ribbon bands are being collapsed in a
+ * cyclic fashion, distributing the collapsed pixels between the different
+ * bands.</li>
+ * <li>{@link CollapseFromLast} under which the ribbon bands are being collapsed
+ * from right to left.</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface RibbonBandResizeSequencingPolicy {
+ /**
+ * Resets this policy. Note that this method is for internal use only and
+ * should not be called by the application code.
+ */
+ public void reset();
+
+ /**
+ * Returns the next ribbon band for collapse.
+ *
+ * @return The next ribbon band for collapse.
+ */
+ public AbstractRibbonBand next();
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/bcb/BasicBreadcrumbBarUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/bcb/BasicBreadcrumbBarUI.java
new file mode 100644
index 0000000..367e995
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/bcb/BasicBreadcrumbBarUI.java
@@ -0,0 +1,720 @@
+/*
+ * Copyright (c) 2003-2010 Flamingo Kirill Grouchnikov
+ * and <a href="http://www.topologi.com">Topologi</a>.
+ * Contributed by <b>Rick Jelliffe</b> of <b>Topologi</b>
+ * in January 2006. in All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov Topologi nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.bcb;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.flamingo.api.bcb.*;
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonPopupOrientationKind;
+import org.pushingpixels.flamingo.api.common.icon.EmptyResizableIcon;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.model.PopupButtonModel;
+import org.pushingpixels.flamingo.api.common.popup.*;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+/**
+ * Basic UI for breadcrumb bar ({@link JBreadcrumbBar}).
+ *
+ * @author Topologi
+ * @author Kirill Grouchnikov
+ * @author Pawel Hajda
+ */
+public class BasicBreadcrumbBarUI extends BreadcrumbBarUI {
+ /**
+ * The associated breadcrumb bar.
+ */
+ protected JBreadcrumbBar breadcrumbBar;
+
+ protected JPanel mainPanel;
+
+ protected JScrollablePanel<JPanel> scrollerPanel;
+
+ protected ComponentListener componentListener;
+
+ protected JCommandButton dummy;
+
+ /**
+ * Contains the item path.
+ */
+ protected LinkedList modelStack;
+
+ protected LinkedList<JCommandButton> buttonStack;
+
+ protected BreadcrumbPathListener pathListener;
+
+ private AtomicInteger atomicCounter;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicBreadcrumbBarUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.breadcrumbBar = (JBreadcrumbBar) c;
+
+ this.modelStack = new LinkedList();
+ this.buttonStack = new LinkedList<JCommandButton>();
+
+ installDefaults(this.breadcrumbBar);
+ installComponents(this.breadcrumbBar);
+ installListeners(this.breadcrumbBar);
+
+ c.setLayout(createLayoutManager());
+
+ if (this.breadcrumbBar.getCallback() != null) {
+ SwingWorker<List<StringValuePair>, Void> worker = new SwingWorker<List<StringValuePair>, Void>() {
+ @Override
+ protected List<StringValuePair> doInBackground()
+ throws Exception {
+ return breadcrumbBar.getCallback().getPathChoices(null);
+ }
+
+ @Override
+ protected void done() {
+ try {
+ pushChoices(new BreadcrumbItemChoices(null, get()));
+ } catch (Exception exc) {
+ }
+ }
+ };
+ worker.execute();
+ }
+
+ this.dummy = new JCommandButton("Dummy", new EmptyResizableIcon(16));
+ this.dummy.setDisplayState(CommandButtonDisplayState.MEDIUM);
+ this.dummy
+ .setCommandButtonKind(CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ c.setLayout(null);
+
+ uninstallListeners((JBreadcrumbBar) c);
+ uninstallComponents((JBreadcrumbBar) c);
+ uninstallDefaults((JBreadcrumbBar) c);
+ this.breadcrumbBar = null;
+ }
+
+ protected void installDefaults(JBreadcrumbBar bar) {
+ Font currFont = bar.getFont();
+ if ((currFont == null) || (currFont instanceof UIResource)) {
+ Font font = FlamingoUtilities.getFont(null, "BreadcrumbBar.font",
+ "Button.font", "Panel.font");
+ bar.setFont(font);
+ }
+ }
+
+ protected void installComponents(JBreadcrumbBar bar) {
+ this.mainPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
+ this.mainPanel.setBorder(new EmptyBorder(0, 0, 0, 0));
+ this.mainPanel.setOpaque(false);
+ this.scrollerPanel = new JScrollablePanel<JPanel>(this.mainPanel,
+ JScrollablePanel.ScrollType.HORIZONTALLY);
+
+ bar.add(this.scrollerPanel, BorderLayout.CENTER);
+ }
+
+ protected void installListeners(final JBreadcrumbBar bar) {
+ this.atomicCounter = new AtomicInteger(0);
+
+ this.componentListener = new ComponentAdapter() {
+ @Override
+ public void componentResized(ComponentEvent e) {
+ updateComponents();
+ }
+ };
+ bar.addComponentListener(this.componentListener);
+
+ this.pathListener = new BreadcrumbPathListener() {
+ private SwingWorker<Void, Object> pathChangeWorker;
+
+ @Override
+ public void breadcrumbPathEvent(BreadcrumbPathEvent event) {
+ final int indexOfFirstChange = event.getIndexOfFirstChange();
+
+ if ((this.pathChangeWorker != null)
+ && !this.pathChangeWorker.isDone()) {
+ this.pathChangeWorker.cancel(true);
+ }
+ this.pathChangeWorker = new SwingWorker<Void, Object>() {
+ @Override
+ protected Void doInBackground() throws Exception {
+ atomicCounter.incrementAndGet();
+
+ synchronized (BasicBreadcrumbBarUI.this) {
+ // remove stack elements after the first change
+ if (indexOfFirstChange == 0) {
+ modelStack.clear();
+ } else {
+ int toLeave = indexOfFirstChange * 2 + 1;
+ while (modelStack.size() > toLeave)
+ modelStack.removeLast();
+ }
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ updateComponents();
+ }
+ });
+
+ if (indexOfFirstChange == 0) {
+ List<StringValuePair> rootChoices = breadcrumbBar
+ .getCallback().getPathChoices(null);
+ BreadcrumbItemChoices bic = new BreadcrumbItemChoices(
+ null, rootChoices);
+ if (!this.isCancelled()) {
+ publish(bic);
+ }
+ }
+
+ List<BreadcrumbItem> items = breadcrumbBar.getModel()
+ .getItems();
+ if (items != null) {
+ for (int itemIndex = indexOfFirstChange; itemIndex < items
+ .size(); itemIndex++) {
+ if (this.isCancelled())
+ break;
+
+ BreadcrumbItem item = items.get(itemIndex);
+ publish(item);
+
+ // now check if it has any children
+ List<BreadcrumbItem> subPath = new ArrayList<BreadcrumbItem>();
+ for (int j = 0; j <= itemIndex; j++) {
+ subPath.add(items.get(j));
+ }
+ BreadcrumbItemChoices bic = new BreadcrumbItemChoices(
+ item, breadcrumbBar.getCallback()
+ .getPathChoices(subPath));
+ if ((bic.getChoices() != null)
+ && (bic.getChoices().length > 0)) {
+ // add the selector - the current item has
+ // children
+ publish(bic);
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void process(List<Object> chunks) {
+ if (chunks != null) {
+ for (Object chunk : chunks) {
+ if (this.isCancelled()
+ || atomicCounter.get() > 1)
+ break;
+
+ if (chunk instanceof BreadcrumbItemChoices) {
+ pushChoices((BreadcrumbItemChoices) chunk,
+ false);
+ }
+ if (chunk instanceof BreadcrumbItem) {
+ pushChoice((BreadcrumbItem) chunk, false);
+ }
+ }
+ }
+ updateComponents();
+ }
+
+ @Override
+ protected void done() {
+ atomicCounter.decrementAndGet();
+ }
+ };
+ pathChangeWorker.execute();
+ }
+ };
+ this.breadcrumbBar.getModel().addPathListener(this.pathListener);
+ }
+
+ protected void uninstallDefaults(JBreadcrumbBar bar) {
+ }
+
+ protected void uninstallComponents(JBreadcrumbBar bar) {
+ this.mainPanel.removeAll();
+ this.buttonStack.clear();
+
+ bar.remove(this.scrollerPanel);
+ }
+
+ protected void uninstallListeners(JBreadcrumbBar bar) {
+ bar.removeComponentListener(this.componentListener);
+ this.componentListener = null;
+
+ this.breadcrumbBar.getModel().removePathListener(this.pathListener);
+ this.pathListener = null;
+ }
+
+ /**
+ * Invoked by <code>installUI</code> to create a layout manager object to
+ * manage the {@link JBreadcrumbBar}.
+ *
+ * @return a layout manager object
+ *
+ * @see BreadcrumbBarLayout
+ */
+ protected LayoutManager createLayoutManager() {
+ return new BreadcrumbBarLayout();
+ }
+
+ /**
+ * Layout for the breadcrumb bar.
+ *
+ * @author Kirill Grouchnikov
+ * @author Topologi
+ */
+ protected class BreadcrumbBarLayout implements LayoutManager {
+ /**
+ * Creates new layout manager.
+ */
+ public BreadcrumbBarLayout() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ // The height of breadcrumb bar is
+ // computed based on the preferred height of a command
+ // button in MEDIUM state.
+ int buttonHeight = dummy.getPreferredSize().height;
+
+ Insets ins = c.getInsets();
+ return new Dimension(c.getWidth(), buttonHeight + ins.top
+ + ins.bottom);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ int buttonHeight = dummy.getPreferredSize().height;
+
+ return new Dimension(10, buttonHeight);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ int width = c.getWidth();
+ int height = c.getHeight();
+ scrollerPanel.setBounds(0, 0, width, height);
+ }
+ }
+
+ protected synchronized void updateComponents() {
+ if (!this.breadcrumbBar.isVisible())
+ return;
+
+ this.mainPanel.removeAll();
+ buttonStack.clear();
+ // update the ui
+ for (int i = 0; i < modelStack.size(); i++) {
+ Object element = modelStack.get(i);
+ if (element instanceof BreadcrumbItemChoices) {
+ BreadcrumbItemChoices bic = (BreadcrumbItemChoices) element;
+ if (buttonStack.isEmpty()) {
+ JCommandButton button = new JCommandButton("");
+ button.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
+ configureBreadcrumbButton(button);
+ configurePopupAction(button, bic);
+ configurePopupRollover(button);
+ buttonStack.add(button);
+ } else {
+ JCommandButton button = buttonStack.getLast();
+ int oldW = button.getPreferredSize().width;
+ button
+ .setCommandButtonKind(CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION);
+ configurePopupAction(button, bic);
+ configurePopupRollover(button);
+ }
+ } else if (element instanceof BreadcrumbItem) {
+ BreadcrumbItem bi = (BreadcrumbItem) element;
+
+ JCommandButton button = new JCommandButton(bi.getKey());
+ configureBreadcrumbButton(button);
+ configureMainAction(button, bi);
+ final Icon icon = bi.getIcon();
+ if (icon != null) {
+ button.setIcon(new ResizableIcon() {
+ int iw = icon.getIconWidth();
+ int ih = icon.getIconHeight();
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x,
+ int y) {
+ int dx = (iw - icon.getIconWidth()) / 2;
+ int dy = (ih - icon.getIconHeight()) / 2;
+ icon.paintIcon(c, g, x + dx, y + dy);
+ }
+
+ @Override
+ public int getIconWidth() {
+ return iw;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return ih;
+ }
+
+ @Override
+ public void setDimension(Dimension newDimension) {
+ iw = newDimension.width;
+ ih = newDimension.height;
+ }
+ });
+ }
+
+ if (i > 0) {
+ BreadcrumbItemChoices lastBic = (BreadcrumbItemChoices) modelStack
+ .get(i - 1);
+ BreadcrumbItem[] choices = lastBic.getChoices();
+ if (choices != null) {
+ for (int j = 0; j < choices.length; j++) {
+ if (bi.getKey().equals(choices[j].getKey())) {
+ lastBic.setSelectedIndex(j);
+ break;
+ }
+ }
+ }
+ }
+
+ buttonStack.addLast(button);
+ }
+ }
+
+ for (JCommandButton jcb : buttonStack) {
+ this.mainPanel.add(jcb);
+ }
+
+ this.scrollerPanel.revalidate();
+ this.scrollerPanel.repaint();
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // scroll to the last element in the breadcrumb bar
+ scrollerPanel.scrollToIfNecessary(
+ mainPanel.getPreferredSize().width, 0);
+ scrollerPanel.repaint();
+ }
+ });
+ }
+
+ private void configureMainAction(JCommandButton button,
+ final BreadcrumbItem bi) {
+ button.getActionModel().addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ BreadcrumbBarModel barModel = breadcrumbBar.getModel();
+ int itemIndex = barModel.indexOf(bi);
+ int toLeave = (itemIndex < 0) ? 0 : itemIndex + 1;
+ barModel.setCumulative(true);
+ while (barModel.getItemCount() > toLeave) {
+ barModel.removeLast();
+ }
+ barModel.setCumulative(false);
+ }
+ });
+ }
+ });
+ }
+
+ private void configurePopupAction(JCommandButton button,
+ final BreadcrumbItemChoices bic) {
+ button.setPopupCallback(new PopupPanelCallback() {
+ @Override
+ public JPopupPanel getPopupPanel(JCommandButton commandButton) {
+ JCommandPopupMenu popup = new JCommandPopupMenu();
+ for (int i = 0; i < bic.getChoices().length; i++) {
+ final BreadcrumbItem bi = bic.getChoices()[i];
+
+ JCommandMenuButton menuButton = new JCommandMenuButton(bi
+ .getKey(), null);
+ final Icon icon = bi.getIcon();
+ if (icon != null) {
+ menuButton.setIcon(new ResizableIcon() {
+ int iw = icon.getIconWidth();
+ int ih = icon.getIconHeight();
+
+ @Override
+ public void paintIcon(Component c, Graphics g,
+ int x, int y) {
+ int dx = (iw - icon.getIconWidth()) / 2;
+ int dy = (ih - icon.getIconHeight()) / 2;
+ icon.paintIcon(c, g, x + dx, y + dy);
+ }
+
+ @Override
+ public int getIconWidth() {
+ return iw;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return ih;
+ }
+
+ @Override
+ public void setDimension(Dimension newDimension) {
+ iw = newDimension.width;
+ ih = newDimension.height;
+ }
+ });
+ }
+ if (i == bic.getSelectedIndex()) {
+ menuButton.setFont(menuButton.getFont().deriveFont(
+ Font.BOLD));
+ }
+
+ final int biIndex = i;
+ menuButton.getActionModel().addActionListener(
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (bi == null)
+ return;
+
+ BreadcrumbBarModel barModel = breadcrumbBar
+ .getModel();
+ barModel.setCumulative(true);
+ int itemIndex = barModel
+ .indexOf(bic.getAncestor());
+ int toLeave = ((bic.getAncestor() == null) || (itemIndex < 0)) ? 0
+ : itemIndex + 1;
+ while (barModel.getItemCount() > toLeave) {
+ barModel.removeLast();
+ }
+ barModel.addLast(bi);
+
+ bic.setSelectedIndex(biIndex);
+
+ barModel.setCumulative(false);
+ }
+ });
+ }
+ });
+
+ popup.addMenuButton(menuButton);
+ menuButton.setCursor(Cursor
+ .getPredefinedCursor(Cursor.HAND_CURSOR));
+ }
+ popup.setMaxVisibleMenuButtons(10);
+ return popup;
+ }
+ });
+ }
+
+ private void configurePopupRollover(final JCommandButton button) {
+ button.getPopupModel().addChangeListener(new ChangeListener() {
+ boolean rollover = button.getPopupModel().isRollover();
+
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ boolean isRollover = button.getPopupModel()
+ .isRollover();
+ if (isRollover == rollover)
+ return;
+
+ if (isRollover) {
+ // does any *other* button show popup?
+ for (JCommandButton bcbButton : buttonStack) {
+ if (bcbButton == button)
+ continue;
+
+ if (bcbButton.getPopupModel().isPopupShowing()) {
+ // scroll to view
+ scrollerPanel.scrollToIfNecessary(button
+ .getBounds().x, button.getWidth());
+ // simulate click on the popup area
+ // of *this* button
+ button.doPopupClick();
+ }
+ }
+ }
+
+ rollover = isRollover;
+ }
+ });
+ }
+ });
+ }
+
+ private void configureBreadcrumbButton(final JCommandButton button) {
+ button.setDisplayState(CommandButtonDisplayState.MEDIUM);
+ button
+ .setPopupOrientationKind(CommandButtonPopupOrientationKind.SIDEWARD);
+ button.setHGapScaleFactor(0.75);
+ button.getPopupModel().addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ PopupButtonModel model = button.getPopupModel();
+ boolean displayDownwards = model.isRollover()
+ || model.isPopupShowing();
+ CommandButtonPopupOrientationKind popupOrientationKind = displayDownwards ? CommandButtonPopupOrientationKind.DOWNWARD
+ : CommandButtonPopupOrientationKind.SIDEWARD;
+ button.setPopupOrientationKind(popupOrientationKind);
+ }
+ });
+ }
+
+ /**
+ * Pushes a choice to the top position of the stack. If the current top is
+ * already a {@link BreadcrumbItemChoices}, replace it.
+ *
+ * @param bic
+ * The choice item to push.
+ * @return The item that has been pushed.
+ */
+ protected Object pushChoices(BreadcrumbItemChoices bic) {
+ return pushChoices(bic, true);
+ }
+
+ /**
+ * Pushes a choice to the top position of the stack. If the current top is
+ * already a {@link BreadcrumbItemChoices}, replace it.
+ *
+ * @param bic
+ * The choice item to push.
+ * @param toUpdateUI
+ * Indication whether the bar should be repainted.
+ * @return The item that has been pushed.
+ */
+ protected synchronized Object pushChoices(BreadcrumbItemChoices bic,
+ boolean toUpdateUI) {
+ if (bic == null)
+ return null;
+ if (modelStack.size() % 2 == 1) {
+ modelStack.pop();
+ }
+ modelStack.addLast(bic);
+ if (toUpdateUI) {
+ updateComponents();
+ }
+ return bic;
+ }
+
+ /**
+ * Pushes an item to the top position of the stack. If the current top is
+ * already a {@link BreadcrumbItemChoices}, replace it.
+ *
+ * @param bi
+ * The item to push.
+ * @param toUpdateUI
+ * Indication whether the bar should be repainted.
+ * @return The item that has been pushed.
+ */
+ protected synchronized Object pushChoice(BreadcrumbItem bi,
+ boolean toUpdateUI) {
+ assert (bi != null);
+ Object result;
+ // synchronized (stack) {
+ if (!modelStack.isEmpty() && modelStack.size() % 2 == 0) {
+ modelStack.pop();
+ }
+ bi.setIndex(modelStack.size());
+ modelStack.addLast(bi);
+ // }
+ return bi;
+ }
+}
\ No newline at end of file
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/bcb/BreadcrumbBarUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/bcb/BreadcrumbBarUI.java
new file mode 100644
index 0000000..1176d32
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/bcb/BreadcrumbBarUI.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2003-2010 Flamingo Kirill Grouchnikov
+ * and <a href="http://www.topologi.com">Topologi</a>.
+ * Contributed by <b>Rick Jelliffe</b> of <b>Topologi</b>
+ * in January 2006. in All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov Topologi nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.bcb;
+
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.bcb.JBreadcrumbBar;
+
+/**
+ * UI for breadcrumb bar ({@link JBreadcrumbBar}).
+ *
+ * @author Topologi
+ * @author Kirill Grouchnikov
+ */
+public abstract class BreadcrumbBarUI extends ComponentUI {
+}
\ No newline at end of file
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/bcb/BreadcrumbItemChoices.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/bcb/BreadcrumbItemChoices.java
new file mode 100644
index 0000000..f4b014b
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/bcb/BreadcrumbItemChoices.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2003-2010 Flamingo Kirill Grouchnikov
+ * and <a href="http://www.topologi.com">Topologi</a>.
+ * Contributed by <b>Rick Jelliffe</b> of <b>Topologi</b>
+ * in January 2006.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov Topologi nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.bcb;
+
+import java.util.List;
+
+import javax.swing.Icon;
+
+import org.pushingpixels.flamingo.api.bcb.BreadcrumbItem;
+import org.pushingpixels.flamingo.api.common.StringValuePair;
+
+/**
+ * This is the model for the popup that is shown by clicking on the path
+ * selector.
+ */
+final class BreadcrumbItemChoices<T> {
+ /**
+ * Contains all possible choices.
+ */
+ private BreadcrumbItem<T>[] choices;
+
+ /**
+ * The ancestor item. This can be <code>null</code> only for the root
+ * choices element.
+ */
+ private BreadcrumbItem ancestor;
+
+ /**
+ * The index of <code>this</code> element.
+ */
+ private int selectedIndex = 0;
+
+ public BreadcrumbItemChoices(BreadcrumbItem ancestor,
+ List<StringValuePair<T>> entries) {
+ this.ancestor = ancestor;
+ this.choices = new BreadcrumbItem[entries.size()];
+ int index = 0;
+ for (StringValuePair<T> pair : entries) {
+ this.choices[index] = new BreadcrumbItem<T>(pair.getKey(), pair
+ .getValue());
+ this.choices[index].setIcon((Icon) pair.get("icon"));
+ index++;
+ }
+ this.selectedIndex = -1;
+ }
+
+ /**
+ * Returns the 0-based index of the first {@link BreadcrumbItem} whose
+ * display name matches the specified string.
+ *
+ * @param s
+ * String.
+ * @return The 0-based index of the first {@link BreadcrumbItem} whose
+ * display name matches the specified string.
+ */
+ public int getPosition(String s) {
+ assert (s != null && s.length() > 0);
+ for (int i = 0; i < choices.length; i++) {
+ BreadcrumbItem it = choices[i];
+ if (s.equals(it.getKey()))
+ return i;
+ }
+ return -1;
+ }
+
+ public void setSelectedIndex(int index) {
+ this.selectedIndex = index;
+ }
+
+ public int getSelectedIndex() {
+ return this.selectedIndex;
+ }
+
+ /**
+ * Returns the item array of <code>true</code>his element.
+ *
+ * @return The item array of <code>true</code>his element.
+ */
+ public BreadcrumbItem[] getChoices() {
+ return choices;
+ }
+
+ public BreadcrumbItem getAncestor() {
+ return ancestor;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandButtonListener.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandButtonListener.java
new file mode 100644
index 0000000..c792534
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandButtonListener.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.*;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.model.PopupButtonModel;
+
+/**
+ * Listener to track user interaction with the command buttons.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicCommandButtonListener implements MouseListener,
+ MouseMotionListener, FocusListener, ChangeListener {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
+ */
+ @Override
+ public void focusLost(FocusEvent e) {
+ AbstractCommandButton b = (AbstractCommandButton) e.getSource();
+ // System.err.println(e.getComponent() + "\n\tlost "
+ // + (e.isTemporary() ? "temporary" : "permanent")
+ // + " focus to \n\t" + e.getOppositeComponent());
+ b.getActionModel().setArmed(false);
+ b.getActionModel().setPressed(false);
+ if (b instanceof JCommandButton) {
+ PopupButtonModel popupModel = ((JCommandButton) b).getPopupModel();
+ popupModel.setPressed(false);
+ popupModel.setArmed(false);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
+ */
+ @Override
+ public void focusGained(FocusEvent e) {
+ AbstractCommandButton b = (AbstractCommandButton) e.getSource();
+ b.repaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (SwingUtilities.isLeftMouseButton(e)) {
+ AbstractCommandButton b = (AbstractCommandButton) e.getSource();
+
+ JScrollablePanel scrollable = (JScrollablePanel) SwingUtilities
+ .getAncestorOfClass(JScrollablePanel.class, b);
+ if (scrollable != null) {
+ // scroll the viewport of the scrollable panel so that
+ // the button is fully viewed.
+ Point loc = SwingUtilities.convertPoint(b.getParent(), b
+ .getLocation(), scrollable.getView());
+ if (scrollable.getScrollType() == JScrollablePanel.ScrollType.HORIZONTALLY) {
+ scrollable.scrollToIfNecessary(loc.x, b.getWidth());
+ } else {
+ scrollable.scrollToIfNecessary(loc.y, b.getHeight());
+ }
+ }
+
+ if (b.contains(e.getX(), e.getY())) {
+ CommandButtonUI ui = b.getUI();
+ Rectangle actionRect = ui.getLayoutInfo().actionClickArea;
+ Rectangle popupRect = ui.getLayoutInfo().popupClickArea;
+
+ if ((actionRect != null) && actionRect.contains(e.getPoint())) {
+ ButtonModel actionModel = b.getActionModel();
+ if (actionModel.isEnabled()) {
+ actionModel.setArmed(true);
+ actionModel.setPressed(true);
+ }
+ } else {
+ if ((popupRect != null) && popupRect.contains(e.getPoint())) {
+ PopupButtonModel popupModel = ((JCommandButton) b)
+ .getPopupModel();
+ if (popupModel.isEnabled()) {
+ popupModel.setArmed(true);
+ popupModel.setPressed(true);
+ }
+ }
+ }
+
+ // System.err.println(b.getText() + " - hasFocus():"
+ // + b.hasFocus() + ", isRequestFocusEnabled():"
+ // + b.isRequestFocusEnabled());
+ if (!b.hasFocus() && b.isRequestFocusEnabled()) {
+ b.requestFocusInWindow();
+ }
+ }
+ }
+ };
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (SwingUtilities.isLeftMouseButton(e)) {
+ AbstractCommandButton b = (AbstractCommandButton) e.getSource();
+ b.getActionModel().setPressed(false);
+ if (b instanceof JCommandButton) {
+ ((JCommandButton) b).getPopupModel().setPressed(false);
+ }
+ b.getActionModel().setArmed(false);
+ if (b instanceof JCommandButton) {
+ ((JCommandButton) b).getPopupModel().setArmed(false);
+ }
+ }
+ };
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent
+ * )
+ */
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ this.syncMouseMovement(e);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ this.syncMouseMovement(e);
+ }
+
+ /**
+ * Synchronizes the action and popup models of the command button with the
+ * specified mouse event.
+ *
+ * @param e
+ * Mouse event for the model synchronization.
+ */
+ private void syncMouseMovement(MouseEvent e) {
+ AbstractCommandButton b = (AbstractCommandButton) e.getSource();
+ ButtonModel actionModel = b.getActionModel();
+ PopupButtonModel popupModel = (b instanceof JCommandButton) ? ((JCommandButton) b)
+ .getPopupModel()
+ : null;
+ CommandButtonUI ui = b.getUI();
+ Rectangle actionRect = ui.getLayoutInfo().actionClickArea;
+ Rectangle popupRect = ui.getLayoutInfo().popupClickArea;
+
+ if ((actionRect != null) && actionRect.contains(e.getPoint())) {
+ if (actionModel.isEnabled()) {
+ if (!SwingUtilities.isLeftMouseButton(e))
+ actionModel.setRollover(true);
+ if (actionModel.isPressed())
+ actionModel.setArmed(true);
+ }
+ if (popupModel != null && !SwingUtilities.isLeftMouseButton(e))
+ popupModel.setRollover(false);
+ } else {
+ if ((popupRect != null) && popupRect.contains(e.getPoint())) {
+ if ((popupModel != null) && popupModel.isEnabled()) {
+ if (!SwingUtilities.isLeftMouseButton(e))
+ popupModel.setRollover(true);
+ if (popupModel.isPressed())
+ popupModel.setArmed(true);
+ }
+ if (!SwingUtilities.isLeftMouseButton(e))
+ actionModel.setRollover(false);
+ }
+ }
+ };
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseExited(MouseEvent e) {
+ AbstractCommandButton b = (AbstractCommandButton) e.getSource();
+ ButtonModel actionModel = b.getActionModel();
+ PopupButtonModel popupModel = (b instanceof JCommandButton) ? ((JCommandButton) b)
+ .getPopupModel()
+ : null;
+ actionModel.setRollover(false);
+ actionModel.setArmed(false);
+ if (popupModel != null) {
+ popupModel.setRollover(false);
+ popupModel.setArmed(false);
+ }
+ };
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent
+ * )
+ */
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ AbstractCommandButton b = (AbstractCommandButton) e.getSource();
+ b.repaint();
+ }
+
+ /**
+ * Installs keyboard action (space / enter keys) on the specified command
+ * button.
+ *
+ * @param button
+ * Command button.
+ */
+ public void installKeyboardActions(AbstractCommandButton button) {
+ ActionMap map = new ActionMap();
+ map.put(PressAction.PRESS, new PressAction(button));
+ map.put(ReleaseAction.RELEASE, new ReleaseAction(button));
+
+ SwingUtilities.replaceUIActionMap(button, map);
+ InputMap km = LookAndFeel.makeInputMap(new Object[] { "SPACE",
+ "pressed", "released SPACE", "released", "ENTER", "pressed",
+ "released ENTER", "released" });
+
+ SwingUtilities.replaceUIInputMap(button, JComponent.WHEN_FOCUSED, km);
+ }
+
+ /**
+ * Button press action.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class PressAction extends AbstractAction {
+ /**
+ * Press action name.
+ */
+ private static final String PRESS = "pressed";
+
+ /**
+ * Associated command button.
+ */
+ AbstractCommandButton button;
+
+ /**
+ * Creates a new press action.
+ *
+ * @param button
+ * Associated command button.
+ */
+ PressAction(AbstractCommandButton button) {
+ super(PRESS);
+ this.button = button;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent
+ * )
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ ButtonModel model = button.getActionModel();
+ model.setArmed(true);
+ model.setPressed(true);
+ if (!button.hasFocus()) {
+ button.requestFocus();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.AbstractAction#isEnabled()
+ */
+ @Override
+ public boolean isEnabled() {
+ return button.getActionModel().isEnabled();
+ }
+ }
+
+ /**
+ * Button release action.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class ReleaseAction extends AbstractAction {
+ /**
+ * Release action name.
+ */
+ private static final String RELEASE = "released";
+
+ /**
+ * Associated command button.
+ */
+ AbstractCommandButton button;
+
+ /**
+ * Creates a new release action.
+ *
+ * @param button
+ * Associated command button.
+ */
+ ReleaseAction(AbstractCommandButton button) {
+ super(RELEASE);
+ this.button = button;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent
+ * )
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ ButtonModel model = button.getActionModel();
+ model.setPressed(false);
+ model.setArmed(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.AbstractAction#isEnabled()
+ */
+ @Override
+ public boolean isEnabled() {
+ return button.getActionModel().isEnabled();
+ }
+ }
+
+ /**
+ * Uninstalls keyboard action (space / enter keys) from the specified
+ * command button.
+ *
+ * @param button
+ * Command button.
+ */
+ public void uninstallKeyboardActions(AbstractCommandButton button) {
+ SwingUtilities.replaceUIInputMap(button,
+ JComponent.WHEN_IN_FOCUSED_WINDOW, null);
+ SwingUtilities.replaceUIInputMap(button, JComponent.WHEN_FOCUSED, null);
+ SwingUtilities.replaceUIActionMap(button, null);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandButtonPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandButtonPanelUI.java
new file mode 100644
index 0000000..0ec5d1d
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandButtonPanelUI.java
@@ -0,0 +1,850 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.common.JCommandButtonPanel;
+import org.pushingpixels.flamingo.api.common.JCommandButtonPanel.LayoutKind;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+/**
+ * Basic UI for command button panel {@link JCommandButtonPanel}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicCommandButtonPanelUI extends CommandButtonPanelUI {
+ /**
+ * The associated command button panel.
+ */
+ protected JCommandButtonPanel buttonPanel;
+
+ /**
+ * Labels of the button panel groups.
+ */
+ protected JLabel[] groupLabels;
+
+ /**
+ * Bounds of button panel groups.
+ */
+ protected Rectangle[] groupRects;
+
+ /**
+ * Property change listener on {@link #buttonPanel}.
+ */
+ protected PropertyChangeListener propertyChangeListener;
+
+ /**
+ * Change listener on {@link #buttonPanel}.
+ */
+ protected ChangeListener changeListener;
+
+ /**
+ * Default insets of button panel groups.
+ */
+ protected static final Insets GROUP_INSETS = new Insets(4, 4, 4, 4);
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicCommandButtonPanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.buttonPanel = (JCommandButtonPanel) c;
+
+ installDefaults();
+ installComponents();
+ installListeners();
+ }
+
+ /**
+ * Installs defaults on the associated button panel.
+ */
+ protected void installDefaults() {
+ this.buttonPanel.setLayout(this.createLayoutManager());
+ Font currFont = this.buttonPanel.getFont();
+ if ((currFont == null) || (currFont instanceof UIResource)) {
+ Font titleFont = FlamingoUtilities.getFont(null,
+ "CommandButtonPanel.font", "Button.font", "Panel.font");
+ this.buttonPanel.setFont(titleFont);
+ }
+ }
+
+ /**
+ * Installs sub-components on the associated button panel.
+ */
+ protected void installComponents() {
+ this.recomputeGroupHeaders();
+ }
+
+ /**
+ * Installs listeners on the associated button panel.
+ */
+ protected void installListeners() {
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("maxButtonColumns".equals(evt.getPropertyName())
+ || "maxButtonRows".equals(evt.getPropertyName())
+ || "toShowGroupLabels".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (buttonPanel != null) {
+ recomputeGroupHeaders();
+ buttonPanel.revalidate();
+ buttonPanel.doLayout();
+ }
+ }
+ });
+ }
+
+ if ("layoutKind".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (buttonPanel != null) {
+ buttonPanel.setLayout(createLayoutManager());
+ buttonPanel.revalidate();
+ buttonPanel.doLayout();
+ }
+ }
+ });
+ }
+ }
+ };
+ this.buttonPanel.addPropertyChangeListener(this.propertyChangeListener);
+
+ this.changeListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ recomputeGroupHeaders();
+ buttonPanel.revalidate();
+ buttonPanel.doLayout();
+ }
+ };
+ this.buttonPanel.addChangeListener(this.changeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ c.setLayout(null);
+
+ uninstallListeners();
+ uninstallComponents();
+ uninstallDefaults();
+ this.buttonPanel = null;
+ }
+
+ /**
+ * Uninstalls defaults from the associated button panel.
+ */
+ protected void uninstallDefaults() {
+ }
+
+ /**
+ * Uninstalls sub-components from the associated button panel.
+ */
+ protected void uninstallComponents() {
+ if (this.groupLabels != null) {
+ for (JLabel groupLabel : this.groupLabels) {
+ this.buttonPanel.remove(groupLabel);
+ }
+ // for (JSeparator groupSeparator : this.groupSeparators) {
+ // this.buttonPanel.remove(groupSeparator);
+ // }
+ }
+ }
+
+ /**
+ * Uninstalls listeners from the associated button panel.
+ */
+ protected void uninstallListeners() {
+ this.buttonPanel
+ .removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+
+ this.buttonPanel.removeChangeListener(this.changeListener);
+ this.changeListener = null;
+ }
+
+ /**
+ * Returns the layout manager for the associated button panel.
+ *
+ * @return The layout manager for the associated button panel.
+ */
+ protected LayoutManager createLayoutManager() {
+ if (this.buttonPanel.getLayoutKind() == LayoutKind.ROW_FILL)
+ return new RowFillLayout();
+ else
+ return new ColumnFillLayout();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Color bg = this.buttonPanel.getBackground();
+ g.setColor(bg);
+ g.fillRect(0, 0, c.getWidth(), c.getHeight());
+
+ for (int i = 0; i < this.buttonPanel.getGroupCount(); i++) {
+ Rectangle groupRect = this.groupRects[i];
+ this.paintGroupBackground(g, i, groupRect.x, groupRect.y,
+ groupRect.width, groupRect.height);
+
+ if (this.groupLabels[i].isVisible()) {
+ Rectangle groupTitleBackground = this.groupLabels[i]
+ .getBounds();
+ this.paintGroupTitleBackground(g, i, groupRect.x,
+ groupTitleBackground.y - getGroupInsets().top,
+ groupRect.width, groupTitleBackground.height
+ + getGroupInsets().top + getLayoutGap());
+ }
+ }
+ }
+
+ /**
+ * Paints the background of the specified button panel group.
+ *
+ * @param g
+ * Graphics context.
+ * @param groupIndex
+ * Group index.
+ * @param x
+ * X coordinate of the button group bounds.
+ * @param y
+ * Y coordinate of the button group bounds.
+ * @param width
+ * Width of the button group bounds.
+ * @param height
+ * Height of the button group bounds.
+ */
+ protected void paintGroupBackground(Graphics g, int groupIndex, int x,
+ int y, int width, int height) {
+ Color c = this.buttonPanel.getBackground();
+ if ((c == null) || (c instanceof UIResource)) {
+ c = UIManager.getColor("Panel.background");
+ if (c == null)
+ c = new Color(190, 190, 190);
+ if (groupIndex % 2 == 1) {
+ double coef = 0.95;
+ c = new Color((int) (c.getRed() * coef),
+ (int) (c.getGreen() * coef), (int) (c.getBlue() * coef));
+ }
+ }
+ g.setColor(c);
+ g.fillRect(x, y, width, height);
+ }
+
+ /**
+ * Paints the background of the title of specified button panel group.
+ *
+ * @param g
+ * Graphics context.
+ * @param groupIndex
+ * Group index.
+ * @param x
+ * X coordinate of the button group title bounds.
+ * @param y
+ * Y coordinate of the button group title bounds.
+ * @param width
+ * Width of the button group title bounds.
+ * @param height
+ * Height of the button group title bounds.
+ */
+ protected void paintGroupTitleBackground(Graphics g, int groupIndex, int x,
+ int y, int width, int height) {
+ FlamingoUtilities.renderSurface(g, this.buttonPanel, new Rectangle(x,
+ y, width, height), false, (groupIndex > 0), true);
+ }
+
+ /**
+ * Returns the height of the group title strip.
+ *
+ * @param groupIndex
+ * Group index.
+ * @return The height of the title strip of the specified group.
+ */
+ protected int getGroupTitleHeight(int groupIndex) {
+ return this.groupLabels[groupIndex].getPreferredSize().height;
+ }
+
+ /**
+ * Returns the insets of button panel groups.
+ *
+ * @return The insets of button panel groups.
+ */
+ protected Insets getGroupInsets() {
+ return GROUP_INSETS;
+ }
+
+ /**
+ * Row-fill layout for the button panel.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class RowFillLayout implements LayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container parent) {
+ Insets bInsets = parent.getInsets();
+ Insets groupInsets = getGroupInsets();
+ int left = bInsets.left;
+ int right = bInsets.right;
+
+ int y = bInsets.top;
+
+ JCommandButtonPanel panel = (JCommandButtonPanel) parent;
+ boolean ltr = panel.getComponentOrientation().isLeftToRight();
+
+ // compute max width of buttons
+ int maxButtonWidth = 0;
+ int maxButtonHeight = 0;
+ int groupCount = panel.getGroupCount();
+ for (int i = 0; i < groupCount; i++) {
+ for (AbstractCommandButton button : panel.getGroupButtons(i)) {
+ maxButtonWidth = Math.max(maxButtonWidth, button
+ .getPreferredSize().width);
+ maxButtonHeight = Math.max(maxButtonHeight, button
+ .getPreferredSize().height);
+ }
+ }
+ groupRects = new Rectangle[groupCount];
+
+ int gap = getLayoutGap();
+ int maxWidth = parent.getWidth() - bInsets.left - bInsets.right
+ - groupInsets.left - groupInsets.right;
+ // for N buttons, there are N-1 gaps. Add the gap to the
+ // available width and divide by the max button width + gap.
+ int buttonsInRow = (maxButtonWidth == 0) ? 0 : (maxWidth + gap)
+ / (maxButtonWidth + gap);
+ int maxButtonColumnsToUse = panel.getMaxButtonColumns();
+ if (maxButtonColumnsToUse > 0) {
+ buttonsInRow = Math.min(buttonsInRow, maxButtonColumnsToUse);
+ }
+
+ // System.out.println("Layout : " + buttonsInRow);
+ for (int i = 0; i < groupCount; i++) {
+ int topGroupY = y;
+ y += groupInsets.top;
+
+ JLabel groupLabel = groupLabels[i];
+ if (buttonPanel.isToShowGroupLabels()) {
+ int labelWidth = groupLabel.getPreferredSize().width;
+ int labelHeight = getGroupTitleHeight(i);
+ if (groupLabel.getComponentOrientation().isLeftToRight()) {
+ groupLabel.setBounds(left + groupInsets.left, y,
+ labelWidth, labelHeight);
+ } else {
+ groupLabel.setBounds(parent.getWidth() - right
+ - groupInsets.right - labelWidth, y,
+ labelWidth, labelHeight);
+ }
+ y += labelHeight + gap;
+ }
+
+ int buttonRows = (buttonsInRow == 0) ? 0 : (int) (Math
+ .ceil((double) panel.getGroupButtons(i).size()
+ / buttonsInRow));
+ if (maxButtonColumnsToUse > 0) {
+ buttonsInRow = Math
+ .min(buttonsInRow, maxButtonColumnsToUse);
+ }
+
+ // spread the buttons so that we don't have extra space
+ // on the right
+ int actualButtonWidth = (buttonRows > 1) ? (maxWidth - (buttonsInRow - 1)
+ * gap)
+ / buttonsInRow
+ : maxButtonWidth;
+ if (maxButtonColumnsToUse == 1)
+ actualButtonWidth = maxWidth;
+
+ if (ltr) {
+ int currX = left + groupInsets.left;
+ for (AbstractCommandButton button : panel
+ .getGroupButtons(i)) {
+ int endX = currX + actualButtonWidth;
+ if (endX > (parent.getWidth() - right - groupInsets.right)) {
+ currX = left + groupInsets.left;
+ y += maxButtonHeight;
+ y += gap;
+ }
+ button.setBounds(currX, y, actualButtonWidth,
+ maxButtonHeight);
+ // System.out.println(button.getText() + ":"
+ // + button.isVisible() + ":" + button.getBounds());
+ currX += actualButtonWidth;
+ currX += gap;
+ }
+ } else {
+ int currX = parent.getWidth() - right - groupInsets.right;
+ for (AbstractCommandButton button : panel
+ .getGroupButtons(i)) {
+ int startX = currX - actualButtonWidth;
+ if (startX < (left + groupInsets.left)) {
+ currX = parent.getWidth() - right
+ - groupInsets.right;
+ y += maxButtonHeight;
+ y += gap;
+ }
+ button.setBounds(currX - actualButtonWidth, y,
+ actualButtonWidth, maxButtonHeight);
+ // System.out.println(button.getText() + ":"
+ // + button.isVisible() + ":" + button.getBounds());
+ currX -= actualButtonWidth;
+ currX -= gap;
+ }
+ }
+
+ y += maxButtonHeight + groupInsets.bottom;
+ int bottomGroupY = y;
+ groupRects[i] = new Rectangle(left, topGroupY, (parent
+ .getWidth()
+ - left - right), (bottomGroupY - topGroupY));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return new Dimension(20, 20);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ JCommandButtonPanel panel = (JCommandButtonPanel) parent;
+
+ int maxButtonColumnsToUse = panel.getMaxButtonColumns();
+
+ Insets bInsets = parent.getInsets();
+ Insets groupInsets = getGroupInsets();
+ int insetsWidth = bInsets.left + groupInsets.left + bInsets.right
+ + groupInsets.right;
+
+ // compute max width of buttons
+ int maxButtonWidth = 0;
+ int maxButtonHeight = 0;
+ int groupCount = panel.getGroupCount();
+ for (int i = 0; i < groupCount; i++) {
+ for (AbstractCommandButton button : panel.getGroupButtons(i)) {
+ maxButtonWidth = Math.max(maxButtonWidth, button
+ .getPreferredSize().width);
+ maxButtonHeight = Math.max(maxButtonHeight, button
+ .getPreferredSize().height);
+ }
+ }
+
+ // total height
+ int gap = getLayoutGap();
+ boolean usePanelWidth = (maxButtonColumnsToUse <= 0);
+ int availableWidth = panel.getWidth();
+ availableWidth -= insetsWidth;
+
+ if (usePanelWidth) {
+ // this hasn't been set. Compute using the available
+ // width
+ maxButtonColumnsToUse = (availableWidth + gap)
+ / (maxButtonWidth + gap);
+ }
+ int height = bInsets.top + bInsets.bottom;
+ // System.out.print(height + "[" + maxButtonColumnsToUse + "]");
+ for (int i = 0; i < groupCount; i++) {
+ if (groupLabels[i].isVisible()) {
+ height += (getGroupTitleHeight(i) + gap);
+ }
+
+ height += (groupInsets.top + groupInsets.bottom);
+
+ int buttonRows = (int) (Math.ceil((double) panel
+ .getGroupButtons(i).size()
+ / maxButtonColumnsToUse));
+ height += buttonRows * maxButtonHeight + (buttonRows - 1) * gap;
+ // System.out.print(" " + height);
+ }
+ int prefWidth = usePanelWidth ? availableWidth
+ : maxButtonColumnsToUse * maxButtonWidth
+ + (maxButtonColumnsToUse - 1) * gap + bInsets.left
+ + bInsets.right + groupInsets.left
+ + groupInsets.right;
+ // System.out.println(" : " + height);
+ return new Dimension(Math.max(10, prefWidth), Math.max(10, height));
+ }
+ }
+
+ /**
+ * Column-fill layout for the button panel.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class ColumnFillLayout implements LayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container parent) {
+ Insets bInsets = parent.getInsets();
+ Insets groupInsets = getGroupInsets();
+ int top = bInsets.top;
+ int bottom = bInsets.bottom;
+
+ JCommandButtonPanel panel = (JCommandButtonPanel) parent;
+ boolean ltr = panel.getComponentOrientation().isLeftToRight();
+
+ // compute max width of buttons
+ int maxButtonWidth = 0;
+ int maxButtonHeight = 0;
+ int groupCount = panel.getGroupCount();
+ for (int i = 0; i < groupCount; i++) {
+ for (AbstractCommandButton button : panel.getGroupButtons(i)) {
+ maxButtonWidth = Math.max(maxButtonWidth, button
+ .getPreferredSize().width);
+ maxButtonHeight = Math.max(maxButtonHeight, button
+ .getPreferredSize().height);
+ }
+ }
+ groupRects = new Rectangle[groupCount];
+
+ int gap = getLayoutGap();
+ int maxHeight = parent.getHeight() - bInsets.top - bInsets.bottom
+ - groupInsets.top - groupInsets.bottom;
+ // for N buttons, there are N-1 gaps. Add the gap to the
+ // available width and divide by the max button width + gap.
+ int buttonsInRow = (maxButtonHeight == 0) ? 0 : (maxHeight + gap)
+ / (maxButtonHeight + gap);
+
+ if (ltr) {
+ int x = bInsets.left + groupInsets.left;
+ for (int i = 0; i < groupCount; i++) {
+ int leftGroupX = x;
+ x += groupInsets.left;
+ int currY = top + groupInsets.top;
+
+ int buttonColumns = (buttonsInRow == 0) ? 0 : (int) (Math
+ .ceil((double) panel.getGroupButtons(i).size()
+ / buttonsInRow));
+ // spread the buttons so that we don't have extra space
+ // on the bottom
+ int actualButtonHeight = (buttonColumns > 1) ? (maxHeight - (buttonsInRow - 1)
+ * gap)
+ / buttonsInRow
+ : maxButtonWidth;
+
+ for (AbstractCommandButton button : panel
+ .getGroupButtons(i)) {
+ int endY = currY + actualButtonHeight;
+ if (endY > (parent.getHeight() - bottom - groupInsets.bottom)) {
+ currY = top + groupInsets.top;
+ x += maxButtonWidth;
+ x += gap;
+ }
+ button.setBounds(x, currY, maxButtonWidth,
+ actualButtonHeight);
+ // System.out.println(button.getText() + ":"
+ // + button.isVisible() + ":" + button.getBounds());
+ currY += actualButtonHeight;
+ currY += gap;
+ }
+ x += maxButtonWidth + groupInsets.bottom;
+ int rightGroupX = x;
+ groupRects[i] = new Rectangle(leftGroupX, top,
+ (rightGroupX - leftGroupX), (parent.getHeight()
+ - top - bottom));
+ }
+ } else {
+ int x = panel.getWidth() - bInsets.right - groupInsets.right;
+ for (int i = 0; i < groupCount; i++) {
+ int rightGroupX = x;
+ x -= groupInsets.left;
+ int currY = top + groupInsets.top;
+
+ int buttonColumns = (buttonsInRow == 0) ? 0 : (int) (Math
+ .ceil((double) panel.getGroupButtons(i).size()
+ / buttonsInRow));
+ // spread the buttons so that we don't have extra space
+ // on the bottom
+ int actualButtonHeight = (buttonColumns > 1) ? (maxHeight - (buttonsInRow - 1)
+ * gap)
+ / buttonsInRow
+ : maxButtonWidth;
+
+ for (AbstractCommandButton button : panel
+ .getGroupButtons(i)) {
+ int endY = currY + actualButtonHeight;
+ if (endY > (parent.getHeight() - bottom - groupInsets.bottom)) {
+ currY = top + groupInsets.top;
+ x -= maxButtonWidth;
+ x -= gap;
+ }
+ button.setBounds(x - maxButtonWidth, currY,
+ maxButtonWidth, actualButtonHeight);
+ // System.out.println(button.getText() + ":"
+ // + button.isVisible() + ":" + button.getBounds());
+ currY += actualButtonHeight;
+ currY += gap;
+ }
+ x -= (maxButtonWidth + groupInsets.bottom);
+ int leftGroupX = x;
+ groupRects[i] = new Rectangle(leftGroupX, top,
+ (rightGroupX - leftGroupX), (parent.getHeight()
+ - top - bottom));
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return new Dimension(20, 20);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ JCommandButtonPanel panel = (JCommandButtonPanel) parent;
+
+ int maxButtonRowsToUse = panel.getMaxButtonRows();
+
+ Insets bInsets = parent.getInsets();
+ Insets groupInsets = getGroupInsets();
+ int insetsHeight = bInsets.top + groupInsets.top + bInsets.bottom
+ + groupInsets.bottom;
+
+ // compute max width of buttons
+ int maxButtonWidth = 0;
+ int maxButtonHeight = 0;
+ int groupCount = panel.getGroupCount();
+ for (int i = 0; i < groupCount; i++) {
+ for (AbstractCommandButton button : panel.getGroupButtons(i)) {
+ maxButtonWidth = Math.max(maxButtonWidth, button
+ .getPreferredSize().width);
+ maxButtonHeight = Math.max(maxButtonHeight, button
+ .getPreferredSize().height);
+ }
+ }
+
+ // total width
+ int gap = getLayoutGap();
+ boolean usePanelHeight = (maxButtonRowsToUse <= 0);
+
+ int availableHeight = panel.getHeight();
+ availableHeight -= insetsHeight;
+ if (usePanelHeight) {
+ // this hasn't been set. Compute using the available
+ // height
+ maxButtonRowsToUse = (availableHeight + gap)
+ / (maxButtonHeight + gap);
+ }
+ // go over all groups and see how many columns each one needs
+ int width = bInsets.left + bInsets.right;
+ for (int i = 0; i < groupCount; i++) {
+ width += (groupInsets.left + groupInsets.right);
+
+ int buttonColumns = (int) (Math.ceil((double) panel
+ .getGroupButtons(i).size()
+ / maxButtonRowsToUse));
+ width += buttonColumns * maxButtonWidth + (buttonColumns - 1)
+ * gap;
+ }
+ int prefHeight = usePanelHeight ? availableHeight
+ : maxButtonRowsToUse * maxButtonWidth
+ + (maxButtonRowsToUse - 1) * gap + bInsets.top
+ + bInsets.bottom + groupInsets.top
+ + groupInsets.bottom;
+ return new Dimension(Math.max(10, width), Math.max(10, prefHeight));
+ }
+ }
+
+ /**
+ * Returns the layout gap for button panel components.
+ *
+ * @return The layout gap for button panel components.
+ */
+ protected int getLayoutGap() {
+ return 4;
+ }
+
+ /**
+ * Recomputes the components for button group headers.
+ */
+ protected void recomputeGroupHeaders() {
+ if (this.groupLabels != null) {
+ for (JLabel groupLabel : this.groupLabels) {
+ this.buttonPanel.remove(groupLabel);
+ }
+ }
+
+ int groupCount = this.buttonPanel.getGroupCount();
+ this.groupLabels = new JLabel[groupCount];
+ for (int i = 0; i < groupCount; i++) {
+ this.groupLabels[i] = new JLabel(this.buttonPanel
+ .getGroupTitleAt(i));
+ this.groupLabels[i].setComponentOrientation(this.buttonPanel
+ .getComponentOrientation());
+
+ this.buttonPanel.add(this.groupLabels[i]);
+
+ this.groupLabels[i].setVisible(this.buttonPanel
+ .isToShowGroupLabels());
+ }
+ }
+
+ /**
+ * Returns the preferred size of the associated button panel for the
+ * specified parameters.
+ *
+ * @param buttonVisibleRows
+ * Target number of visible button rows.
+ * @param titleVisibleRows
+ * Target number of visible group title rows.
+ * @return The preferred size of the associated button panel for the
+ * specified parameters.
+ */
+ public int getPreferredHeight(int buttonVisibleRows, int titleVisibleRows) {
+ Insets bInsets = this.buttonPanel.getInsets();
+ Insets groupInsets = getGroupInsets();
+ int maxButtonHeight = 0;
+ int groupCount = this.buttonPanel.getGroupCount();
+ for (int i = 0; i < groupCount; i++) {
+ for (AbstractCommandButton button : this.buttonPanel
+ .getGroupButtons(i)) {
+ maxButtonHeight = Math.max(maxButtonHeight, button
+ .getPreferredSize().height);
+ }
+ }
+
+ // total height
+ int gap = getLayoutGap();
+
+ // panel insets
+ int totalHeight = bInsets.top + bInsets.bottom;
+ // height of icon rows
+ totalHeight += buttonVisibleRows * maxButtonHeight;
+ // gaps between icon rows
+ totalHeight += (buttonVisibleRows - 1) * gap;
+ // title height
+ totalHeight += titleVisibleRows * getGroupTitleHeight(0);
+ // title insets
+ totalHeight += (titleVisibleRows - 1)
+ * (groupInsets.top + groupInsets.bottom);
+
+ return totalHeight;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandButtonStripUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandButtonStripUI.java
new file mode 100644
index 0000000..f784626
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandButtonStripUI.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+
+import javax.swing.JComponent;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.common.JCommandButtonStrip;
+import org.pushingpixels.flamingo.api.common.JCommandButtonStrip.StripOrientation;
+
+/**
+ * Basic UI for button strip {@link JCommandButtonStrip}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicCommandButtonStripUI extends CommandButtonStripUI {
+ /**
+ * The associated button strip.
+ */
+ protected JCommandButtonStrip buttonStrip;
+
+ protected ChangeListener changeListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicCommandButtonStripUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.buttonStrip = (JCommandButtonStrip) c;
+ installDefaults();
+ installComponents();
+ installListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ uninstallListeners();
+ uninstallComponents();
+ uninstallDefaults();
+
+ c.setLayout(null);
+
+ this.buttonStrip = null;
+ }
+
+ /**
+ * Installs listeners on the associated button strip.
+ */
+ protected void installListeners() {
+ this.changeListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (buttonStrip.getButtonCount() == 1) {
+ buttonStrip
+ .getButton(0)
+ .setLocationOrderKind(
+ AbstractCommandButton.CommandButtonLocationOrderKind.ONLY);
+ } else {
+ buttonStrip
+ .getButton(0)
+ .setLocationOrderKind(
+ AbstractCommandButton.CommandButtonLocationOrderKind.FIRST);
+ for (int i = 1; i < buttonStrip.getButtonCount() - 1; i++) {
+ buttonStrip
+ .getButton(i)
+ .setLocationOrderKind(
+ AbstractCommandButton.CommandButtonLocationOrderKind.MIDDLE);
+
+ }
+ buttonStrip
+ .getButton(buttonStrip.getButtonCount() - 1)
+ .setLocationOrderKind(
+ AbstractCommandButton.CommandButtonLocationOrderKind.LAST);
+ }
+ }
+ };
+ this.buttonStrip.addChangeListener(this.changeListener);
+ }
+
+ /**
+ * Uninstalls listeners from the associated button strip.
+ */
+ protected void uninstallListeners() {
+ this.buttonStrip.removeChangeListener(this.changeListener);
+ this.changeListener = null;
+ }
+
+ /**
+ * Installs defaults on the associated button strip.
+ */
+ protected void installDefaults() {
+ this.buttonStrip.setBorder(new EmptyBorder(0, 0, 0, 0));
+ }
+
+ /**
+ * Uninstalls defaults from the associated button strip.
+ */
+ protected void uninstallDefaults() {
+ }
+
+ /**
+ * Installs subcomponents on the associated button strip.
+ */
+ protected void installComponents() {
+ this.buttonStrip.setLayout(createLayoutManager());
+ }
+
+ /**
+ * Uninstalls subcomponents from the associated ribbon.
+ */
+ protected void uninstallComponents() {
+ }
+
+ /**
+ * Invoked by <code>installUI</code> to create a layout manager object to
+ * manage the {@link JCommandButtonStrip}.
+ *
+ * @return a layout manager object
+ */
+ protected LayoutManager createLayoutManager() {
+ return new ButtonStripLayout();
+ }
+
+ /**
+ * Layout for the button strip.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class ButtonStripLayout implements LayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ int width = 0;
+ int height = 0;
+ if (buttonStrip.getOrientation() == StripOrientation.HORIZONTAL) {
+ for (int i = 0; i < buttonStrip.getButtonCount(); i++) {
+ width += buttonStrip.getButton(i).getPreferredSize().width;
+ height = Math.max(height, buttonStrip.getButton(i)
+ .getPreferredSize().height);
+ }
+ } else {
+ for (int i = 0; i < buttonStrip.getButtonCount(); i++) {
+ height += buttonStrip.getButton(i).getPreferredSize().height;
+ width = Math.max(width, buttonStrip.getButton(i)
+ .getPreferredSize().width);
+ }
+ }
+ Insets ins = c.getInsets();
+ // System.out.println(ins + ":" + width + ":" + height);
+ return new Dimension(width + ins.left + ins.right, height + ins.top
+ + ins.bottom);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ return this.preferredLayoutSize(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ if (buttonStrip.getButtonCount() == 0)
+ return;
+ Insets ins = c.getInsets();
+ int height = c.getHeight() - ins.top - ins.bottom;
+ int width = c.getWidth() - ins.left - ins.right;
+ if (buttonStrip.getOrientation() == StripOrientation.HORIZONTAL) {
+ int totalPreferredWidth = 0;
+ for (int i = 0; i < buttonStrip.getButtonCount(); i++) {
+ AbstractCommandButton currButton = buttonStrip.getButton(i);
+ totalPreferredWidth += currButton.getPreferredSize().width;
+ }
+ int deltaX = (width - totalPreferredWidth)
+ / buttonStrip.getButtonCount();
+ if (buttonStrip.getComponentOrientation().isLeftToRight()) {
+ int x = ins.left;
+ for (int i = 0; i < buttonStrip.getButtonCount(); i++) {
+ AbstractCommandButton currButton = buttonStrip
+ .getButton(i);
+ currButton.setBounds(x, ins.top, currButton
+ .getPreferredSize().width
+ + deltaX, height);
+ x += (currButton.getPreferredSize().width + deltaX);
+ }
+ } else {
+ int x = c.getWidth() - ins.right;
+ for (int i = 0; i < buttonStrip.getButtonCount(); i++) {
+ AbstractCommandButton currButton = buttonStrip
+ .getButton(i);
+ int buttonWidth = currButton.getPreferredSize().width
+ + deltaX;
+ currButton.setBounds(x - buttonWidth, ins.top,
+ buttonWidth, height);
+ x -= buttonWidth;
+ }
+ }
+ } else {
+ int totalPreferredHeight = 0;
+ for (int i = 0; i < buttonStrip.getButtonCount(); i++) {
+ AbstractCommandButton currButton = buttonStrip.getButton(i);
+ totalPreferredHeight += currButton.getPreferredSize().height;
+ }
+ float deltaY = (float) (height - totalPreferredHeight)
+ / (float) buttonStrip.getButtonCount();
+ float y = ins.top;
+ for (int i = 0; i < buttonStrip.getButtonCount(); i++) {
+ AbstractCommandButton currButton = buttonStrip.getButton(i);
+ float buttonHeight = (currButton.getPreferredSize().height + deltaY);
+ currButton.setBounds(ins.left, (int) y, width, (int) Math
+ .ceil(buttonHeight));
+ y += buttonHeight;
+ }
+ }
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandButtonUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandButtonUI.java
new file mode 100755
index 0000000..baad3f3
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandButtonUI.java
@@ -0,0 +1,1263 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.ColorConvertOp;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicGraphicsUtils;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.CommandButtonLayoutManager.CommandButtonLayoutInfo;
+import org.pushingpixels.flamingo.api.common.CommandButtonLayoutManager.CommandButtonSeparatorOrientation;
+import org.pushingpixels.flamingo.api.common.JCommandButtonStrip.StripOrientation;
+import org.pushingpixels.flamingo.api.common.icon.FilteredResizableIcon;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.model.PopupButtonModel;
+import org.pushingpixels.flamingo.api.common.popup.*;
+import org.pushingpixels.flamingo.internal.utils.*;
+
+/**
+ * Basic UI for command button {@link JCommandButton}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicCommandButtonUI extends CommandButtonUI {
+ /**
+ * The associated command button.
+ */
+ protected AbstractCommandButton commandButton;
+
+ /**
+ * Indication whether the mouse pointer is over the associated command
+ * button.
+ */
+ protected boolean isUnderMouse;
+
+ /**
+ * Property change listener.
+ */
+ protected PropertyChangeListener propertyChangeListener;
+
+ /**
+ * Tracks user interaction with the command button (including keyboard and
+ * mouse).
+ */
+ protected BasicCommandButtonListener basicPopupButtonListener;
+
+ /**
+ * Layout information.
+ */
+ protected CommandButtonLayoutManager.CommandButtonLayoutInfo layoutInfo;
+
+ /**
+ * Client property to mark the command button to have square corners. This
+ * client property is for internal use only.
+ */
+ public static final String EMULATE_SQUARE_BUTTON = "flamingo.internal.commandButton.ui.emulateSquare";
+
+ /**
+ * Client property to mark the command button to not dispose the popups on
+ * activation.
+ *
+ * @see #disposePopupsActionListener
+ */
+ public static final String DONT_DISPOSE_POPUPS = "flamingo.internal.commandButton.ui.dontDisposePopups";
+
+ /**
+ * This listener disposes all popup panels when button's action is
+ * activated. An example of scenario would be a command button in the popup
+ * panel of an in-ribbon gallery. When this command button is activated, the
+ * associated popup panel is dismissed.
+ *
+ * @see #DONT_DISPOSE_POPUPS
+ */
+ protected ActionListener disposePopupsActionListener;
+
+ /**
+ * Action listener on the popup area.
+ */
+ protected PopupActionListener popupActionListener;
+
+ /**
+ * The "expand" action icon.
+ */
+ protected ResizableIcon popupActionIcon;
+
+ protected CommandButtonLayoutManager layoutManager;
+
+ /**
+ * Used to provide a LAF-consistent appearance under core LAFs.
+ */
+ protected CellRendererPane buttonRendererPane;
+
+ /**
+ * Used to provide a LAF-consistent appearance under core LAFs.
+ */
+ protected AbstractButton rendererButton;
+
+ /**
+ * Used to paint the separator between the action and popup areas.
+ */
+ protected JSeparator rendererSeparator;
+
+ /**
+ * Internal listener for icon sync, One copy so we can remove what we add
+ */
+ private AsynchronousLoadListener resizableIconSyncListener = new AsynchronousLoadListener() {
+ @Override
+ public void completed(boolean success) {
+ if (success) {
+ if (commandButton != null) {
+ syncIconDimension();
+ syncDisabledIcon();
+ commandButton.repaint();
+ }
+ }
+ }
+ };
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicCommandButtonUI();
+ }
+
+ /**
+ * Creates a new UI delegate.
+ */
+ public BasicCommandButtonUI() {
+ // this.toTakeSavedDimension = false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.commandButton = (AbstractCommandButton) c;
+ installDefaults();
+ installComponents();
+ installListeners();
+ installKeyboardActions();
+
+ this.layoutManager = this.commandButton.getDisplayState()
+ .createLayoutManager(this.commandButton);
+
+ this.updateCustomDimension();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ c.setLayout(null);
+
+ uninstallKeyboardActions();
+ uninstallListeners();
+ uninstallComponents();
+ uninstallDefaults();
+ this.commandButton = null;
+ }
+
+ /**
+ * Installs defaults on the associated command button.
+ */
+ protected void installDefaults() {
+ configureRenderer();
+
+ this.updateBorder();
+ this.syncDisabledIcon();
+ }
+
+ protected void configureRenderer() {
+ this.buttonRendererPane = new CellRendererPane();
+ this.commandButton.add(buttonRendererPane);
+ this.rendererButton = createRendererButton();
+ this.rendererButton.setOpaque(false);
+ this.rendererSeparator = new JSeparator();
+ Font currFont = this.commandButton.getFont();
+ if ((currFont == null) || (currFont instanceof UIResource)) {
+ this.commandButton.setFont(this.rendererButton.getFont());
+ }
+ // special handling for Mac OS X native look-and-feel
+ this.rendererButton.putClientProperty("JButton.buttonType", "square");
+ }
+
+ protected void updateBorder() {
+ Border currBorder = this.commandButton.getBorder();
+ if ((currBorder == null) || (currBorder instanceof UIResource)) {
+ int tb = (int) (this.commandButton.getVGapScaleFactor() * 4);
+ int lr = (int) (this.commandButton.getHGapScaleFactor() * 6);
+ this.commandButton
+ .setBorder(new BorderUIResource.EmptyBorderUIResource(tb,
+ lr, tb, lr));
+ }
+ }
+
+ /**
+ * Creates the renderer button.
+ *
+ * @return The renderer button.
+ */
+ protected AbstractButton createRendererButton() {
+ return new JButton("");
+ }
+
+ /**
+ * Installs subcomponents on the associated command button.
+ */
+ protected void installComponents() {
+ this.updatePopupActionIcon();
+
+ ResizableIcon buttonIcon = this.commandButton.getIcon();
+ if (buttonIcon instanceof AsynchronousLoading) {
+ ((AsynchronousLoading) buttonIcon).addAsynchronousLoadListener(resizableIconSyncListener);
+ }
+
+ if (this.commandButton instanceof JCommandButton) {
+ this.popupActionIcon = this.createPopupActionIcon();
+ }
+ }
+
+ /**
+ * Installs listeners on the associated command button.
+ */
+ protected void installListeners() {
+ this.basicPopupButtonListener = createButtonListener(this.commandButton);
+ if (this.basicPopupButtonListener != null) {
+ this.commandButton.addMouseListener(this.basicPopupButtonListener);
+ this.commandButton
+ .addMouseMotionListener(this.basicPopupButtonListener);
+ this.commandButton.addFocusListener(this.basicPopupButtonListener);
+ this.commandButton.addChangeListener(this.basicPopupButtonListener);
+ }
+
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (AbstractButton.ICON_CHANGED_PROPERTY.equals(evt
+ .getPropertyName())) {
+ Icon newIcon = (Icon) evt.getNewValue();
+ if (newIcon instanceof AsynchronousLoading) {
+ AsynchronousLoading async = (AsynchronousLoading) newIcon;
+ async.addAsynchronousLoadListener(resizableIconSyncListener);
+ if (!async.isLoading()) {
+ syncIconDimension();
+ syncDisabledIcon();
+ commandButton.repaint();
+ }
+ } else {
+ syncIconDimension();
+ syncDisabledIcon();
+ commandButton.revalidate();
+ commandButton.repaint();
+ }
+ Icon oldIcon = (Icon) evt.getOldValue();
+ if (oldIcon instanceof AsynchronousLoading) {
+ ((AsynchronousLoading)oldIcon).removeAsynchronousLoadListener(resizableIconSyncListener);
+ }
+
+ }
+ if ("commandButtonKind".equals(evt.getPropertyName())) {
+ updatePopupActionIcon();
+ }
+ if ("popupOrientationKind".equals(evt.getPropertyName())) {
+ updatePopupActionIcon();
+ }
+ if ("customDimension".equals(evt.getPropertyName())) {
+ updateCustomDimension();
+ }
+ if ("hgapScaleFactor".equals(evt.getPropertyName())) {
+ updateBorder();
+ }
+ if ("vgapScaleFactor".equals(evt.getPropertyName())) {
+ updateBorder();
+ }
+
+ if ("popupModel".equals(evt.getPropertyName())) {
+ // rewire the popup action listener on the new popup model
+ PopupButtonModel oldModel = (PopupButtonModel) evt
+ .getOldValue();
+ PopupButtonModel newModel = (PopupButtonModel) evt
+ .getNewValue();
+
+ if (oldModel != null) {
+ oldModel.removePopupActionListener(popupActionListener);
+ popupActionListener = null;
+ }
+
+ if (newModel != null) {
+ popupActionListener = createPopupActionListener();
+ newModel.addPopupActionListener(popupActionListener);
+ }
+ }
+ if ("displayState".equals(evt.getPropertyName()) || ("enabled".equals(evt.getPropertyName()) && !((Boolean) evt.getNewValue()))) {
+ syncIconDimension();
+ syncDisabledIcon();
+
+ commandButton.invalidate();
+ commandButton.revalidate();
+ commandButton.doLayout();
+ }
+
+ // pass the event to the layout manager
+ if (layoutManager != null) {
+ layoutManager.propertyChange(evt);
+ }
+
+ if ("componentOrientation".equals(evt.getPropertyName())) {
+ updatePopupActionIcon();
+ commandButton.repaint();
+ }
+ }
+ };
+ this.commandButton
+ .addPropertyChangeListener(this.propertyChangeListener);
+
+ this.disposePopupsActionListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ boolean toDismiss = !Boolean.TRUE.equals(commandButton
+ .getClientProperty(DONT_DISPOSE_POPUPS));
+ if (toDismiss) {
+ JCommandPopupMenu menu = (JCommandPopupMenu) SwingUtilities
+ .getAncestorOfClass(JCommandPopupMenu.class,
+ commandButton);
+ if (menu != null) {
+ toDismiss = menu.isToDismissOnChildClick();
+ }
+ }
+ if (toDismiss) {
+ if (SwingUtilities.getAncestorOfClass(JPopupPanel.class,
+ commandButton) != null) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // command button may be cleared if the
+ // button click resulted in LAF switch
+ if (commandButton != null) {
+ // clear the active states
+ commandButton.getActionModel().setPressed(
+ false);
+ commandButton.getActionModel().setRollover(
+ false);
+ commandButton.getActionModel().setArmed(
+ false);
+ }
+ }
+ });
+ }
+ PopupPanelManager.defaultManager().hidePopups(null);
+ }
+ }
+ };
+ this.commandButton.addActionListener(this.disposePopupsActionListener);
+
+ if (this.commandButton instanceof JCommandButton) {
+ this.popupActionListener = this.createPopupActionListener();
+ ((JCommandButton) this.commandButton).getPopupModel()
+ .addPopupActionListener(this.popupActionListener);
+ }
+
+ }
+
+ /**
+ * Creates the icon for the popup area.
+ *
+ * @return The icon for the popup area.
+ */
+ protected ResizableIcon createPopupActionIcon() {
+ return FlamingoUtilities
+ .getCommandButtonPopupActionIcon((JCommandButton) this.commandButton);
+ }
+
+ /**
+ * Creates the button listener for the specified command button.
+ *
+ * @param b
+ * Command button.
+ * @return The button listener for the specified command button.
+ */
+ protected BasicCommandButtonListener createButtonListener(
+ AbstractCommandButton b) {
+ return new BasicCommandButtonListener();
+ }
+
+ /**
+ * Installs the keyboard actions on the associated command button.
+ */
+ protected void installKeyboardActions() {
+ if (this.basicPopupButtonListener != null) {
+ basicPopupButtonListener.installKeyboardActions(this.commandButton);
+ }
+ }
+
+ /**
+ * Uninstalls defaults from the associated command button.
+ */
+ protected void uninstallDefaults() {
+ unconfigureRenderer();
+ }
+
+ protected void unconfigureRenderer() {
+ if (this.buttonRendererPane != null) {
+ this.commandButton.remove(this.buttonRendererPane);
+ }
+ this.buttonRendererPane = null;
+ }
+
+ /**
+ * Uninstalls subcomponents from the associated command button.
+ */
+ protected void uninstallComponents() {
+ ResizableIcon buttonIcon = this.commandButton.getIcon();
+ if (buttonIcon instanceof AsynchronousLoading) {
+ ((AsynchronousLoading) buttonIcon).removeAsynchronousLoadListener(resizableIconSyncListener);
+ }
+ }
+
+ /**
+ * Uninstalls listeners from the associated command button.
+ */
+ protected void uninstallListeners() {
+ if (this.basicPopupButtonListener != null) {
+ this.commandButton
+ .removeMouseListener(this.basicPopupButtonListener);
+ this.commandButton
+ .removeMouseListener(this.basicPopupButtonListener);
+ this.commandButton
+ .removeMouseMotionListener(this.basicPopupButtonListener);
+ this.commandButton
+ .removeFocusListener(this.basicPopupButtonListener);
+ this.commandButton
+ .removeChangeListener(this.basicPopupButtonListener);
+ }
+
+ this.commandButton
+ .removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+
+ this.commandButton
+ .removeActionListener(this.disposePopupsActionListener);
+ this.disposePopupsActionListener = null;
+
+ if (this.commandButton instanceof JCommandButton) {
+ ((JCommandButton) this.commandButton).getPopupModel()
+ .removePopupActionListener(this.popupActionListener);
+ this.popupActionListener = null;
+ }
+
+ }
+
+ /**
+ * Uninstalls the keyboard actions from the associated command button.
+ */
+ protected void uninstallKeyboardActions() {
+ if (this.basicPopupButtonListener != null) {
+ this.basicPopupButtonListener
+ .uninstallKeyboardActions(this.commandButton);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ RenderingUtils.installDesktopHints(g2d);
+ super.update(g2d, c);
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ g.setFont(FlamingoUtilities.getFont(commandButton, "Ribbon.font",
+ "Button.font", "Panel.font"));
+ this.layoutInfo = this.layoutManager.getLayoutInfo(this.commandButton,
+ g);
+ commandButton.putClientProperty("icon.bounds", layoutInfo.iconRect);
+
+ if (this.isPaintingBackground()) {
+ this.paintButtonBackground(g, new Rectangle(0, 0, commandButton
+ .getWidth(), commandButton.getHeight()));
+ }
+ // Graphics2D g2d = (Graphics2D) g.create();
+ // g2d.setColor(new Color(255, 0, 0, 64));
+ // if (getActionClickArea() != null) {
+ // g2d.fill(getActionClickArea());
+ // }
+ // g2d.setColor(new Color(0, 0, 255, 64));
+ // if (getPopupClickArea() != null) {
+ // g2d.fill(getPopupClickArea());
+ // }
+ // g2d.dispose();
+
+ if (layoutInfo.iconRect != null) {
+ this.paintButtonIcon(g, layoutInfo.iconRect);
+ }
+ if (layoutInfo.popupActionRect.getWidth() > 0) {
+ paintPopupActionIcon(g, layoutInfo.popupActionRect);
+ }
+ FontMetrics fm = g.getFontMetrics();
+
+ boolean isTextPaintedEnabled = commandButton.isEnabled();
+ if (commandButton instanceof JCommandButton) {
+ JCommandButton jCommandButton = (JCommandButton) commandButton;
+ isTextPaintedEnabled = layoutInfo.isTextInActionArea ? jCommandButton
+ .getActionModel().isEnabled()
+ : jCommandButton.getPopupModel().isEnabled();
+ }
+
+ g.setColor(getForegroundColor(isTextPaintedEnabled));
+
+ if (layoutInfo.textLayoutInfoList != null) {
+ for (CommandButtonLayoutManager.TextLayoutInfo mainTextLayoutInfo : layoutInfo.textLayoutInfoList) {
+ if (mainTextLayoutInfo.text != null) {
+ BasicGraphicsUtils.drawString(g, mainTextLayoutInfo.text,
+ -1, mainTextLayoutInfo.textRect.x,
+ mainTextLayoutInfo.textRect.y + fm.getAscent());
+ }
+ }
+ }
+
+ if (isTextPaintedEnabled) {
+ g.setColor(FlamingoUtilities.getColor(Color.gray,
+ "Label.disabledForeground"));
+ } else {
+ g.setColor(FlamingoUtilities.getColor(Color.gray,
+ "Label.disabledForeground").brighter());
+ }
+
+ if (layoutInfo.extraTextLayoutInfoList != null) {
+ for (CommandButtonLayoutManager.TextLayoutInfo extraTextLayoutInfo : layoutInfo.extraTextLayoutInfoList) {
+ if (extraTextLayoutInfo.text != null) {
+ BasicGraphicsUtils.drawString(g, extraTextLayoutInfo.text,
+ -1, extraTextLayoutInfo.textRect.x,
+ extraTextLayoutInfo.textRect.y + fm.getAscent());
+ }
+ }
+ }
+
+ if (this.isPaintingSeparators() && (layoutInfo.separatorArea != null)) {
+ if (layoutInfo.separatorOrientation == CommandButtonSeparatorOrientation.HORIZONTAL) {
+ this
+ .paintButtonHorizontalSeparator(g,
+ layoutInfo.separatorArea);
+ } else {
+ this.paintButtonVerticalSeparator(g, layoutInfo.separatorArea);
+ }
+ }
+
+ // Graphics2D g2d = (Graphics2D) g.create();
+ //
+ // g2d.setColor(Color.red);
+ // g2d.draw(layoutInfo.iconRect);
+ // g2d.setColor(Color.blue);
+ // if (layoutInfo.textLayoutInfoList != null) {
+ // for (CommandButtonLayoutManager.TextLayoutInfo mainTextLayoutInfo :
+ // layoutInfo.textLayoutInfoList) {
+ // if (mainTextLayoutInfo.text != null) {
+ // g2d.draw(mainTextLayoutInfo.textRect);
+ // }
+ // }
+ // }
+ // g2d.setColor(Color.magenta);
+ // if (layoutInfo.extraTextLayoutInfoList != null) {
+ // for (CommandButtonLayoutManager.TextLayoutInfo extraTextLayoutInfo :
+ // layoutInfo.extraTextLayoutInfoList) {
+ // if (extraTextLayoutInfo.text != null) {
+ // g2d.draw(extraTextLayoutInfo.textRect);
+ // }
+ // }
+ // }
+ // g2d.setColor(Color.green);
+ // g2d.draw(layoutInfo.popupActionRect);
+ // g2d.dispose();
+ }
+
+ protected Color getForegroundColor(boolean isTextPaintedEnabled) {
+ if (isTextPaintedEnabled) {
+ return FlamingoUtilities.getColor(Color.black, "Button.foreground");
+ } else {
+ return FlamingoUtilities.getColor(Color.gray,
+ "Label.disabledForeground");
+ }
+ }
+
+ /**
+ * Paints the icon of the popup area.
+ *
+ * @param g
+ * Graphics context.
+ * @param popupActionRect
+ */
+ protected void paintPopupActionIcon(Graphics g, Rectangle popupActionRect) {
+ int size = Math.max(popupActionRect.width - 2, 7);
+ if (size % 2 == 0)
+ size--;
+ popupActionIcon.setDimension(new Dimension(size, size));
+ popupActionIcon.paintIcon(this.commandButton, g, popupActionRect.x
+ + (popupActionRect.width - size) / 2, popupActionRect.y
+ + (popupActionRect.height - size) / 2);
+ }
+
+ /**
+ * Returns the current icon.
+ *
+ * @return Current icon.
+ */
+ protected Icon getIconToPaint() {
+ return (toUseDisabledIcon() && this.commandButton.getDisabledIcon() != null) ? this.commandButton
+ .getDisabledIcon()
+ : this.commandButton.getIcon();
+ }
+
+ protected boolean toUseDisabledIcon() {
+ // special case for command buttons with POPUP_ONLY kind -
+ // check the popup model
+ boolean toUseDisabledIcon;
+ if (this.commandButton instanceof JCommandButton
+ && ((JCommandButton) this.commandButton).getCommandButtonKind() == JCommandButton.CommandButtonKind.POPUP_ONLY) {
+ toUseDisabledIcon = !((JCommandButton) this.commandButton)
+ .getPopupModel().isEnabled();
+ } else {
+ toUseDisabledIcon = !this.commandButton.getActionModel()
+ .isEnabled();
+ }
+ return toUseDisabledIcon;
+ }
+
+ /**
+ * Paints command button vertical separator.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param separatorArea
+ * Separator area.
+ */
+ protected void paintButtonVerticalSeparator(Graphics graphics,
+ Rectangle separatorArea) {
+ this.buttonRendererPane.setBounds(0, 0, this.commandButton.getWidth(),
+ this.commandButton.getHeight());
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ this.rendererSeparator.setOrientation(JSeparator.VERTICAL);
+ this.buttonRendererPane.paintComponent(g2d, this.rendererSeparator,
+ this.commandButton, separatorArea.x, 2, 2, this.commandButton
+ .getHeight() - 4, true);
+ g2d.dispose();
+ }
+
+ /**
+ * Paints command button horizontal separator.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param separatorArea
+ * Separator area.
+ */
+ protected void paintButtonHorizontalSeparator(Graphics graphics,
+ Rectangle separatorArea) {
+ this.buttonRendererPane.setBounds(0, 0, this.commandButton.getWidth(),
+ this.commandButton.getHeight());
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ this.rendererSeparator.setOrientation(JSeparator.HORIZONTAL);
+ this.buttonRendererPane.paintComponent(g2d, this.rendererSeparator,
+ this.commandButton, 2, separatorArea.y, this.commandButton
+ .getWidth() - 4, 2, true);
+ g2d.dispose();
+ }
+
+ /**
+ * Paints command button background.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param toFill
+ * Rectangle for the background.
+ */
+ protected void paintButtonBackground(Graphics graphics, Rectangle toFill) {
+ ButtonModel actionModel = this.commandButton.getActionModel();
+ PopupButtonModel popupModel = (this.commandButton instanceof JCommandButton) ? ((JCommandButton) this.commandButton)
+ .getPopupModel()
+ : null;
+
+ // first time - paint the full background passing both models
+ this.paintButtonBackground(graphics, toFill, actionModel, popupModel);
+
+ Rectangle actionArea = this.getLayoutInfo().actionClickArea;
+ Rectangle popupArea = this.getLayoutInfo().popupClickArea;
+ if ((actionArea != null) && !actionArea.isEmpty()) {
+ // now overlay the action area with the background matching action
+ // model
+ Graphics2D graphicsAction = (Graphics2D) graphics.create();
+ // System.out.println(actionArea);
+ graphicsAction.clip(actionArea);
+ float actionAlpha = 0.4f;
+ if ((popupModel != null) && !popupModel.isEnabled())
+ actionAlpha = 1.0f;
+ graphicsAction.setComposite(AlphaComposite.SrcOver
+ .derive(actionAlpha));
+ // System.out.println(graphicsAction.getClipBounds());
+ this.paintButtonBackground(graphicsAction, toFill, actionModel);
+ graphicsAction.dispose();
+ }
+ if ((popupArea != null) && !popupArea.isEmpty()) {
+ // now overlay the popup area with the background matching popup
+ // model
+ Graphics2D graphicsPopup = (Graphics2D) graphics.create();
+ // System.out.println(popupArea);
+ graphicsPopup.clip(popupArea);
+ // System.out.println(graphicsPopup.getClipBounds());
+ float popupAlpha = 0.4f;
+ if (!actionModel.isEnabled())
+ popupAlpha = 1.0f;
+ graphicsPopup.setComposite(AlphaComposite.SrcOver
+ .derive(popupAlpha));
+ this.paintButtonBackground(graphicsPopup, toFill, popupModel);
+ graphicsPopup.dispose();
+ }
+ }
+
+ /**
+ * Paints the background of the command button.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param toFill
+ * Rectangle to fill.
+ * @param modelToUse
+ * Button models to use for computing the background fill.
+ */
+ protected void paintButtonBackground(Graphics graphics, Rectangle toFill,
+ ButtonModel... modelToUse) {
+ if (modelToUse.length == 0)
+ return;
+ if ((modelToUse.length == 1) && (modelToUse[0] == null))
+ return;
+
+ this.buttonRendererPane.setBounds(toFill.x, toFill.y, toFill.width,
+ toFill.height);
+ this.rendererButton.setRolloverEnabled(true);
+ boolean isEnabled = true;
+ boolean isRollover = false;
+ boolean isPressed = true;
+ boolean isArmed = true;
+ boolean isSelected = true;
+ for (ButtonModel model : modelToUse) {
+ if (model == null)
+ continue;
+ isEnabled = isEnabled && model.isEnabled();
+ isRollover = isRollover || model.isRollover();
+ isPressed = isPressed && model.isPressed();
+ isArmed = isArmed && model.isArmed();
+ isSelected = isSelected && model.isSelected();
+ if (model instanceof PopupButtonModel) {
+ isRollover = isRollover
+ || ((PopupButtonModel) model).isPopupShowing();
+ }
+ }
+ this.rendererButton.getModel().setEnabled(isEnabled);
+ this.rendererButton.getModel().setRollover(isRollover);
+ this.rendererButton.getModel().setPressed(isPressed);
+ this.rendererButton.getModel().setArmed(isArmed);
+ this.rendererButton.getModel().setSelected(isSelected);
+ // System.out.println(this.commandButton.getText() + " - e:"
+ // + this.rendererButton.getModel().isEnabled() + ", s:"
+ // + this.rendererButton.getModel().isSelected() + ", r:"
+ // + this.rendererButton.getModel().isRollover() + ", p:"
+ // + this.rendererButton.getModel().isPressed() + ", a:"
+ // + this.rendererButton.getModel().isArmed());
+ Graphics2D g2d = (Graphics2D) graphics.create();
+
+ Color borderColor = FlamingoUtilities.getBorderColor();
+ if (Boolean.TRUE.equals(this.commandButton
+ .getClientProperty(EMULATE_SQUARE_BUTTON))) {
+ this.buttonRendererPane.paintComponent(g2d, this.rendererButton,
+ this.commandButton, toFill.x - toFill.width / 2, toFill.y
+ - toFill.height / 2, 2 * toFill.width,
+ 2 * toFill.height, true);
+ g2d.setColor(borderColor);
+ g2d.drawRect(toFill.x, toFill.y, toFill.width - 1,
+ toFill.height - 1);
+ } else {
+ AbstractCommandButton.CommandButtonLocationOrderKind locationKind = this.commandButton
+ .getLocationOrderKind();
+ Insets outsets = (this.rendererButton instanceof JToggleButton) ? ButtonSizingUtils
+ .getInstance().getToggleOutsets()
+ : ButtonSizingUtils.getInstance().getOutsets();
+ if (locationKind != null) {
+ if (locationKind == AbstractCommandButton.CommandButtonLocationOrderKind.ONLY) {
+ this.buttonRendererPane.paintComponent(g2d,
+ this.rendererButton, this.commandButton, toFill.x
+ - outsets.left, toFill.y - outsets.top,
+ toFill.width + outsets.left + outsets.right,
+ toFill.height + outsets.top + outsets.bottom, true);
+ } else {
+ // special case for parent component which is a vertical
+ // button strip
+ Component parent = this.commandButton.getParent();
+ if ((parent instanceof JCommandButtonStrip)
+ && (((JCommandButtonStrip) parent).getOrientation() == StripOrientation.VERTICAL)) {
+ switch (locationKind) {
+ case FIRST:
+ this.buttonRendererPane.paintComponent(g2d,
+ this.rendererButton, this.commandButton,
+ toFill.x - outsets.left, toFill.y
+ - outsets.top, toFill.width
+ + outsets.left + outsets.right,
+ 2 * toFill.height, true);
+ g2d.setColor(borderColor);
+ g2d.drawLine(toFill.x + 1, toFill.y + toFill.height
+ - 1, toFill.x + toFill.width - 2, toFill.y
+ + toFill.height - 1);
+ break;
+ case LAST:
+ this.buttonRendererPane.paintComponent(g2d,
+ this.rendererButton, this.commandButton,
+ toFill.x - outsets.left, toFill.y
+ - toFill.height, toFill.width
+ + outsets.left + outsets.right, 2
+ * toFill.height + outsets.bottom,
+ true);
+ break;
+ case MIDDLE:
+ this.buttonRendererPane.paintComponent(g2d,
+ this.rendererButton, this.commandButton,
+ toFill.x - outsets.left, toFill.y
+ - toFill.height, toFill.width
+ + outsets.left + outsets.right,
+ 3 * toFill.height, true);
+ g2d.setColor(borderColor);
+ g2d.drawLine(toFill.x + 1, toFill.y + toFill.height
+ - 1, toFill.x + toFill.width - 2, toFill.y
+ + toFill.height - 1);
+ }
+ } else {
+ // horizontal
+ boolean ltr = this.commandButton
+ .getComponentOrientation().isLeftToRight();
+ if (locationKind == AbstractCommandButton.CommandButtonLocationOrderKind.MIDDLE) {
+ this.buttonRendererPane.paintComponent(g2d,
+ this.rendererButton, this.commandButton,
+ toFill.x - toFill.width, toFill.y
+ - outsets.top, 3 * toFill.width,
+ toFill.height + outsets.top
+ + outsets.bottom, true);
+ g2d.setColor(borderColor);
+ g2d.drawLine(toFill.x + toFill.width - 1,
+ toFill.y + 1, toFill.x + toFill.width - 1,
+ toFill.y + toFill.height - 2);
+ } else {
+ boolean curveOnLeft = (ltr && (locationKind == AbstractCommandButton.CommandButtonLocationOrderKind.FIRST))
+ || (!ltr && (locationKind == AbstractCommandButton.CommandButtonLocationOrderKind.LAST));
+ if (curveOnLeft) {
+ this.buttonRendererPane.paintComponent(g2d,
+ this.rendererButton,
+ this.commandButton, toFill.x
+ - outsets.left, toFill.y
+ - outsets.top,
+ 2 * toFill.width, toFill.height
+ + outsets.top + outsets.bottom,
+ true);
+ g2d.setColor(borderColor);
+ g2d.drawLine(toFill.x + toFill.width - 1,
+ toFill.y + 1, toFill.x + toFill.width
+ - 1, toFill.y + toFill.height
+ - 2);
+ } else {
+ this.buttonRendererPane.paintComponent(g2d,
+ this.rendererButton,
+ this.commandButton, toFill.x
+ - toFill.width, toFill.y
+ - outsets.top, 2 * toFill.width
+ + outsets.right, toFill.height
+ + outsets.top + outsets.bottom,
+ true);
+ }
+ }
+ }
+ }
+ } else {
+ this.buttonRendererPane.paintComponent(g2d,
+ this.rendererButton, this.commandButton, toFill.x
+ - outsets.left, toFill.y - outsets.top,
+ toFill.width + outsets.left + outsets.right,
+ toFill.height + outsets.top + outsets.bottom, true);
+ }
+ }
+ g2d.dispose();
+ }
+
+ /**
+ * Updates the custom dimension.
+ */
+ protected void updateCustomDimension() {
+ int dimension = this.commandButton.getCustomDimension();
+
+ if (dimension > 0) {
+ this.commandButton.getIcon().setDimension(
+ new Dimension(dimension, dimension));
+ this.commandButton
+ .setDisplayState(CommandButtonDisplayState.FIT_TO_ICON);
+
+ this.commandButton.invalidate();
+ this.commandButton.revalidate();
+ this.commandButton.doLayout();
+ this.commandButton.repaint();
+ }
+ }
+
+ /**
+ * Updates the popup action icon.
+ */
+ protected void updatePopupActionIcon() {
+ JCommandButton button = (JCommandButton) this.commandButton;
+ if (button.getCommandButtonKind().hasPopup()) {
+ this.popupActionIcon = this.createPopupActionIcon();
+ } else {
+ this.popupActionIcon = null;
+ }
+ }
+
+ /**
+ * Paints the button icon.
+ *
+ * @param g
+ * Graphics context.
+ * @param iconRect
+ * Icon rectangle.
+ */
+ protected void paintButtonIcon(Graphics g, Rectangle iconRect) {
+ Icon iconToPaint = this.getIconToPaint();
+ if ((iconRect == null) || (iconToPaint == null)
+ || (iconRect.width == 0) || (iconRect.height == 0)) {
+ return;
+ }
+
+ iconToPaint.paintIcon(this.commandButton, g, iconRect.x, iconRect.y);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.ComponentUI#getPreferredSize(javax.swing.JComponent)
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ AbstractCommandButton button = (AbstractCommandButton) c;
+ return this.layoutManager.getPreferredSize(button);
+ }
+
+ @Override
+ public CommandButtonLayoutInfo getLayoutInfo() {
+ if (this.layoutInfo != null) {
+ return this.layoutInfo;
+ }
+ this.layoutInfo = this.layoutManager.getLayoutInfo(commandButton,
+ this.commandButton.getGraphics());
+ return this.layoutInfo;
+ }
+
+ /**
+ * Returns the layout gap for the visuals of the associated command button.
+ *
+ * @return The layout gap for the visuals of the associated command button.
+ */
+ protected int getLayoutGap() {
+ Font font = this.commandButton.getFont();
+ if (font == null)
+ font = UIManager.getFont("Button.font");
+ return (font.getSize() - 4) / 4;
+ }
+
+ /**
+ * Returns indication whether the action-popup areas separator is painted.
+ *
+ * @return <code>true</code> if the action-popup areas separator is painted.
+ */
+ protected boolean isPaintingSeparators() {
+ PopupButtonModel popupModel = (this.commandButton instanceof JCommandButton) ? ((JCommandButton) this.commandButton)
+ .getPopupModel()
+ : null;
+ boolean isActionRollover = this.commandButton.getActionModel()
+ .isRollover();
+ boolean isPopupRollover = (popupModel != null)
+ && popupModel.isRollover();
+ // Rectangle actionArea = this.getActionClickArea();
+ // Rectangle popupArea = this.getPopupClickArea();
+ // boolean hasNonEmptyAreas = (actionArea.width * actionArea.height
+ // * popupArea.width * popupArea.height > 0);
+ return // hasNonEmptyAreas &&
+ (isActionRollover || isPopupRollover);
+ }
+
+ /**
+ * Returns indication whether the button background is painted.
+ *
+ * @return <code>true</code> if the button background is painted.
+ */
+ protected boolean isPaintingBackground() {
+ PopupButtonModel popupModel = (this.commandButton instanceof JCommandButton) ? ((JCommandButton) this.commandButton)
+ .getPopupModel()
+ : null;
+ boolean isActionSelected = this.commandButton.getActionModel()
+ .isSelected();
+ boolean isPopupSelected = (popupModel != null)
+ && popupModel.isSelected();
+ boolean isActionRollover = this.commandButton.getActionModel()
+ .isRollover();
+ boolean isPopupRollover = (popupModel != null)
+ && popupModel.isRollover();
+ boolean isPopupShowing = (popupModel != null)
+ && (popupModel.isPopupShowing());
+ boolean isActionArmed = this.commandButton.getActionModel().isArmed();
+ boolean isPopupArmed = (popupModel != null) && (popupModel.isArmed());
+
+ return (isActionSelected || isPopupSelected || isActionRollover
+ || isPopupRollover || isPopupShowing || isActionArmed
+ || isPopupArmed || !this.commandButton.isFlat());
+ }
+
+ /**
+ * Creates the popup action listener for this command button.
+ *
+ * @return Popup action listener for this command button.
+ */
+ protected PopupActionListener createPopupActionListener() {
+ return new PopupActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ processPopupAction();
+ }
+ };
+ }
+
+ protected void processPopupAction() {
+ boolean wasPopupShowing = false;
+ if (this.commandButton instanceof JCommandButton) {
+ wasPopupShowing = ((JCommandButton) this.commandButton)
+ .getPopupModel().isPopupShowing();
+ }
+
+ // dismiss all the popups that are currently showing
+ // up until <this> button.
+ PopupPanelManager.defaultManager().hidePopups(commandButton);
+
+ if (!(commandButton instanceof JCommandButton))
+ return;
+
+ if (wasPopupShowing)
+ return;
+
+ JCommandButton jcb = (JCommandButton) this.commandButton;
+
+ // check if the command button has an associated popup
+ // panel
+ PopupPanelCallback popupCallback = jcb.getPopupCallback();
+ final JPopupPanel popupPanel = (popupCallback != null) ? popupCallback
+ .getPopupPanel(jcb) : null;
+ if (popupPanel != null) {
+ popupPanel.applyComponentOrientation(jcb.getComponentOrientation());
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if ((commandButton == null) || (popupPanel == null))
+ return;
+
+ if (!commandButton.isShowing())
+ return;
+
+ popupPanel.doLayout();
+
+ int x = 0;
+ int y = 0;
+
+ JPopupPanel.PopupPanelCustomizer customizer = popupPanel
+ .getCustomizer();
+ boolean ltr = commandButton.getComponentOrientation()
+ .isLeftToRight();
+ Dimension popupPanelPrefSize = popupPanel.getPreferredSize();
+ if (customizer == null) {
+ switch (((JCommandButton) commandButton)
+ .getPopupOrientationKind()) {
+ case DOWNWARD:
+ if (ltr) {
+ x = commandButton.getLocationOnScreen().x;
+ } else {
+ x = commandButton.getLocationOnScreen().x
+ + commandButton.getWidth()
+ - popupPanelPrefSize.width;
+ }
+ y = commandButton.getLocationOnScreen().y
+ + commandButton.getSize().height;
+ break;
+ case SIDEWARD:
+ if (ltr) {
+ x = commandButton.getLocationOnScreen().x
+ + commandButton.getWidth();
+ } else {
+ x = commandButton.getLocationOnScreen().x
+ - popupPanelPrefSize.width;
+ }
+ y = commandButton.getLocationOnScreen().y
+ + getLayoutInfo().popupClickArea.y;
+ break;
+ }
+ } else {
+ Rectangle placementRect = customizer.getScreenBounds();
+ // System.out.println(placementRect);
+ x = placementRect.x;
+ y = placementRect.y;
+ }
+
+ // make sure that the popup stays in bounds
+ Rectangle scrBounds = commandButton
+ .getGraphicsConfiguration().getBounds();
+ int pw = popupPanelPrefSize.width;
+ int ph = popupPanelPrefSize.height;
+ if (pw > scrBounds.width || ph > scrBounds.height) {
+ pw = Math.min(pw, scrBounds.width);
+ ph = Math.min(ph, scrBounds.height);
+ popupPanelPrefSize = new Dimension(pw, ph);
+ popupPanel.setPreferredSize(popupPanelPrefSize);
+ popupPanel.setSize(popupPanelPrefSize);
+ popupPanel.doLayout();
+ }
+
+ if ((x + pw) > (scrBounds.x + scrBounds.width)) {
+ x = scrBounds.x + scrBounds.width - pw;
+ }
+ if ((y + ph) > (scrBounds.y + scrBounds.height)) {
+ y = scrBounds.y + scrBounds.height - ph;
+ }
+
+ // get the popup and show it
+ if (customizer != null) {
+ Rectangle placementRect = customizer.getScreenBounds();
+ popupPanel.setPreferredSize(new Dimension(
+ placementRect.width, placementRect.height));
+ }
+ Popup popup = PopupFactory.getSharedInstance().getPopup(
+ commandButton, popupPanel, x, y);
+ // System.out.println("Showing the popup panel");
+ PopupPanelManager.defaultManager().addPopup(commandButton,
+ popup, popupPanel);
+ }
+ });
+ }
+ }
+
+ protected void syncDisabledIcon() {
+ ResizableIcon currDisabledIcon = this.commandButton.getDisabledIcon();
+ ResizableIcon icon = this.commandButton.getIcon();
+ if ((currDisabledIcon == null)
+ || (currDisabledIcon instanceof UIResource)) {
+ if (icon != null) {
+ this.commandButton.setDisabledIcon(new ResizableIconUIResource(
+ new FilteredResizableIcon(icon, new ColorConvertOp(
+ ColorSpace.getInstance(ColorSpace.CS_GRAY),
+ null))));
+ } else {
+ this.commandButton.setDisabledIcon(null);
+ }
+ } else {
+ // disabled icon coming from app code
+ if (icon != null) {
+ this.commandButton.getDisabledIcon()
+ .setDimension(
+ new Dimension(icon.getIconWidth(), icon
+ .getIconHeight()));
+ }
+ }
+ }
+
+ protected void syncIconDimension() {
+ ResizableIcon icon = this.commandButton.getIcon();
+ CommandButtonDisplayState commandButtonState = this.commandButton
+ .getDisplayState();
+
+ this.layoutManager = commandButtonState
+ .createLayoutManager(this.commandButton);
+
+ if (icon == null)
+ return;
+
+ int maxHeight = layoutManager.getPreferredIconSize();
+ if (maxHeight < 0) {
+ maxHeight = this.commandButton.getIcon().getIconHeight();
+ }
+
+ if (commandButtonState != CommandButtonDisplayState.FIT_TO_ICON) {
+ @SuppressWarnings("SuspiciousNameCombination")
+ Dimension newDim = new Dimension(maxHeight, maxHeight);
+ icon.setDimension(newDim);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.CommandButtonUI#getKeyTipAnchorCenterPoint()
+ */
+ @Override
+ public Point getKeyTipAnchorCenterPoint() {
+ return this.layoutManager
+ .getKeyTipAnchorCenterPoint(this.commandButton);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandMenuButtonUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandMenuButtonUI.java
new file mode 100644
index 0000000..3acd492
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandMenuButtonUI.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.JCommandMenuButton;
+import org.pushingpixels.flamingo.api.common.RolloverActionListener;
+import org.pushingpixels.flamingo.internal.utils.KeyTipRenderingUtilities;
+
+/**
+ * Basic UI delegate for the {@link JCommandMenuButton} component.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicCommandMenuButtonUI extends BasicCommandButtonUI {
+ /**
+ * Rollover menu mouse listener.
+ */
+ protected MouseListener rolloverMenuMouseListener;
+
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicCommandMenuButtonUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.ui.BasicCommandButtonUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.rolloverMenuMouseListener = new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (commandButton.isEnabled()) {
+ int modifiers = 0;
+ AWTEvent currentEvent = EventQueue.getCurrentEvent();
+ if (currentEvent instanceof InputEvent) {
+ modifiers = ((InputEvent) currentEvent).getModifiers();
+ } else if (currentEvent instanceof ActionEvent) {
+ modifiers = ((ActionEvent) currentEvent).getModifiers();
+ }
+ fireRolloverActionPerformed(new ActionEvent(this,
+ ActionEvent.ACTION_PERFORMED, commandButton
+ .getActionModel().getActionCommand(),
+ EventQueue.getMostRecentEventTime(), modifiers));
+
+ processPopupAction();
+ }
+ }
+ };
+ this.commandButton.addMouseListener(this.rolloverMenuMouseListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.commandButton.removeMouseListener(this.rolloverMenuMouseListener);
+ this.rolloverMenuMouseListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /**
+ * Fires the rollover action on all registered handlers.
+ *
+ * @param e
+ * Event object.
+ */
+ protected void fireRolloverActionPerformed(ActionEvent e) {
+ // Guaranteed to return a non-null array
+ RolloverActionListener[] listeners = commandButton
+ .getListeners(RolloverActionListener.class);
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 1; i >= 0; i--) {
+ (listeners[i]).actionPerformed(e);
+ }
+ }
+
+ @Override
+ public void update(Graphics g, JComponent c) {
+ JCommandMenuButton menuButton = (JCommandMenuButton) c;
+ super.update(g, c);
+
+ // System.out.println("Updating " + menuButton.getText());
+ KeyTipRenderingUtilities.renderMenuButtonKeyTips(g, menuButton,
+ layoutManager);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandToggleButtonUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandToggleButtonUI.java
new file mode 100644
index 0000000..c9035e0
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandToggleButtonUI.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.JCommandToggleButton;
+
+/**
+ * Basic UI for command toggle button {@link JCommandToggleButton}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicCommandToggleButtonUI extends BasicCommandButtonUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicCommandToggleButtonUI();
+ }
+
+ /**
+ * Creates a new UI delegate.
+ */
+ public BasicCommandToggleButtonUI() {
+ }
+
+ @Override
+ protected void updatePopupActionIcon() {
+ }
+
+ @Override
+ protected boolean isPaintingSeparators() {
+ return false;
+ }
+
+ @Override
+ protected AbstractButton createRendererButton() {
+ return new JToggleButton("");
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandToggleMenuButtonUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandToggleMenuButtonUI.java
new file mode 100644
index 0000000..d757fb8
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicCommandToggleMenuButtonUI.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.JCommandToggleMenuButton;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+/**
+ * Basic UI delegate for the {@link JCommandToggleMenuButton} component.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicCommandToggleMenuButtonUI extends BasicCommandToggleButtonUI {
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicCommandToggleMenuButtonUI();
+ }
+
+ @Override
+ protected void paintButtonIcon(Graphics g, Rectangle iconRect) {
+ boolean isSelected = this.commandButton.getActionModel().isSelected();
+ if (isSelected) {
+ Color selectionColor = FlamingoUtilities.getColor(Color.blue
+ .darker(), "Table.selectionBackground", "textHighlight");
+ Rectangle extended = new Rectangle(iconRect.x - 1, iconRect.y - 1,
+ iconRect.width + 1, iconRect.height + 1);
+ g.setColor(selectionColor);
+ g.fillRect(extended.x, extended.y, extended.width, extended.height);
+ g.setColor(selectionColor.darker());
+ g.drawRect(extended.x, extended.y, extended.width, extended.height);
+ }
+ super.paintButtonIcon(g, iconRect);
+ // does it actually have an icon?
+ Icon iconToPaint = this.getIconToPaint();
+ if (isSelected && (iconToPaint == null)) {
+ // draw a checkmark
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setColor(getForegroundColor(this.commandButton.getActionModel()
+ .isEnabled()));
+
+ int iw = iconRect.width;
+ int ih = iconRect.height;
+ GeneralPath path = new GeneralPath();
+
+ path.moveTo(0.2f * iw, 0.5f * ih);
+ path.lineTo(0.42f * iw, 0.8f * ih);
+ path.lineTo(0.8f * iw, 0.2f * ih);
+ g2d.translate(iconRect.x, iconRect.y);
+ Stroke stroke = new BasicStroke((float) 0.1 * iw,
+ BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+ g2d.setStroke(stroke);
+ g2d.draw(path);
+
+ g2d.dispose();
+ }
+ }
+
+ @Override
+ protected boolean isPaintingBackground() {
+ boolean isActionRollover = this.commandButton.getActionModel()
+ .isRollover();
+
+ return (isActionRollover || !this.commandButton.isFlat());
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicRichTooltipPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicRichTooltipPanelUI.java
new file mode 100755
index 0000000..2f29797
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicRichTooltipPanelUI.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+import java.awt.font.*;
+import java.awt.geom.AffineTransform;
+import java.text.AttributedString;
+import java.util.ArrayList;
+
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.flamingo.api.common.RichTooltip;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+/**
+ * Basic UI for rich tooltip panel {@link JRichTooltipPanel}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicRichTooltipPanelUI extends RichTooltipPanelUI {
+ /**
+ * The associated tooltip panel.
+ */
+ protected JRichTooltipPanel richTooltipPanel;
+
+ protected java.util.List<JLabel> titleLabels;
+
+ protected java.util.List<JLabel> descriptionLabels;
+
+ protected JLabel mainImageLabel;
+
+ protected JSeparator footerSeparator;
+
+ protected JLabel footerImageLabel;
+
+ protected java.util.List<JLabel> footerLabels;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicRichTooltipPanelUI();
+ }
+
+ public BasicRichTooltipPanelUI() {
+ this.titleLabels = new ArrayList<JLabel>();
+ this.descriptionLabels = new ArrayList<JLabel>();
+ this.footerLabels = new ArrayList<JLabel>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.richTooltipPanel = (JRichTooltipPanel) c;
+ super.installUI(this.richTooltipPanel);
+ installDefaults();
+ installComponents();
+ installListeners();
+
+ this.richTooltipPanel.setLayout(createLayoutManager());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ uninstallListeners();
+ uninstallComponents();
+ uninstallDefaults();
+ super.uninstallUI(this.richTooltipPanel);
+ }
+
+ /**
+ * Installs default settings for the associated rich tooltip panel.
+ */
+ protected void installDefaults() {
+ Border b = this.richTooltipPanel.getBorder();
+ if (b == null || b instanceof UIResource) {
+ Border toSet = UIManager.getBorder("RichTooltipPanel.border");
+ if (toSet == null)
+ toSet = new BorderUIResource.CompoundBorderUIResource(
+ new LineBorder(FlamingoUtilities.getBorderColor()),
+ new EmptyBorder(2, 4, 3, 4));
+ this.richTooltipPanel.setBorder(toSet);
+ }
+ LookAndFeel.installProperty(this.richTooltipPanel, "opaque",
+ Boolean.TRUE);
+ }
+
+ /**
+ * Installs listeners on the associated rich tooltip panel.
+ */
+ protected void installListeners() {
+ }
+
+ /**
+ * Installs components on the associated rich tooltip panel.
+ */
+ protected void installComponents() {
+ }
+
+ /**
+ * Uninstalls default settings from the associated rich tooltip panel.
+ */
+ protected void uninstallDefaults() {
+ LookAndFeel.uninstallBorder(this.richTooltipPanel);
+ }
+
+ /**
+ * Uninstalls listeners from the associated rich tooltip panel.
+ */
+ protected void uninstallListeners() {
+ }
+
+ /**
+ * Uninstalls subcomponents from the associated rich tooltip panel.
+ */
+ protected void uninstallComponents() {
+ this.removeExistingComponents();
+ }
+
+ @Override
+ public void update(Graphics g, JComponent c) {
+ this.paintBackground(g);
+ this.paint(g, c);
+ }
+
+ protected void paintBackground(Graphics g) {
+ Color main = FlamingoUtilities.getColor(Color.gray,
+ "Label.disabledForeground").brighter();
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setPaint(new GradientPaint(0, 0, FlamingoUtilities.getLighterColor(
+ main, 0.9), 0, this.richTooltipPanel.getHeight(),
+ FlamingoUtilities.getLighterColor(main, 0.4)));
+ g2d.fillRect(0, 0, this.richTooltipPanel.getWidth(),
+ this.richTooltipPanel.getHeight());
+ g2d.setFont(FlamingoUtilities.getFont(this.richTooltipPanel,
+ "Ribbon.font", "Button.font", "Panel.font"));
+ g2d.dispose();
+ }
+
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ }
+
+ protected LayoutManager createLayoutManager() {
+ return new RichTooltipPanelLayout();
+ }
+
+ protected class RichTooltipPanelLayout implements LayoutManager {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return this.preferredLayoutSize(parent);
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ Insets ins = parent.getInsets();
+ int gap = getLayoutGap();
+ Font font = FlamingoUtilities.getFont(parent, "Ribbon.font",
+ "Button.font", "Panel.font");
+ Font titleFont = font.deriveFont(Font.BOLD);
+
+ // the main text gets 200 pixels. The width is defined
+ // by this and the presence of the main text.
+ // The height is defined based on the width and the
+ // text broken into multiline paragraphs
+
+ int descTextWidth = getDescriptionTextWidth();
+ int width = ins.left + 2 * gap + descTextWidth + ins.right;
+ RichTooltip tooltipInfo = richTooltipPanel.getTooltipInfo();
+ FontRenderContext frc = new FontRenderContext(
+ new AffineTransform(), true, false);
+ if (tooltipInfo.getMainImage() != null) {
+ width += tooltipInfo.getMainImage().getWidth(null);
+ }
+
+ int fontHeight = parent.getFontMetrics(font).getHeight();
+
+ int height = ins.top;
+
+ // The title label
+ int titleTextHeight = 0;
+ AttributedString titleAttributedDescription = new AttributedString(
+ tooltipInfo.getTitle());
+ titleAttributedDescription.addAttribute(TextAttribute.FONT,
+ titleFont);
+ LineBreakMeasurer titleLineBreakMeasurer = new LineBreakMeasurer(
+ titleAttributedDescription.getIterator(), frc);
+ int maxTitleLineWidth = 0;
+ while (true) {
+ TextLayout tl = titleLineBreakMeasurer
+ .nextLayout(descTextWidth);
+ if (tl == null)
+ break;
+ titleTextHeight += fontHeight;
+ int lineWidth = (int) Math.ceil(tl.getBounds().getWidth());
+ maxTitleLineWidth = Math.max(maxTitleLineWidth, lineWidth);
+ }
+ height += titleTextHeight;
+
+ // The description text
+ int descriptionTextHeight = 0;
+ for (String descText : tooltipInfo.getDescriptionSections()) {
+ AttributedString descAttributedDescription = new AttributedString(
+ descText);
+ descAttributedDescription
+ .addAttribute(TextAttribute.FONT, font);
+ LineBreakMeasurer descLineBreakMeasurer = new LineBreakMeasurer(
+ descAttributedDescription.getIterator(), frc);
+ while (true) {
+ TextLayout tl = descLineBreakMeasurer
+ .nextLayout(descTextWidth);
+ if (tl == null)
+ break;
+ descriptionTextHeight += fontHeight;
+ }
+ // add an empty line after the paragraph
+ descriptionTextHeight += fontHeight;
+ }
+ if (!tooltipInfo.getDescriptionSections().isEmpty()) {
+ // remove the empty line after the last paragraph
+ descriptionTextHeight -= fontHeight;
+ // add gap between the title and the description
+ descriptionTextHeight += gap;
+ }
+
+ if (tooltipInfo.getMainImage() != null) {
+ height += Math.max(descriptionTextHeight, new JLabel(
+ new ImageIcon(tooltipInfo.getMainImage()))
+ .getPreferredSize().height);
+ } else {
+ height += descriptionTextHeight;
+ }
+
+ if ((tooltipInfo.getFooterImage() != null)
+ || (tooltipInfo.getFooterSections().size() > 0)) {
+ height += gap;
+ // The footer separator
+ height += new JSeparator(JSeparator.HORIZONTAL)
+ .getPreferredSize().height;
+
+ height += gap;
+
+ int footerTextHeight = 0;
+ int availableWidth = descTextWidth;
+ if (tooltipInfo.getFooterImage() != null) {
+ availableWidth -= tooltipInfo.getFooterImage().getWidth(
+ null);
+ }
+ if (tooltipInfo.getMainImage() != null) {
+ availableWidth += tooltipInfo.getMainImage().getWidth(null);
+ }
+ for (String footerText : tooltipInfo.getFooterSections()) {
+ AttributedString footerAttributedDescription = new AttributedString(
+ footerText);
+ footerAttributedDescription.addAttribute(
+ TextAttribute.FONT, font);
+ LineBreakMeasurer footerLineBreakMeasurer = new LineBreakMeasurer(
+ footerAttributedDescription.getIterator(), frc);
+ while (true) {
+ TextLayout tl = footerLineBreakMeasurer
+ .nextLayout(availableWidth);
+ if (tl == null)
+ break;
+ footerTextHeight += fontHeight;
+ }
+ // add an empty line after the paragraph
+ footerTextHeight += fontHeight;
+ }
+ // remove the empty line after the last paragraph
+ footerTextHeight -= fontHeight;
+
+ if (tooltipInfo.getFooterImage() != null) {
+ height += Math.max(footerTextHeight, new JLabel(
+ new ImageIcon(tooltipInfo.getFooterImage()))
+ .getPreferredSize().height);
+ } else {
+ height += footerTextHeight;
+ }
+ }
+
+ height += ins.bottom;
+
+ // special case for rich tooltips that only have titles
+ if (tooltipInfo.getDescriptionSections().isEmpty()
+ && (tooltipInfo.getMainImage() == null)
+ && tooltipInfo.getFooterSections().isEmpty()
+ && (tooltipInfo.getFooterImage() == null)) {
+ width = maxTitleLineWidth + 1 + ins.left + ins.right;
+ }
+
+ return new Dimension(width, height);
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ removeExistingComponents();
+
+ Font font = FlamingoUtilities.getFont(parent, "Ribbon.font",
+ "Button.font", "Panel.font");
+ Insets ins = richTooltipPanel.getInsets();
+ int y = ins.top;
+ RichTooltip tooltipInfo = richTooltipPanel.getTooltipInfo();
+ FontRenderContext frc = new FontRenderContext(
+ new AffineTransform(), true, false);
+ int gap = getLayoutGap();
+
+ int fontHeight = parent.getFontMetrics(font).getHeight();
+ Font titleFont = font.deriveFont(Font.BOLD);
+
+ boolean ltr = richTooltipPanel.getComponentOrientation()
+ .isLeftToRight();
+
+ // The title label
+ int titleLabelWidth = parent.getWidth() - ins.left - ins.right;
+ AttributedString titleAtributedDescription = new AttributedString(
+ tooltipInfo.getTitle());
+ titleAtributedDescription.addAttribute(TextAttribute.FONT,
+ titleFont);
+ LineBreakMeasurer titleLineBreakMeasurer = new LineBreakMeasurer(
+ titleAtributedDescription.getIterator(), frc);
+ int titleCurrOffset = 0;
+ while (true) {
+ TextLayout tl = titleLineBreakMeasurer
+ .nextLayout(titleLabelWidth);
+ if (tl == null)
+ break;
+ int charCount = tl.getCharacterCount();
+ String line = tooltipInfo.getTitle().substring(titleCurrOffset,
+ titleCurrOffset + charCount);
+
+ JLabel titleLabel = new JLabel(line);
+ titleLabel.setFont(titleFont);
+ titleLabels.add(titleLabel);
+ richTooltipPanel.add(titleLabel);
+ int currLabelWidth = titleLabel.getPreferredSize().width;
+ if (ltr) {
+ titleLabel.setBounds(ins.left, y, currLabelWidth,
+ fontHeight);
+ } else {
+ titleLabel.setBounds(parent.getWidth() - ins.right
+ - currLabelWidth, y, currLabelWidth, fontHeight);
+ }
+ y += titleLabel.getHeight();
+
+ titleCurrOffset += charCount;
+ }
+ y += gap;
+
+ // The main image
+ int x = ltr ? ins.left : parent.getWidth() - ins.right;
+ if (tooltipInfo.getMainImage() != null) {
+ mainImageLabel = new JLabel(new ImageIcon(tooltipInfo
+ .getMainImage()));
+ richTooltipPanel.add(mainImageLabel);
+ int mainImageWidth = mainImageLabel.getPreferredSize().width;
+ if (ltr) {
+ mainImageLabel.setBounds(x, y, mainImageWidth,
+ mainImageLabel.getPreferredSize().height);
+ x += mainImageWidth;
+ } else {
+ mainImageLabel.setBounds(x - mainImageWidth, y,
+ mainImageWidth,
+ mainImageLabel.getPreferredSize().height);
+ x -= mainImageWidth;
+ }
+ }
+ if (ltr) {
+ x += 2 * gap;
+ } else {
+ x -= 2 * gap;
+ }
+
+ // The description text
+ int descLabelWidth = ltr ? parent.getWidth() - x - ins.right : x
+ - ins.left;
+ // enforce a minimal 200 pixel width
+ if (descLabelWidth < 200) {
+ descLabelWidth = 200;
+ }
+ for (String descText : tooltipInfo.getDescriptionSections()) {
+ AttributedString attributedDescription = new AttributedString(
+ descText);
+ attributedDescription.addAttribute(TextAttribute.FONT, font);
+ LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer(
+ attributedDescription.getIterator(), frc);
+ int currOffset = 0;
+ while (true) {
+ TextLayout tl = lineBreakMeasurer
+ .nextLayout(descLabelWidth);
+ if (tl == null)
+ break;
+ int charCount = tl.getCharacterCount();
+ String line = descText.substring(currOffset, currOffset
+ + charCount);
+
+ JLabel descLabel = new JLabel(line);
+ descriptionLabels.add(descLabel);
+ richTooltipPanel.add(descLabel);
+ int currDescWidth = descLabel.getPreferredSize().width;
+ if (ltr) {
+ descLabel.setBounds(x, y, currDescWidth, fontHeight);
+ } else {
+ descLabel.setBounds(x - currDescWidth, y,
+ currDescWidth, fontHeight);
+ }
+ y += descLabel.getHeight();
+
+ currOffset += charCount;
+ }
+ // add an empty line after the paragraph
+ y += fontHeight;
+ }
+ // remove the empty line after the last paragraph
+ y -= fontHeight;
+
+ if (mainImageLabel != null) {
+ y = Math.max(y, mainImageLabel.getY()
+ + mainImageLabel.getHeight());
+ }
+
+ if ((tooltipInfo.getFooterImage() != null)
+ || (tooltipInfo.getFooterSections().size() > 0)) {
+ y += gap;
+ // The footer separator
+ footerSeparator = new JSeparator(JSeparator.HORIZONTAL);
+ richTooltipPanel.add(footerSeparator);
+ footerSeparator.setBounds(ins.left, y, parent.getWidth()
+ - ins.left - ins.right, footerSeparator
+ .getPreferredSize().height);
+
+ y += footerSeparator.getHeight() + gap;
+
+ // The footer image
+ x = ltr ? ins.left : parent.getWidth() - ins.right;
+ if (tooltipInfo.getFooterImage() != null) {
+ footerImageLabel = new JLabel(new ImageIcon(tooltipInfo
+ .getFooterImage()));
+ richTooltipPanel.add(footerImageLabel);
+ int footerImageWidth = footerImageLabel.getPreferredSize().width;
+ if (ltr) {
+ footerImageLabel.setBounds(x, y, footerImageWidth,
+ footerImageLabel.getPreferredSize().height);
+ x += footerImageWidth + 2 * gap;
+ } else {
+ footerImageLabel.setBounds(x - footerImageWidth, y,
+ footerImageWidth, footerImageLabel
+ .getPreferredSize().height);
+ x -= (footerImageWidth + 2 * gap);
+ }
+ }
+
+ // The footer text
+ int footerLabelWidth = ltr ? parent.getWidth() - x - ins.right
+ : x - ins.left;
+ for (String footerText : tooltipInfo.getFooterSections()) {
+ AttributedString attributedDescription = new AttributedString(
+ footerText);
+ attributedDescription
+ .addAttribute(TextAttribute.FONT, font);
+ LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer(
+ attributedDescription.getIterator(), frc);
+ int currOffset = 0;
+ while (true) {
+ TextLayout tl = lineBreakMeasurer
+ .nextLayout(footerLabelWidth);
+ if (tl == null)
+ break;
+ int charCount = tl.getCharacterCount();
+ String line = footerText.substring(currOffset,
+ currOffset + charCount);
+
+ JLabel footerLabel = new JLabel(line);
+ footerLabels.add(footerLabel);
+ richTooltipPanel.add(footerLabel);
+ int currLabelWidth = footerLabel.getPreferredSize().width;
+ if (ltr) {
+ footerLabel.setBounds(x, y, currLabelWidth,
+ fontHeight);
+ } else {
+ footerLabel.setBounds(x - currLabelWidth, y,
+ currLabelWidth, fontHeight);
+ }
+ y += footerLabel.getHeight();
+
+ currOffset += charCount;
+ }
+ // add an empty line after the paragraph
+ y += fontHeight;
+ }
+ // remove the empty line after the last paragraph
+ y -= fontHeight;
+ }
+ }
+ }
+
+ protected int getDescriptionTextWidth() {
+ return 200;
+ }
+
+ protected int getLayoutGap() {
+ return 4;
+ }
+
+ protected void removeExistingComponents() {
+ for (JLabel label : this.titleLabels)
+ this.richTooltipPanel.remove(label);
+
+ if (this.mainImageLabel != null) {
+ this.richTooltipPanel.remove(this.mainImageLabel);
+ }
+
+ for (JLabel label : this.descriptionLabels)
+ this.richTooltipPanel.remove(label);
+
+ if (this.footerSeparator != null) {
+ this.richTooltipPanel.remove(this.footerSeparator);
+ }
+
+ if (this.footerImageLabel != null) {
+ this.richTooltipPanel.remove(this.footerImageLabel);
+ }
+
+ for (JLabel label : this.footerLabels)
+ this.richTooltipPanel.remove(label);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicScrollablePanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicScrollablePanelUI.java
new file mode 100644
index 0000000..a4ee6dd
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/BasicScrollablePanelUI.java
@@ -0,0 +1,544 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.flamingo.api.common.JScrollablePanel;
+import org.pushingpixels.flamingo.api.common.JScrollablePanel.ScrollType;
+import org.pushingpixels.flamingo.internal.utils.DoubleArrowResizableIcon;
+
+/**
+ * Basic UI for scrollable panel {@link JScrollablePanel}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicScrollablePanelUI extends ScrollablePanelUI {
+ /**
+ * The associated scrollable panel.
+ */
+ protected JScrollablePanel scrollablePanel;
+
+ private JPanel viewport;
+
+ private JCommandButton leadingScroller;
+
+ private JCommandButton trailingScroller;
+
+ private int viewOffset;
+
+ private MouseWheelListener mouseWheelListener;
+
+ private PropertyChangeListener propertyChangeListener;
+
+ private ComponentListener componentListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicScrollablePanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.scrollablePanel = (JScrollablePanel) c;
+ super.installUI(this.scrollablePanel);
+ installDefaults();
+ installComponents();
+ installListeners();
+ }
+
+ protected void installListeners() {
+ this.mouseWheelListener = new MouseWheelListener() {
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ if (scrollablePanel.getScrollType() != JScrollablePanel.ScrollType.VERTICALLY) {
+ return;
+ }
+
+ int scrollAmount = 8 * e.getScrollAmount()
+ * e.getWheelRotation();
+ viewOffset += scrollAmount;
+ syncScrolling();
+ }
+ };
+ this.scrollablePanel.addMouseWheelListener(this.mouseWheelListener);
+
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("scrollOnRollover".equals(evt.getPropertyName())) {
+ boolean isScrollOnRollover = (Boolean) evt.getNewValue();
+ leadingScroller.setFireActionOnRollover(isScrollOnRollover);
+ trailingScroller
+ .setFireActionOnRollover(isScrollOnRollover);
+ }
+ }
+ };
+ this.scrollablePanel
+ .addPropertyChangeListener(this.propertyChangeListener);
+
+ if (this.scrollablePanel.getView() != null) {
+ this.componentListener = new ComponentAdapter() {
+ @Override
+ public void componentResized(ComponentEvent e) {
+ scrollablePanel.doLayout();
+ }
+ };
+ this.scrollablePanel.getView().addComponentListener(
+ this.componentListener);
+
+ }
+ }
+
+ protected void installComponents() {
+ this.viewport = new JPanel(new LayoutManager() {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ return new Dimension(10, 10);
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return preferredLayoutSize(parent);
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ JComponent view = scrollablePanel.getView();
+ if (scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY) {
+ int viewWidth = view.getPreferredSize().width;
+ int availWidth = parent.getWidth();
+
+ int offsetX = -viewOffset;
+ view.setBounds(offsetX, 0, Math.max(viewWidth, availWidth),
+ parent.getHeight());
+ } else {
+ int viewHeight = view.getPreferredSize().height;
+ int availHeight = parent.getHeight();
+
+ int offsetY = -viewOffset;
+ view.setBounds(0, offsetY, parent.getWidth(), Math.max(
+ viewHeight, availHeight));
+ }
+ }
+ });
+ JComponent view = scrollablePanel.getView();
+ if (view != null) {
+ this.viewport.add(view);
+ }
+ this.scrollablePanel.add(this.viewport);
+
+ this.leadingScroller = this.createLeadingScroller();
+ this.configureLeftScrollerButtonAction();
+ this.scrollablePanel.add(this.leadingScroller);
+
+ this.trailingScroller = this.createTrailingScroller();
+ this.configureRightScrollerButtonAction();
+ this.scrollablePanel.add(this.trailingScroller);
+ }
+
+ protected void installDefaults() {
+ this.scrollablePanel.setLayout(new ScrollablePanelLayout());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ uninstallListeners();
+ uninstallComponents();
+ uninstallDefaults();
+ super.uninstallUI(this.scrollablePanel);
+ }
+
+ protected void uninstallDefaults() {
+ }
+
+ protected void uninstallComponents() {
+ this.scrollablePanel.remove(this.viewport);
+ this.scrollablePanel.remove(this.leadingScroller);
+ this.scrollablePanel.remove(this.trailingScroller);
+ }
+
+ protected void uninstallListeners() {
+ this.scrollablePanel
+ .removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+
+ this.scrollablePanel.removeMouseWheelListener(this.mouseWheelListener);
+ this.mouseWheelListener = null;
+
+ if (this.scrollablePanel.getView() != null) {
+ this.scrollablePanel.getView().removeComponentListener(
+ this.componentListener);
+ this.componentListener = null;
+ }
+ }
+
+ protected JCommandButton createLeadingScroller() {
+ JCommandButton b = new JCommandButton(
+ null,
+ new DoubleArrowResizableIcon(
+ new Dimension(9, 9),
+ this.scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY ? SwingConstants.WEST
+ : SwingConstants.NORTH));
+
+ b.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
+ b.setFocusable(false);
+ b.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ b.putClientProperty(BasicCommandButtonUI.EMULATE_SQUARE_BUTTON,
+ Boolean.TRUE);
+ b.putClientProperty(BasicCommandButtonUI.DONT_DISPOSE_POPUPS,
+ Boolean.TRUE);
+ return b;
+ }
+
+ protected JCommandButton createTrailingScroller() {
+ JCommandButton b = new JCommandButton(
+ null,
+ new DoubleArrowResizableIcon(
+ new Dimension(9, 9),
+ this.scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY ? SwingConstants.EAST
+ : SwingConstants.SOUTH));
+
+ b.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
+ b.setFocusable(false);
+ b.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ b.putClientProperty(BasicCommandButtonUI.EMULATE_SQUARE_BUTTON,
+ Boolean.TRUE);
+ b.putClientProperty(BasicCommandButtonUI.DONT_DISPOSE_POPUPS,
+ Boolean.TRUE);
+ return b;
+ }
+
+ private void syncScrolling() {
+ this.scrollablePanel.doLayout();
+ }
+
+ public void removeScrollers() {
+ if (this.leadingScroller.getParent() == this.scrollablePanel) {
+ this.scrollablePanel.remove(this.leadingScroller);
+ this.scrollablePanel.remove(this.trailingScroller);
+ syncScrolling();
+ this.scrollablePanel.revalidate();
+ this.scrollablePanel.repaint();
+ }
+ }
+
+ private void addScrollers() {
+ this.scrollablePanel.add(this.leadingScroller);
+ this.scrollablePanel.add(this.trailingScroller);
+ this.scrollablePanel.revalidate();
+ JComponent view = this.scrollablePanel.getView();
+ view.setPreferredSize(view.getMinimumSize());
+ view.setSize(view.getMinimumSize());
+ this.scrollablePanel.doLayout();
+
+ this.scrollablePanel.repaint();
+ }
+
+ protected void configureLeftScrollerButtonAction() {
+ this.leadingScroller.setAutoRepeatAction(true);
+ this.leadingScroller.setAutoRepeatActionIntervals(200, 50);
+ this.leadingScroller.setFireActionOnRollover(this.scrollablePanel
+ .isScrollOnRollover());
+ this.leadingScroller.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ viewOffset -= 12;
+ syncScrolling();
+ }
+ });
+ }
+
+ protected void configureRightScrollerButtonAction() {
+ this.trailingScroller.setAutoRepeatAction(true);
+ this.trailingScroller.setAutoRepeatActionIntervals(200, 50);
+ this.trailingScroller.setFireActionOnRollover(this.scrollablePanel
+ .isScrollOnRollover());
+ this.trailingScroller.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ viewOffset += 12;
+ syncScrolling();
+ }
+ });
+ }
+
+ @Override
+ public void scrollToIfNecessary(int startPosition, int span) {
+ if (this.scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY) {
+ if (this.scrollablePanel.getComponentOrientation().isLeftToRight()) {
+ revealRightEdge(startPosition, span);
+ revealLeftEdge(startPosition);
+ } else {
+ revealLeftEdge(startPosition);
+ revealRightEdge(startPosition, span);
+ }
+ } else {
+ revealBottomEdge(startPosition, span);
+ revealTopEdge(startPosition);
+ }
+ }
+
+ private void revealLeftEdge(int x) {
+ if (x < viewOffset) {
+ // left edge is not visible
+ viewOffset = x - 5;
+ syncScrolling();
+ }
+ }
+
+ private void revealRightEdge(int x, int width) {
+ if ((x + width) > (viewOffset + viewport.getWidth())) {
+ // right edge is not visible
+ viewOffset = x + width - viewport.getWidth() + 5;
+ syncScrolling();
+ }
+ }
+
+ private void revealTopEdge(int y) {
+ if (y < viewOffset) {
+ // top edge is not visible
+ viewOffset = y - 5;
+ syncScrolling();
+ }
+ }
+
+ private void revealBottomEdge(int y, int height) {
+ if ((y + height) > (viewOffset + viewport.getHeight())) {
+ // bottom edge is not visible
+ viewOffset = y + height - viewport.getHeight() + 5;
+ syncScrolling();
+ }
+ }
+
+ @Override
+ public boolean isShowingScrollButtons() {
+ return (this.leadingScroller.isVisible());
+ }
+
+ /**
+ * Layout for the scrollable panel.
+ *
+ * @author Kirill Grouchnikov
+ * @author Topologi
+ */
+ protected class ScrollablePanelLayout implements LayoutManager {
+ /**
+ * Creates new layout manager.
+ */
+ public ScrollablePanelLayout() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ if (scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY) {
+ return new Dimension(c.getWidth(), 21);
+ } else {
+ return new Dimension(21, c.getHeight());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ if (scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY) {
+ return new Dimension(10, 21);
+ } else {
+ return new Dimension(21, 10);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ int width = c.getWidth();
+ int height = c.getHeight();
+
+ Insets ins = c.getInsets();
+
+ JComponent view = scrollablePanel.getView();
+ Dimension viewPrefSize = view.getPreferredSize();
+
+ // System.out.println(width + "*" + height + " - "
+ // + viewPrefSize.width + "*" + viewPrefSize.height);
+
+ if (scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY) {
+ boolean shouldShowScrollerButtons = (viewPrefSize.width > width);
+
+ leadingScroller.setVisible(shouldShowScrollerButtons);
+ trailingScroller.setVisible(shouldShowScrollerButtons);
+
+ int scrollPanelWidth = shouldShowScrollerButtons ? width
+ - ins.left - ins.right
+ - leadingScroller.getPreferredSize().width
+ - trailingScroller.getPreferredSize().width - 4 : width
+ - ins.left - ins.right;
+ int x = ins.left;
+ if (shouldShowScrollerButtons) {
+ int spw = leadingScroller.getPreferredSize().width;
+ leadingScroller.setBounds(x, ins.top, spw, height - ins.top
+ - ins.bottom);
+ x += spw + 2;
+ }
+ viewport.setBounds(x, ins.top, scrollPanelWidth, height
+ - ins.top - ins.bottom);
+
+ int viewPreferredWidth = view.getPreferredSize().width;
+ if (viewOffset < 0) {
+ viewOffset = 0;
+ }
+ if ((viewPreferredWidth > 0)
+ && (viewOffset + scrollPanelWidth > viewPreferredWidth)) {
+ viewOffset = Math.max(0, viewPreferredWidth
+ - scrollPanelWidth);
+ }
+ viewport.doLayout();
+
+ x += scrollPanelWidth + 2;
+ if (shouldShowScrollerButtons) {
+ int spw = trailingScroller.getPreferredSize().width;
+ trailingScroller.setBounds(x, ins.top, spw, height
+ - ins.top - ins.bottom);
+ }
+ } else {
+ boolean shouldShowScrollerButtons = (viewPrefSize.height > height);
+
+ leadingScroller.setVisible(shouldShowScrollerButtons);
+ trailingScroller.setVisible(shouldShowScrollerButtons);
+
+ int scrollPanelHeight = shouldShowScrollerButtons ? height
+ - ins.top - ins.bottom
+ - leadingScroller.getPreferredSize().height
+ - trailingScroller.getPreferredSize().height - 4
+ : height - ins.top - ins.bottom;
+ int y = ins.top;
+ if (shouldShowScrollerButtons) {
+ int sph = leadingScroller.getPreferredSize().height;
+ leadingScroller.setBounds(ins.left, y, width - ins.left
+ - ins.right, sph);
+ y += sph + 2;
+ }
+ viewport.setBounds(ins.left, y, width - ins.left - ins.right,
+ scrollPanelHeight);
+
+ int viewPreferredHeight = view.getPreferredSize().height;
+ if (viewOffset < 0) {
+ viewOffset = 0;
+ }
+ if ((viewPreferredHeight > 0)
+ && (viewOffset + scrollPanelHeight > viewPreferredHeight)) {
+ viewOffset = Math.max(0, viewPreferredHeight
+ - scrollPanelHeight);
+ }
+ viewport.doLayout();
+
+ y += scrollPanelHeight + 2;
+ if (shouldShowScrollerButtons) {
+ int sph = trailingScroller.getPreferredSize().height;
+ trailingScroller.setBounds(ins.left, y, width - ins.left
+ - ins.right, sph);
+ }
+ }
+
+ if (scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY) {
+ trailingScroller
+ .setEnabled((viewOffset + viewport.getWidth()) < view
+ .getWidth());
+ } else {
+ trailingScroller
+ .setEnabled((viewOffset + viewport.getHeight()) < view
+ .getHeight());
+ }
+ leadingScroller.setEnabled(viewOffset > 0);
+ }
+ }
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerBig.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerBig.java
new file mode 100644
index 0000000..cf49347
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerBig.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+import javax.swing.JSeparator;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+public class CommandButtonLayoutManagerBig implements
+ CommandButtonLayoutManager {
+ protected AbstractCommandButton commandButton;
+
+ /**
+ * The first part of (possibly) two-lined split of {@link #commandButton}'s
+ * title.
+ */
+ protected String titlePart1;
+
+ /**
+ * The second part of (possibly) two-lined split of {@link #commandButton}'s
+ * title.
+ */
+ protected String titlePart2;
+
+ public CommandButtonLayoutManagerBig(AbstractCommandButton commandButton) {
+ this.commandButton = commandButton;
+ this.updateTitleStrings();
+ }
+
+ @Override
+ public int getPreferredIconSize() {
+ return 32;
+ }
+
+ @Override
+ public Dimension getPreferredSize(AbstractCommandButton commandButton) {
+ Insets borderInsets = (commandButton == null) ? new Insets(0, 0, 0, 0)
+ : commandButton.getInsets();
+ int bx = borderInsets.left + borderInsets.right;
+ FontMetrics fm = commandButton.getFontMetrics(commandButton.getFont());
+ JSeparator jsep = new JSeparator(JSeparator.HORIZONTAL);
+ int layoutHGap = FlamingoUtilities.getHLayoutGap(commandButton);
+ int layoutVGap = FlamingoUtilities.getVLayoutGap(commandButton);
+
+ boolean hasIcon = (commandButton.getIcon() != null);
+ boolean hasText = (this.titlePart1 != null);
+ boolean hasPopupIcon = FlamingoUtilities.hasPopupAction(commandButton);
+
+ int title1Width = (this.titlePart1 == null) ? 0 : fm
+ .stringWidth(this.titlePart1);
+ int title2Width = (this.titlePart2 == null) ? 0 : fm
+ .stringWidth(this.titlePart2);
+
+ int prefIconSize = hasIcon ? this.getPreferredIconSize() : 0;
+
+ int width = Math.max(prefIconSize, Math.max(title1Width, title2Width
+ + 4
+ * layoutHGap
+ + jsep.getPreferredSize().height
+ + (FlamingoUtilities.hasPopupAction(commandButton) ? 1 + fm
+ .getHeight() / 2 : 0)));
+
+ // start height with the top inset
+ int height = borderInsets.top;
+ // icon?
+ if (hasIcon) {
+ // padding above the icon
+ height += layoutVGap;
+ // icon height
+ height += prefIconSize;
+ // padding below the icon
+ height += layoutVGap;
+ }
+ // text?
+ if (hasText) {
+ // padding above the text
+ height += layoutVGap;
+ // text height - two lines
+ height += 2 * (fm.getAscent() + fm.getDescent());
+ // padding below the text
+ height += layoutVGap;
+ }
+ // popup icon (no text)?
+ if (!hasText && hasPopupIcon) {
+ // padding above the popup icon
+ height += layoutVGap;
+ // popup icon height - one line of text
+ height += fm.getHeight();
+ // padding below the popup icon
+ height += layoutVGap;
+ }
+
+ // separator?
+ if (commandButton instanceof JCommandButton) {
+ JCommandButton jcb = (JCommandButton) commandButton;
+ CommandButtonKind buttonKind = jcb.getCommandButtonKind();
+ if (hasIcon && buttonKind.hasAction() && buttonKind.hasPopup()) {
+ // space for a horizontal separator
+ height += new JSeparator(JSeparator.HORIZONTAL)
+ .getPreferredSize().height;
+ }
+ }
+
+ // bottom insets
+ height += borderInsets.bottom;
+
+ // and remove the padding above the first and below the last elements
+ height -= 2 * layoutVGap;
+
+ return new Dimension(bx + width, height);
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("text".equals(evt.getPropertyName())
+ || "font".equals(evt.getPropertyName())) {
+ this.updateTitleStrings();
+ }
+ }
+
+ /**
+ * Updates the title strings for {@link CommandButtonDisplayState#BIG} and
+ * other relevant states.
+ */
+ protected void updateTitleStrings() {
+ // Break the title in two parts (the second part may be empty),
+ // finding the "inflection" point. The inflection point is a space
+ // character that breaks the title in two parts, such that the maximal
+ // length of the first part and the second part + action label icon
+ // is minimal between all possible space characters
+ BufferedImage tempImage = new BufferedImage(30, 30,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = (Graphics2D) tempImage.getGraphics();
+ g.setFont(FlamingoUtilities.getFont(this.commandButton, "Ribbon.font",
+ "Button.font", "Panel.font"));
+ FontMetrics fm = g.getFontMetrics();
+
+ String title = (this.commandButton == null) ? null : this.commandButton
+ .getText();
+ if (title != null) {
+ StringTokenizer tokenizer = new StringTokenizer(title, " _-", true);
+ if (tokenizer.countTokens() <= 1) {
+ // single word
+ this.titlePart1 = title;
+ this.titlePart2 = null;
+ } else {
+ int currMaxLength = (int) fm.getStringBounds(
+ this.commandButton.getText(), g).getWidth();
+ int actionIconWidth = FlamingoUtilities
+ .hasPopupAction(this.commandButton) ? 0 : 2
+ * FlamingoUtilities.getHLayoutGap(commandButton)
+ + (fm.getAscent() + fm.getDescent()) / 2;
+
+ String currLeading = "";
+ while (tokenizer.hasMoreTokens()) {
+ currLeading += tokenizer.nextToken();
+ String part1 = currLeading;
+ String part2 = title.substring(currLeading.length());
+
+ int len1 = (int) fm.getStringBounds(part1, g).getWidth();
+ int len2 = (int) fm.getStringBounds(part2, g).getWidth()
+ + actionIconWidth;
+ int len = Math.max(len1, len2);
+
+ if (currMaxLength > len) {
+ currMaxLength = len;
+ this.titlePart1 = part1;
+ this.titlePart2 = part2;
+ }
+ }
+ }
+ } else {
+ this.titlePart1 = null;
+ this.titlePart2 = null;
+ }
+ }
+
+ @Override
+ public Point getKeyTipAnchorCenterPoint(AbstractCommandButton commandButton) {
+ // center of the bottom edge
+ return new Point(commandButton.getWidth() / 2, commandButton
+ .getHeight());
+ }
+
+ @Override
+ public CommandButtonLayoutInfo getLayoutInfo(
+ AbstractCommandButton commandButton, Graphics g) {
+ CommandButtonLayoutInfo result = new CommandButtonLayoutInfo();
+
+ result.actionClickArea = new Rectangle(0, 0, 0, 0);
+ result.popupClickArea = new Rectangle(0, 0, 0, 0);
+
+ Insets ins = commandButton.getInsets();
+
+ result.iconRect = new Rectangle();
+ result.popupActionRect = new Rectangle();
+
+ int width = commandButton.getWidth();
+ int height = commandButton.getHeight();
+
+ int x = ins.left;
+
+ boolean ltr = commandButton.getComponentOrientation().isLeftToRight();
+
+ FontMetrics fm = g.getFontMetrics();
+ int labelHeight = fm.getAscent() + fm.getDescent();
+
+ JCommandButton.CommandButtonKind buttonKind = (commandButton instanceof JCommandButton) ? ((JCommandButton) commandButton)
+ .getCommandButtonKind()
+ : JCommandButton.CommandButtonKind.ACTION_ONLY;
+
+ ResizableIcon buttonIcon = commandButton.getIcon();
+
+ boolean hasIcon = (commandButton.getIcon() != null);
+ boolean hasText = (this.titlePart1 != null);
+ boolean hasPopupIcon = FlamingoUtilities.hasPopupAction(commandButton);
+
+ int layoutHGap = FlamingoUtilities.getHLayoutGap(commandButton);
+ int layoutVGap = FlamingoUtilities.getVLayoutGap(commandButton);
+
+ int prefHeight = this.getPreferredSize(commandButton).height;
+ int shiftY = 0;
+ if (height > prefHeight) {
+ shiftY = (height - prefHeight) / 2;
+ }
+
+ int y = ins.top + shiftY - layoutVGap;
+
+ // icon
+ if (hasIcon) {
+ y += layoutVGap;
+
+ int iconHeight = buttonIcon.getIconHeight();
+ int iconWidth = buttonIcon.getIconWidth();
+
+ result.iconRect.x = (width - iconWidth) / 2;
+ result.iconRect.y = y;
+ result.iconRect.width = iconWidth;
+ result.iconRect.height = iconHeight;
+
+ y += (iconHeight + layoutVGap);
+ }
+
+ // separator?
+ if (commandButton instanceof JCommandButton) {
+ // horizontal separator is always after the icon
+ if (hasIcon && buttonKind.hasAction() && buttonKind.hasPopup()) {
+ result.separatorOrientation = CommandButtonLayoutManager.CommandButtonSeparatorOrientation.HORIZONTAL;
+
+ result.separatorArea = new Rectangle(0, 0, 0, 0);
+ result.separatorArea.x = 0;
+ result.separatorArea.y = y;
+ result.separatorArea.width = width;
+ result.separatorArea.height = new JSeparator(
+ JSeparator.HORIZONTAL).getPreferredSize().height;
+
+ y += result.separatorArea.height;
+ }
+ }
+
+ int lastTextLineWidth = 0;
+ // text
+ if (hasText) {
+ y += layoutVGap;
+ lastTextLineWidth = (this.titlePart1 != null) ? (int) fm
+ .getStringBounds(this.titlePart1, g).getWidth() : 0;
+
+ TextLayoutInfo line1LayoutInfo = new TextLayoutInfo();
+ line1LayoutInfo.text = this.titlePart1;
+ line1LayoutInfo.textRect = new Rectangle();
+
+ line1LayoutInfo.textRect.x = ins.left
+ + (width - lastTextLineWidth - ins.left - ins.right) / 2;
+ line1LayoutInfo.textRect.y = y;
+ line1LayoutInfo.textRect.width = lastTextLineWidth;
+ line1LayoutInfo.textRect.height = labelHeight;
+
+ if (this.titlePart1 != null) {
+ y += labelHeight;
+ }
+
+ lastTextLineWidth = (this.titlePart2 != null) ? (int) fm
+ .getStringBounds(this.titlePart2, g).getWidth() : 0;
+
+ int extraWidth = hasPopupIcon ? 4 * layoutHGap + labelHeight / 2
+ : 0;
+
+ if (ltr) {
+ x = ins.left
+ + (width - lastTextLineWidth - extraWidth - ins.left - ins.right)
+ / 2;
+ }
+ if (!ltr) {
+ x = width
+ - ins.right
+ - lastTextLineWidth
+ - +(width - lastTextLineWidth - extraWidth - ins.left - ins.right)
+ / 2;
+ }
+
+ TextLayoutInfo line2LayoutInfo = new TextLayoutInfo();
+ line2LayoutInfo.text = this.titlePart2;
+ line2LayoutInfo.textRect = new Rectangle();
+
+ line2LayoutInfo.textRect.x = x;
+ line2LayoutInfo.textRect.y = y;
+ line2LayoutInfo.textRect.width = lastTextLineWidth;
+ line2LayoutInfo.textRect.height = labelHeight;
+
+ result.textLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.textLayoutInfoList.add(line1LayoutInfo);
+ result.textLayoutInfoList.add(line2LayoutInfo);
+ }
+
+ if (hasPopupIcon) {
+ if (lastTextLineWidth > 0) {
+ if (ltr) {
+ x += 2 * layoutHGap;
+ x += lastTextLineWidth;
+ } else {
+ x -= 2 * layoutHGap;
+ x -= labelHeight / 2;
+ }
+ } else {
+ x = (width - 1 - labelHeight / 2) / 2;
+ }
+
+ result.popupActionRect.x = x;
+ result.popupActionRect.y = y - 1;
+ result.popupActionRect.width = 1 + labelHeight / 2;
+ result.popupActionRect.height = labelHeight + 2;
+ }
+
+ switch (buttonKind) {
+ case ACTION_ONLY:
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width;
+ result.actionClickArea.height = height;
+ result.isTextInActionArea = true;
+ break;
+ case POPUP_ONLY:
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+ result.isTextInActionArea = false;
+ break;
+ case ACTION_AND_POPUP_MAIN_ACTION:
+ case ACTION_AND_POPUP_MAIN_POPUP:
+ // 1. break after icon if button has icon
+ // 2. no break (all popup) if button has no icon
+ if (hasIcon) {
+ int yBorderBetweenActionAndPopupAreas = result.iconRect.y
+ + result.iconRect.height + layoutVGap;
+
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width;
+ result.actionClickArea.height = yBorderBetweenActionAndPopupAreas;
+
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = yBorderBetweenActionAndPopupAreas;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height
+ - yBorderBetweenActionAndPopupAreas;
+
+ result.isTextInActionArea = false;
+ } else {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+
+ result.isTextInActionArea = false;
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerCustom.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerCustom.java
new file mode 100644
index 0000000..d9ebe80
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerCustom.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+
+import javax.swing.JSeparator;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+public class CommandButtonLayoutManagerCustom extends
+ CommandButtonLayoutManagerBig {
+
+ public CommandButtonLayoutManagerCustom(AbstractCommandButton commandButton) {
+ super(commandButton);
+ }
+
+ @Override
+ public int getPreferredIconSize() {
+ return -1;
+ }
+
+ @Override
+ public Dimension getPreferredSize(AbstractCommandButton commandButton) {
+ Insets borderInsets = commandButton.getInsets();
+ int bx = borderInsets.left + borderInsets.right;
+ int by = borderInsets.top + borderInsets.bottom;
+ FontMetrics fm = commandButton.getFontMetrics(commandButton.getFont());
+ JSeparator jsep = new JSeparator(JSeparator.HORIZONTAL);
+ int layoutHGap = FlamingoUtilities.getHLayoutGap(commandButton);
+ int layoutVGap = FlamingoUtilities.getVLayoutGap(commandButton);
+
+ int title1Width = (this.titlePart1 == null) ? 0 : fm
+ .stringWidth(this.titlePart1);
+ int title2Width = (this.titlePart2 == null) ? 0 : fm
+ .stringWidth(this.titlePart2);
+
+ ResizableIcon icon = commandButton.getIcon();
+ int iconWidth = (icon == null) ? 0 : icon.getIconWidth();
+ int width = Math.max(iconWidth, Math.max(title1Width, title2Width
+ + 4
+ * layoutHGap
+ + jsep.getPreferredSize().width
+ + (FlamingoUtilities.hasPopupAction(commandButton) ? 1 + fm
+ .getHeight() / 2 : 0)));
+
+ boolean hasIcon = (commandButton.getIcon() != null);
+ boolean hasText = (this.titlePart1 != null);
+ boolean hasPopupIcon = FlamingoUtilities.hasPopupAction(commandButton);
+
+ // start height with the top inset
+ int height = borderInsets.top;
+ // icon?
+ if (hasIcon) {
+ // padding above the icon
+ height += layoutVGap;
+ // icon height
+ height += icon.getIconHeight();
+ // padding below the icon
+ height += layoutVGap;
+ }
+ // text?
+ if (hasText) {
+ // padding above the text
+ height += layoutVGap;
+ // text height - two lines
+ height += 2 * (fm.getAscent() + fm.getDescent());
+ // padding below the text
+ height += layoutVGap;
+ }
+ // popup icon (no text)?
+ if (!hasText && hasPopupIcon) {
+ // padding above the popup icon
+ height += layoutVGap;
+ // popup icon height - one line of text
+ height += fm.getHeight();
+ // padding below the popup icon
+ height += layoutVGap;
+ }
+
+ if (hasPopupIcon) {
+ // space for a horizontal separator
+ height += new JSeparator(JSeparator.HORIZONTAL).getPreferredSize().height;
+ }
+
+ // bottom insets
+ height += borderInsets.bottom;
+
+ // and remove the padding above the first and below the last elements
+ height -= 2 * layoutVGap;
+
+ return new Dimension(bx + width, height);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerMedium.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerMedium.java
new file mode 100644
index 0000000..f94120d
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerMedium.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.util.ArrayList;
+
+import javax.swing.JSeparator;
+import javax.swing.SwingConstants;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.internal.ui.common.popup.BasicCommandPopupMenuUI;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+public class CommandButtonLayoutManagerMedium implements
+ CommandButtonLayoutManager {
+ @Override
+ public int getPreferredIconSize() {
+ return 16;
+ }
+
+ protected float getIconTextGapFactor() {
+ return 1.0f;
+ }
+
+ private boolean hasIcon(AbstractCommandButton button) {
+ return button.getIcon() != null || Boolean.TRUE.equals(button.getClientProperty(BasicCommandPopupMenuUI.FORCE_ICON));
+ }
+
+ @Override
+ public Dimension getPreferredSize(AbstractCommandButton commandButton) {
+ Insets borderInsets = commandButton.getInsets();
+ int by = borderInsets.top + borderInsets.bottom;
+ FontMetrics fm = commandButton.getFontMetrics(commandButton.getFont());
+
+ String buttonText = commandButton.getText();
+ int layoutHGap = FlamingoUtilities.getHLayoutGap(commandButton);
+
+ boolean hasIcon = this.hasIcon(commandButton);
+ boolean hasText = (buttonText != null);
+ boolean hasPopupIcon = FlamingoUtilities.hasPopupAction(commandButton);
+
+ int prefIconSize = hasIcon ? this.getPreferredIconSize() : 0;
+
+ // start with the left insets
+ int width = borderInsets.left;
+ // icon?
+ if (hasIcon) {
+ // padding before the icon
+ width += layoutHGap;
+ // icon width
+ width += prefIconSize;
+ // padding after the icon
+ width += layoutHGap;
+ }
+ // text?
+ if (hasText) {
+ // padding before the text
+ if (hasIcon) {
+ width += (int) (layoutHGap * getIconTextGapFactor());
+ } else {
+ width += layoutHGap;
+ }
+ // text width
+ width += fm.stringWidth(buttonText);
+ // padding after the text
+ width += layoutHGap;
+ }
+ // popup icon?
+ if (hasPopupIcon) {
+ // padding before the popup icon
+ if (hasText && hasIcon) {
+ width += 2 * layoutHGap;
+ }
+ // text width
+ width += 1 + fm.getHeight() / 2;
+ // padding after the popup icon
+ width += 2 * layoutHGap;
+ }
+
+ // separator?
+ if (commandButton instanceof JCommandButton) {
+ JCommandButton jcb = (JCommandButton) commandButton;
+ CommandButtonKind buttonKind = jcb.getCommandButtonKind();
+ boolean hasSeparator = false;
+ if (buttonKind == CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION
+ && (hasIcon || hasText)) {
+ hasSeparator = true;
+ }
+ if (buttonKind == CommandButtonKind.ACTION_AND_POPUP_MAIN_POPUP
+ && hasIcon) {
+ hasSeparator = true;
+ }
+ if (hasSeparator) {
+ // space for a vertical separator
+ width += new JSeparator(JSeparator.VERTICAL).getPreferredSize().width;
+ }
+ }
+
+ // right insets
+ width += borderInsets.right;
+
+ // and remove the padding before the first and after the last elements
+ width -= 2 * layoutHGap;
+
+ return new Dimension(width, by
+ + Math.max(prefIconSize, fm.getAscent() + fm.getDescent()));
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ }
+
+ @Override
+ public Point getKeyTipAnchorCenterPoint(AbstractCommandButton commandButton) {
+ Insets ins = commandButton.getInsets();
+ int height = commandButton.getHeight();
+ boolean hasIcon = this.hasIcon(commandButton);
+ int iconSize = this.getPreferredIconSize();
+ if (hasIcon) {
+ // bottom-right corner of the icon area
+ return new Point(ins.left + iconSize, (height + iconSize) / 2);
+ } else {
+ // bottom-left corner of the button
+ return new Point(ins.left, 3 * height / 4);
+ }
+ }
+
+ @Override
+ public CommandButtonLayoutInfo getLayoutInfo(
+ AbstractCommandButton commandButton, Graphics g) {
+ CommandButtonLayoutInfo result = new CommandButtonLayoutInfo();
+
+ result.actionClickArea = new Rectangle(0, 0, 0, 0);
+ result.popupClickArea = new Rectangle(0, 0, 0, 0);
+
+ Insets ins = commandButton.getInsets();
+
+ result.iconRect = new Rectangle();
+ result.popupActionRect = new Rectangle();
+
+ int width = commandButton.getWidth();
+ int height = commandButton.getHeight();
+
+ String buttonText = commandButton.getText();
+ int iconSize = this.getPreferredIconSize();
+
+ boolean hasIcon = this.hasIcon(commandButton);
+ boolean hasText = (buttonText != null);
+ boolean hasPopupIcon = FlamingoUtilities.hasPopupAction(commandButton);
+
+ boolean ltr = commandButton.getComponentOrientation().isLeftToRight();
+
+ int prefWidth = this.getPreferredSize(commandButton).width;
+ int shiftX = 0;
+ if (commandButton.getHorizontalAlignment() == SwingConstants.CENTER) {
+ if (width > prefWidth) {
+ shiftX = (width - prefWidth) / 2;
+ }
+ }
+
+ FontMetrics fm = g.getFontMetrics();
+ int labelHeight = fm.getAscent() + fm.getDescent();
+
+ JCommandButton.CommandButtonKind buttonKind = (commandButton instanceof JCommandButton) ? ((JCommandButton) commandButton)
+ .getCommandButtonKind()
+ : JCommandButton.CommandButtonKind.ACTION_ONLY;
+ int layoutHGap = FlamingoUtilities.getHLayoutGap(commandButton);
+
+ if (ltr) {
+ int x = ins.left + shiftX - layoutHGap;
+
+ // icon
+ if (hasIcon) {
+ x += layoutHGap;
+
+ result.iconRect.x = x;
+ result.iconRect.y = (height - iconSize) / 2;
+ result.iconRect.width = iconSize;
+ result.iconRect.height = iconSize;
+
+ x += (iconSize + layoutHGap);
+ }
+
+ // text
+ if (hasText) {
+ if (hasIcon) {
+ x += (int) (layoutHGap * getIconTextGapFactor());
+ } else {
+ x += layoutHGap;
+ }
+
+ TextLayoutInfo lineLayoutInfo = new TextLayoutInfo();
+ lineLayoutInfo.text = commandButton.getText();
+ lineLayoutInfo.textRect = new Rectangle();
+ result.textLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.textLayoutInfoList.add(lineLayoutInfo);
+
+ lineLayoutInfo.textRect.x = x;
+ lineLayoutInfo.textRect.y = (height - labelHeight) / 2;
+ lineLayoutInfo.textRect.width = (int) fm.getStringBounds(
+ buttonText, g).getWidth();
+ lineLayoutInfo.textRect.height = labelHeight;
+ x += lineLayoutInfo.textRect.width;
+
+ x += layoutHGap;
+ }
+
+ int verticalSeparatorWidth = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ if (hasPopupIcon) {
+ if (hasText && hasIcon) {
+ x += 2 * layoutHGap;
+ }
+
+ result.popupActionRect.x = commandButton.getWidth() - 1 - labelHeight / 2 - 3*layoutHGap + verticalSeparatorWidth;
+ result.popupActionRect.y = (height - labelHeight) / 2 - 1;
+ result.popupActionRect.width = 1 + labelHeight / 2;
+ result.popupActionRect.height = labelHeight + 2;
+ x += result.popupActionRect.width;
+
+ x += 2 * layoutHGap;
+ }
+
+ int xBorderBetweenActionAndPopup;
+ // compute the action and popup click areas
+ switch (buttonKind) {
+ case ACTION_ONLY:
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width;
+ result.actionClickArea.height = height;
+ result.isTextInActionArea = true;
+ break;
+ case POPUP_ONLY:
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+ result.isTextInActionArea = false;
+ break;
+ case ACTION_AND_POPUP_MAIN_ACTION:
+ // 1. break before popup icon if button has text or icon
+ // 2. no break (all popup) if button has no text and no icon
+ if (hasText || hasIcon) {
+ xBorderBetweenActionAndPopup = result.popupActionRect.x - 2
+ * layoutHGap;
+
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = xBorderBetweenActionAndPopup;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = xBorderBetweenActionAndPopup;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width
+ - xBorderBetweenActionAndPopup;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = xBorderBetweenActionAndPopup;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = verticalSeparatorWidth;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = true;
+ } else {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+
+ result.isTextInActionArea = false;
+ }
+ break;
+ case ACTION_AND_POPUP_MAIN_POPUP:
+ // 1. break after icon if button has icon
+ // 2. no break (all popup) if button has no icon
+ if (hasIcon) {
+ // shift text rectangle to the right
+ // to accomodate the vertical separator
+ if (result.textLayoutInfoList != null) {
+ for (TextLayoutInfo textLayoutInfo : result.textLayoutInfoList) {
+ textLayoutInfo.textRect.x += verticalSeparatorWidth;
+ }
+ }
+
+ xBorderBetweenActionAndPopup = result.iconRect.x
+ + result.iconRect.width + layoutHGap;
+
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = xBorderBetweenActionAndPopup;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = xBorderBetweenActionAndPopup;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width
+ - xBorderBetweenActionAndPopup;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = xBorderBetweenActionAndPopup;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = verticalSeparatorWidth;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = false;
+ } else {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+
+ result.isTextInActionArea = true;
+ }
+ break;
+ }
+ } else {
+ int x = width - ins.right - shiftX + layoutHGap;
+
+ // icon
+ if (hasIcon) {
+ x -= layoutHGap;
+
+ result.iconRect.x = x - iconSize;
+ result.iconRect.y = (height - iconSize) / 2;
+ result.iconRect.width = iconSize;
+ result.iconRect.height = iconSize;
+
+ x -= (iconSize + layoutHGap);
+ }
+
+ // text
+ if (hasText) {
+ if (hasIcon) {
+ x -= (int) (layoutHGap * getIconTextGapFactor());
+ } else {
+ x -= layoutHGap;
+ }
+
+ TextLayoutInfo lineLayoutInfo = new TextLayoutInfo();
+ lineLayoutInfo.text = commandButton.getText();
+ lineLayoutInfo.textRect = new Rectangle();
+ result.textLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.textLayoutInfoList.add(lineLayoutInfo);
+
+ lineLayoutInfo.textRect.width = (int) fm.getStringBounds(
+ buttonText, g).getWidth();
+ lineLayoutInfo.textRect.x = x - lineLayoutInfo.textRect.width;
+ lineLayoutInfo.textRect.y = (height - labelHeight) / 2;
+ lineLayoutInfo.textRect.height = labelHeight;
+ x -= lineLayoutInfo.textRect.width;
+
+ x -= layoutHGap;
+ }
+
+ if (hasPopupIcon) {
+ if (hasText && hasIcon) {
+ x -= 2 * layoutHGap;
+ }
+
+ result.popupActionRect.width = 1 + labelHeight / 2;
+ result.popupActionRect.x = x - result.popupActionRect.width;
+ result.popupActionRect.x = layoutHGap*2;
+ result.popupActionRect.y = (height - labelHeight) / 2 - 1;
+ result.popupActionRect.height = labelHeight + 2;
+ x -= result.popupActionRect.width;
+
+ x -= 2 * layoutHGap;
+ }
+
+ int xBorderBetweenActionAndPopup;
+ int verticalSeparatorWidth = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ // compute the action and popup click areas
+ switch (buttonKind) {
+ case ACTION_ONLY:
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width;
+ result.actionClickArea.height = height;
+ result.isTextInActionArea = true;
+ break;
+ case POPUP_ONLY:
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+ result.isTextInActionArea = false;
+ break;
+ case ACTION_AND_POPUP_MAIN_ACTION:
+ // 1. break before popup icon if button has text or icon
+ // 2. no break (all popup) if button has no text and no icon
+ if (hasText || hasIcon) {
+ // shift popup action rectangle to the left to
+ // accomodate the vertical separator
+
+ xBorderBetweenActionAndPopup = result.popupActionRect.x
+ + result.popupActionRect.width + 2 * layoutHGap;
+
+ result.actionClickArea.x = xBorderBetweenActionAndPopup;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width
+ - xBorderBetweenActionAndPopup;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = xBorderBetweenActionAndPopup;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = xBorderBetweenActionAndPopup;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = verticalSeparatorWidth;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = true;
+ } else {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+
+ result.isTextInActionArea = false;
+ }
+ break;
+ case ACTION_AND_POPUP_MAIN_POPUP:
+ // 1. break after icon if button has icon
+ // 2. no break (all popup) if button has no icon
+ if (hasIcon) {
+ // shift text rectangle and popup action rectangle to the
+ // left to accomodate the vertical separator
+ if (result.textLayoutInfoList != null) {
+ for (TextLayoutInfo textLayoutInfo : result.textLayoutInfoList) {
+ textLayoutInfo.textRect.x -= verticalSeparatorWidth;
+ }
+ }
+
+ xBorderBetweenActionAndPopup = result.iconRect.x
+ - layoutHGap;
+
+ result.actionClickArea.x = xBorderBetweenActionAndPopup;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width
+ - xBorderBetweenActionAndPopup;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = xBorderBetweenActionAndPopup;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = xBorderBetweenActionAndPopup;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = verticalSeparatorWidth;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = false;
+ } else {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+
+ result.isTextInActionArea = true;
+ }
+ break;
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerSmall.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerSmall.java
new file mode 100644
index 0000000..74d39a0
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerSmall.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+
+import javax.swing.JSeparator;
+import javax.swing.SwingConstants;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+public class CommandButtonLayoutManagerSmall implements
+ CommandButtonLayoutManager {
+
+ @Override
+ public int getPreferredIconSize() {
+ return 16;
+ }
+
+ @Override
+ public Dimension getPreferredSize(AbstractCommandButton commandButton) {
+ Insets borderInsets = commandButton.getInsets();
+ // int bx = borderInsets.left + borderInsets.right;
+ int by = borderInsets.top + borderInsets.bottom;
+ FontMetrics fm = commandButton.getFontMetrics(commandButton.getFont());
+
+ int layoutHGap = FlamingoUtilities.getHLayoutGap(commandButton);
+
+ boolean hasIcon = (commandButton.getIcon() != null);
+ boolean hasPopupIcon = FlamingoUtilities.hasPopupAction(commandButton);
+
+ int prefIconSize = hasIcon ? this.getPreferredIconSize() : 0;
+
+ // start with the left insets
+ int width = borderInsets.left;
+ // icon?
+ if (hasIcon) {
+ // padding before the icon
+ width += layoutHGap;
+ // icon width
+ width += prefIconSize;
+ // padding after the icon
+ width += layoutHGap;
+ }
+ // popup icon?
+ if (hasPopupIcon) {
+ // padding before the popup icon
+ width += 2 * layoutHGap;
+ // text width
+ width += 1 + fm.getHeight() / 2;
+ // padding after the popup icon
+ width += 2 * layoutHGap;
+ }
+
+ if (commandButton instanceof JCommandButton) {
+ JCommandButton jcb = (JCommandButton) commandButton;
+ CommandButtonKind buttonKind = jcb.getCommandButtonKind();
+ if (hasIcon && buttonKind.hasAction() && buttonKind.hasPopup()) {
+ // space for a vertical separator
+ width += new JSeparator(JSeparator.VERTICAL).getPreferredSize().width;
+ }
+ }
+
+ // right insets
+ width += borderInsets.right;
+
+ // and remove the padding before the first and after the last elements
+ width -= 2 * layoutHGap;
+
+ return new Dimension(width, by
+ + Math.max(prefIconSize, fm.getAscent() + fm.getDescent()));
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ }
+
+ @Override
+ public Point getKeyTipAnchorCenterPoint(AbstractCommandButton commandButton) {
+ Insets ins = commandButton.getInsets();
+ int height = commandButton.getHeight();
+ ResizableIcon buttonIcon = commandButton.getIcon();
+ if (buttonIcon != null) {
+ // bottom-right corner of the icon area
+ return new Point(ins.left + buttonIcon.getIconWidth(),
+ (height + buttonIcon.getIconHeight()) / 2);
+ } else {
+ // bottom-left corner of the button
+ return new Point(ins.left, 3 * height / 4);
+ }
+ }
+
+ @Override
+ public CommandButtonLayoutInfo getLayoutInfo(
+ AbstractCommandButton commandButton, Graphics g) {
+ CommandButtonLayoutInfo result = new CommandButtonLayoutInfo();
+
+ result.actionClickArea = new Rectangle(0, 0, 0, 0);
+ result.popupClickArea = new Rectangle(0, 0, 0, 0);
+
+ Insets ins = commandButton.getInsets();
+
+ result.iconRect = new Rectangle();
+ result.popupActionRect = new Rectangle();
+
+ int width = commandButton.getWidth();
+ int height = commandButton.getHeight();
+
+ int prefWidth = this.getPreferredSize(commandButton).width;
+ int shiftX = 0;
+ if (commandButton.getHorizontalAlignment() == SwingConstants.CENTER) {
+ if (width > prefWidth) {
+ shiftX = (width - prefWidth) / 2;
+ }
+ }
+
+ ResizableIcon buttonIcon = commandButton.getIcon();
+
+ boolean hasIcon = (buttonIcon != null);
+ boolean hasPopupIcon = FlamingoUtilities.hasPopupAction(commandButton);
+
+ boolean ltr = commandButton.getComponentOrientation().isLeftToRight();
+
+ FontMetrics fm = g.getFontMetrics();
+ int labelHeight = fm.getAscent() + fm.getDescent();
+
+ JCommandButton.CommandButtonKind buttonKind = (commandButton instanceof JCommandButton) ? ((JCommandButton) commandButton)
+ .getCommandButtonKind()
+ : JCommandButton.CommandButtonKind.ACTION_ONLY;
+ int layoutHGap = FlamingoUtilities.getHLayoutGap(commandButton);
+
+ if (ltr) {
+ int x = ins.left + shiftX - layoutHGap;
+
+ // icon
+ if (hasIcon) {
+ x += layoutHGap;
+
+ int iconHeight = buttonIcon.getIconHeight();
+ int iconWidth = buttonIcon.getIconWidth();
+
+ result.iconRect.x = x;
+ result.iconRect.y = (height - iconHeight) / 2;
+ result.iconRect.width = iconWidth;
+ result.iconRect.height = iconHeight;
+
+ x += (iconWidth + layoutHGap);
+ }
+
+ if (hasPopupIcon) {
+ x += 2 * layoutHGap;
+
+ result.popupActionRect.x = x;
+ result.popupActionRect.y = (height - labelHeight) / 2 - 1;
+ result.popupActionRect.width = 1 + labelHeight / 2;
+ result.popupActionRect.height = labelHeight + 2;
+ x += result.popupActionRect.width;
+
+ x += 2 * layoutHGap;
+ }
+
+ int xBorderBetweenActionAndPopup = 0;
+ int verticalSeparatorWidth = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ // compute the action and popup click areas
+ switch (buttonKind) {
+ case ACTION_ONLY:
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width;
+ result.actionClickArea.height = height;
+ result.isTextInActionArea = true;
+ break;
+ case POPUP_ONLY:
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+ result.isTextInActionArea = false;
+ break;
+ case ACTION_AND_POPUP_MAIN_ACTION:
+ case ACTION_AND_POPUP_MAIN_POPUP:
+ // 1. break after icon if button has icon
+ // 2. no break (all popup) if button has no icon
+ if (hasIcon) {
+ // shift popup action rectangle to the right
+ // to accomodate the vertical separator
+ result.popupActionRect.x += verticalSeparatorWidth;
+
+ xBorderBetweenActionAndPopup = result.iconRect.x
+ + result.iconRect.width + layoutHGap;
+
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = xBorderBetweenActionAndPopup;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = xBorderBetweenActionAndPopup;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width
+ - xBorderBetweenActionAndPopup;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = xBorderBetweenActionAndPopup;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = verticalSeparatorWidth;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = true;
+ } else {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+
+ result.isTextInActionArea = true;
+ }
+ break;
+ }
+ } else {
+ int x = width - ins.right - shiftX + layoutHGap;
+
+ // icon
+ if (hasIcon) {
+ x -= layoutHGap;
+
+ int iconHeight = buttonIcon.getIconHeight();
+ int iconWidth = buttonIcon.getIconWidth();
+
+ result.iconRect.x = x - iconWidth;
+ result.iconRect.y = (height - iconHeight) / 2;
+ result.iconRect.width = iconWidth;
+ result.iconRect.height = iconHeight;
+
+ x -= (iconWidth + layoutHGap);
+ }
+
+ if (hasPopupIcon) {
+ x -= 2 * layoutHGap;
+
+ result.popupActionRect.width = 1 + labelHeight / 2;
+ result.popupActionRect.x = x - result.popupActionRect.width;
+ result.popupActionRect.y = (height - labelHeight) / 2 - 1;
+ result.popupActionRect.height = labelHeight + 2;
+ x -= result.popupActionRect.width;
+
+ x -= 2 * layoutHGap;
+ }
+
+ int xBorderBetweenActionAndPopup = 0;
+ int verticalSeparatorWidth = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ // compute the action and popup click areas
+ switch (buttonKind) {
+ case ACTION_ONLY:
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width;
+ result.actionClickArea.height = height;
+ result.isTextInActionArea = true;
+ break;
+ case POPUP_ONLY:
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+ result.isTextInActionArea = false;
+ break;
+ case ACTION_AND_POPUP_MAIN_ACTION:
+ case ACTION_AND_POPUP_MAIN_POPUP:
+ // 1. break after icon if button has icon
+ // 2. no break (all popup) if button has no icon
+ if (hasIcon) {
+ // shift popup action rectangle to the left
+ // to accomodate the vertical separator
+ result.popupActionRect.x -= verticalSeparatorWidth;
+
+ xBorderBetweenActionAndPopup = result.iconRect.x
+ - layoutHGap;
+
+ result.actionClickArea.x = xBorderBetweenActionAndPopup;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width
+ - xBorderBetweenActionAndPopup;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = xBorderBetweenActionAndPopup;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = xBorderBetweenActionAndPopup;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = verticalSeparatorWidth;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = true;
+ } else {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+
+ result.isTextInActionArea = true;
+ }
+ break;
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerTile.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerTile.java
new file mode 100644
index 0000000..74a643a
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonLayoutManagerTile.java
@@ -0,0 +1,554 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.util.ArrayList;
+
+import javax.swing.JSeparator;
+import javax.swing.SwingConstants;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+public class CommandButtonLayoutManagerTile implements
+ CommandButtonLayoutManager {
+
+ @Override
+ public int getPreferredIconSize() {
+ return 32;
+ }
+
+ @Override
+ public Dimension getPreferredSize(AbstractCommandButton commandButton) {
+ Insets borderInsets = commandButton.getInsets();
+ int by = borderInsets.top + borderInsets.bottom;
+ FontMetrics fm = commandButton.getFontMetrics(commandButton.getFont());
+
+ String buttonText = commandButton.getText();
+ int titleWidth = (buttonText == null) ? 0 : fm
+ .stringWidth(commandButton.getText());
+ String extraText = commandButton.getExtraText();
+ int extraWidth = (extraText == null) ? 0 : fm.stringWidth(extraText);
+ double textWidth = Math.max(titleWidth, extraWidth);
+
+ int layoutHGap = FlamingoUtilities.getHLayoutGap(commandButton);
+
+ boolean hasIcon = (commandButton.getIcon() != null);
+ boolean hasText = (textWidth > 0);
+ boolean hasPopupIcon = FlamingoUtilities.hasPopupAction(commandButton);
+
+ int prefIconSize = hasIcon ? this.getPreferredIconSize() : 0;
+
+ // start with the left insets
+ int width = borderInsets.left;
+ // icon?
+ if (hasIcon) {
+ // padding before the icon
+ width += layoutHGap;
+ // icon width
+ width += prefIconSize;
+ // padding after the icon
+ width += layoutHGap;
+ }
+ // text?
+ if (hasText) {
+ // padding before the text
+ width += layoutHGap;
+ // text width
+ width += textWidth;
+ // padding after the text
+ width += layoutHGap;
+ }
+ // popup icon?
+ if (hasPopupIcon) {
+ // padding before the popup icon
+ width += 2 * layoutHGap;
+ // text width
+ width += 1 + fm.getHeight() / 2;
+ // padding after the popup icon
+ width += 2 * layoutHGap;
+ }
+
+ if (commandButton instanceof JCommandButton) {
+ JCommandButton jcb = (JCommandButton) commandButton;
+ CommandButtonKind buttonKind = jcb.getCommandButtonKind();
+ boolean hasSeparator = false;
+ if (buttonKind == CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION
+ && (hasIcon || hasText)) {
+ hasSeparator = true;
+ }
+ if (buttonKind == CommandButtonKind.ACTION_AND_POPUP_MAIN_POPUP
+ && hasIcon) {
+ hasSeparator = true;
+ }
+ if (hasSeparator) {
+ // space for a vertical separator
+ width += new JSeparator(JSeparator.VERTICAL).getPreferredSize().width;
+ }
+ }
+
+ // right insets
+ width += borderInsets.right;
+
+ // and remove the padding before the first and after the last elements
+ width -= 2 * layoutHGap;
+
+ return new Dimension(width, by
+ + Math
+ .max(prefIconSize, 2 * (fm.getAscent() + fm
+ .getDescent())));
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ }
+
+ @Override
+ public Point getKeyTipAnchorCenterPoint(AbstractCommandButton commandButton) {
+ Insets ins = commandButton.getInsets();
+ int height = commandButton.getHeight();
+ ResizableIcon buttonIcon = commandButton.getIcon();
+ if (buttonIcon != null) {
+ // bottom-right corner of the icon area
+ return new Point(ins.left + buttonIcon.getIconWidth(),
+ (height + buttonIcon.getIconHeight()) / 2);
+ } else {
+ // bottom-left corner of the button
+ return new Point(ins.left, 3 * height / 4);
+ }
+ }
+
+ @Override
+ public CommandButtonLayoutInfo getLayoutInfo(
+ AbstractCommandButton commandButton, Graphics g) {
+ CommandButtonLayoutInfo result = new CommandButtonLayoutInfo();
+
+ result.actionClickArea = new Rectangle(0, 0, 0, 0);
+ result.popupClickArea = new Rectangle(0, 0, 0, 0);
+
+ Insets ins = commandButton.getInsets();
+
+ result.iconRect = new Rectangle();
+ result.popupActionRect = new Rectangle();
+
+ int width = commandButton.getWidth();
+ int height = commandButton.getHeight();
+
+ int prefWidth = this.getPreferredSize(commandButton).width;
+ int shiftX = 0;
+ if (commandButton.getHorizontalAlignment() == SwingConstants.CENTER) {
+ if (width > prefWidth) {
+ shiftX = (width - prefWidth) / 2;
+ }
+ }
+
+ ResizableIcon buttonIcon = commandButton.getIcon();
+ String buttonText = commandButton.getText();
+ String buttonExtraText = commandButton.getExtraText();
+
+ boolean hasIcon = (buttonIcon != null);
+ boolean hasText = (buttonText != null) || (buttonExtraText != null);
+ boolean hasPopupIcon = FlamingoUtilities.hasPopupAction(commandButton);
+
+ boolean ltr = commandButton.getComponentOrientation().isLeftToRight();
+
+ FontMetrics fm = g.getFontMetrics();
+ int labelHeight = fm.getAscent() + fm.getDescent();
+
+ JCommandButton.CommandButtonKind buttonKind = (commandButton instanceof JCommandButton) ? ((JCommandButton) commandButton)
+ .getCommandButtonKind()
+ : JCommandButton.CommandButtonKind.ACTION_ONLY;
+ int layoutHGap = FlamingoUtilities.getHLayoutGap(commandButton);
+
+ if (ltr) {
+ int x = ins.left + shiftX - layoutHGap;
+
+ // icon
+ if (hasIcon) {
+ x += layoutHGap;
+
+ int iconHeight = buttonIcon.getIconHeight();
+ int iconWidth = buttonIcon.getIconWidth();
+
+ result.iconRect.x = x;
+ result.iconRect.y = (height - iconHeight) / 2;
+ result.iconRect.width = iconWidth;
+ result.iconRect.height = iconHeight;
+
+ x += (iconWidth + layoutHGap);
+ }
+
+ // text
+ if (hasText) {
+ x += layoutHGap;
+
+ TextLayoutInfo lineLayoutInfo = new TextLayoutInfo();
+ lineLayoutInfo.text = commandButton.getText();
+ lineLayoutInfo.textRect = new Rectangle();
+
+ lineLayoutInfo.textRect.x = x;
+ lineLayoutInfo.textRect.y = (height - 2 * labelHeight) / 2;
+ lineLayoutInfo.textRect.width = (buttonText == null) ? 0
+ : (int) fm.getStringBounds(buttonText, g).getWidth();
+ lineLayoutInfo.textRect.height = labelHeight;
+
+ result.textLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.textLayoutInfoList.add(lineLayoutInfo);
+
+ String extraText = commandButton.getExtraText();
+
+ TextLayoutInfo extraLineLayoutInfo = new TextLayoutInfo();
+ extraLineLayoutInfo.text = extraText;
+ extraLineLayoutInfo.textRect = new Rectangle();
+
+ extraLineLayoutInfo.textRect.x = x;
+ extraLineLayoutInfo.textRect.y = lineLayoutInfo.textRect.y
+ + labelHeight;
+ extraLineLayoutInfo.textRect.width = (extraText == null) ? 0
+ : (int) fm.getStringBounds(extraText, g).getWidth();
+ extraLineLayoutInfo.textRect.height = labelHeight;
+
+ result.extraTextLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.extraTextLayoutInfoList.add(extraLineLayoutInfo);
+
+ x += Math.max(lineLayoutInfo.textRect.width,
+ extraLineLayoutInfo.textRect.width);
+
+ x += layoutHGap;
+ }
+
+ if (hasPopupIcon) {
+ x += 2 * layoutHGap;
+
+ result.popupActionRect.x = x;
+ result.popupActionRect.y = (height - labelHeight) / 2 - 1;
+ result.popupActionRect.width = 1 + labelHeight / 2;
+ result.popupActionRect.height = labelHeight + 2;
+ x += result.popupActionRect.width;
+
+ x += 2 * layoutHGap;
+ }
+
+ int xBorderBetweenActionAndPopup = 0;
+ int verticalSeparatorWidth = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ // compute the action and popup click areas
+ switch (buttonKind) {
+ case ACTION_ONLY:
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width;
+ result.actionClickArea.height = height;
+ result.isTextInActionArea = true;
+ break;
+ case POPUP_ONLY:
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+ result.isTextInActionArea = false;
+ break;
+ case ACTION_AND_POPUP_MAIN_ACTION:
+ // 1. break before popup icon if button has text or icon
+ // 2. no break (all popup) if button has no text and no icon
+ if (hasText || hasIcon) {
+ // shift popup action rectangle to the right to
+ // accomodate the vertical separator
+ result.popupActionRect.x += verticalSeparatorWidth;
+
+ xBorderBetweenActionAndPopup = result.popupActionRect.x - 2
+ * layoutHGap;
+
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = xBorderBetweenActionAndPopup;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = xBorderBetweenActionAndPopup;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width
+ - xBorderBetweenActionAndPopup;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = xBorderBetweenActionAndPopup;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = verticalSeparatorWidth;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = true;
+ } else {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+
+ result.isTextInActionArea = true;
+ }
+ break;
+ case ACTION_AND_POPUP_MAIN_POPUP:
+ // 1. break after icon if button has icon
+ // 2. no break (all popup) if button has no icon
+ if (hasIcon) {
+ // shift text rectangles and popup action rectangle to the
+ // right
+ // to accomodate the vertical separator
+ if (result.textLayoutInfoList != null) {
+ for (TextLayoutInfo textLayoutInfo : result.textLayoutInfoList) {
+ textLayoutInfo.textRect.x += verticalSeparatorWidth;
+ }
+ }
+ if (result.extraTextLayoutInfoList != null) {
+ for (TextLayoutInfo extraTextLayoutInfo : result.extraTextLayoutInfoList) {
+ extraTextLayoutInfo.textRect.x += verticalSeparatorWidth;
+ }
+ }
+ result.popupActionRect.x += verticalSeparatorWidth;
+
+ xBorderBetweenActionAndPopup = result.iconRect.x
+ + result.iconRect.width + layoutHGap;
+
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = xBorderBetweenActionAndPopup;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = xBorderBetweenActionAndPopup;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width
+ - xBorderBetweenActionAndPopup;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = xBorderBetweenActionAndPopup;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = verticalSeparatorWidth;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = true;
+ } else {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+
+ result.isTextInActionArea = true;
+ }
+ break;
+ }
+ } else {
+ int x = width - ins.right - shiftX + layoutHGap;
+
+ // icon
+ if (hasIcon) {
+ x -= layoutHGap;
+
+ int iconHeight = buttonIcon.getIconHeight();
+ int iconWidth = buttonIcon.getIconWidth();
+
+ result.iconRect.x = x - iconWidth;
+ result.iconRect.y = (height - iconHeight) / 2;
+ result.iconRect.width = iconWidth;
+ result.iconRect.height = iconHeight;
+
+ x -= (iconWidth + layoutHGap);
+ }
+
+ // text
+ if (hasText) {
+ x -= layoutHGap;
+
+ TextLayoutInfo lineLayoutInfo = new TextLayoutInfo();
+ lineLayoutInfo.text = commandButton.getText();
+ lineLayoutInfo.textRect = new Rectangle();
+
+ lineLayoutInfo.textRect.width = (buttonText == null) ? 0
+ : (int) fm.getStringBounds(buttonText, g).getWidth();
+ lineLayoutInfo.textRect.x = x - lineLayoutInfo.textRect.width;
+ lineLayoutInfo.textRect.y = (height - 2 * labelHeight) / 2;
+ lineLayoutInfo.textRect.height = labelHeight;
+
+ result.textLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.textLayoutInfoList.add(lineLayoutInfo);
+
+ String extraText = commandButton.getExtraText();
+
+ TextLayoutInfo extraLineLayoutInfo = new TextLayoutInfo();
+ extraLineLayoutInfo.text = extraText;
+ extraLineLayoutInfo.textRect = new Rectangle();
+
+ extraLineLayoutInfo.textRect.width = (extraText == null) ? 0
+ : (int) fm.getStringBounds(extraText, g).getWidth();
+ extraLineLayoutInfo.textRect.x = x
+ - extraLineLayoutInfo.textRect.width;
+ extraLineLayoutInfo.textRect.y = lineLayoutInfo.textRect.y
+ + labelHeight;
+ extraLineLayoutInfo.textRect.height = labelHeight;
+
+ result.extraTextLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.extraTextLayoutInfoList.add(extraLineLayoutInfo);
+
+ x -= Math.max(lineLayoutInfo.textRect.width,
+ extraLineLayoutInfo.textRect.width);
+
+ x -= layoutHGap;
+ }
+
+ if (hasPopupIcon) {
+ x -= 2 * layoutHGap;
+
+ result.popupActionRect.width = 1 + labelHeight / 2;
+ result.popupActionRect.x = x - result.popupActionRect.width;
+ result.popupActionRect.y = (height - labelHeight) / 2 - 1;
+ result.popupActionRect.height = labelHeight + 2;
+ x -= result.popupActionRect.width;
+
+ x -= 2 * layoutHGap;
+ }
+
+ int xBorderBetweenActionAndPopup = 0;
+ int verticalSeparatorWidth = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ // compute the action and popup click areas
+ switch (buttonKind) {
+ case ACTION_ONLY:
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width;
+ result.actionClickArea.height = height;
+ result.isTextInActionArea = true;
+ break;
+ case POPUP_ONLY:
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+ result.isTextInActionArea = false;
+ break;
+ case ACTION_AND_POPUP_MAIN_ACTION:
+ // 1. break before popup icon if button has text or icon
+ // 2. no break (all popup) if button has no text and no icon
+ if (hasText || hasIcon) {
+ // shift popup action rectangle to the left to
+ // accomodate the vertical separator
+ result.popupActionRect.x -= verticalSeparatorWidth;
+
+ xBorderBetweenActionAndPopup = result.popupActionRect.x
+ + result.popupActionRect.width + 2 * layoutHGap;
+
+ result.actionClickArea.x = xBorderBetweenActionAndPopup;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width
+ - xBorderBetweenActionAndPopup;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = xBorderBetweenActionAndPopup;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = xBorderBetweenActionAndPopup;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = verticalSeparatorWidth;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = true;
+ } else {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+
+ result.isTextInActionArea = true;
+ }
+ break;
+ case ACTION_AND_POPUP_MAIN_POPUP:
+ // 1. break after icon if button has icon
+ // 2. no break (all popup) if button has no icon
+ if (hasIcon) {
+ // shift text rectangles and popup action rectangle to the
+ // left to accomodate the vertical separator
+ if (result.textLayoutInfoList != null) {
+ for (TextLayoutInfo textLayoutInfo : result.textLayoutInfoList) {
+ textLayoutInfo.textRect.x -= verticalSeparatorWidth;
+ }
+ }
+ if (result.extraTextLayoutInfoList != null) {
+ for (TextLayoutInfo extraTextLayoutInfo : result.extraTextLayoutInfoList) {
+ extraTextLayoutInfo.textRect.x -= verticalSeparatorWidth;
+ }
+ }
+ result.popupActionRect.x -= verticalSeparatorWidth;
+
+ xBorderBetweenActionAndPopup = result.iconRect.x
+ - layoutHGap;
+
+ result.actionClickArea.x = xBorderBetweenActionAndPopup;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width
+ - xBorderBetweenActionAndPopup;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = xBorderBetweenActionAndPopup;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = xBorderBetweenActionAndPopup;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = verticalSeparatorWidth;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = true;
+ } else {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+
+ result.isTextInActionArea = true;
+ }
+ break;
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonPanelUI.java
new file mode 100644
index 0000000..4e936e6
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonPanelUI.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import javax.swing.plaf.PanelUI;
+
+import org.pushingpixels.flamingo.api.common.JCommandButtonPanel;
+
+/**
+ * UI for icon panel ({@link JCommandButtonPanel}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class CommandButtonPanelUI extends PanelUI {
+}
\ No newline at end of file
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonStripUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonStripUI.java
new file mode 100644
index 0000000..d0bdfa7
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonStripUI.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.JCommandButtonStrip;
+
+/**
+ * UI for button strip ({@link JCommandButtonStrip}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class CommandButtonStripUI extends ComponentUI {
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonUI.java
new file mode 100644
index 0000000..27fc6a6
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/CommandButtonUI.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.Point;
+
+import javax.swing.plaf.ButtonUI;
+
+import org.pushingpixels.flamingo.api.common.CommandButtonLayoutManager;
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+
+/**
+ * UI for command button ({@link JCommandButton}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class CommandButtonUI extends ButtonUI {
+ /**
+ * Returns the layout information for the associated button.
+ *
+ * @return Layout information for the associated button.
+ */
+ public abstract CommandButtonLayoutManager.CommandButtonLayoutInfo getLayoutInfo();
+
+ public abstract Point getKeyTipAnchorCenterPoint();
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/JRichTooltipPanel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/JRichTooltipPanel.java
new file mode 100644
index 0000000..a6ffa64
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/JRichTooltipPanel.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+
+import org.pushingpixels.flamingo.api.common.RichTooltip;
+
+public class JRichTooltipPanel extends JPanel {
+ protected RichTooltip tooltipInfo;
+
+ /**
+ * @see #getUIClassID
+ */
+ public static final String uiClassID = "RichTooltipPanelUI";
+
+ public JRichTooltipPanel(RichTooltip tooltipInfo) {
+ this.tooltipInfo = tooltipInfo;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#getUI()
+ */
+ @Override
+ public RichTooltipPanelUI getUI() {
+ return (RichTooltipPanelUI) ui;
+ }
+
+ /**
+ * Sets the look and feel (L&F) object that renders this component.
+ *
+ * @param ui
+ * The UI delegate.
+ */
+ protected void setUI(RichTooltipPanelUI ui) {
+ super.setUI(ui);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((RichTooltipPanelUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicRichTooltipPanelUI.createUI(this));
+ }
+ }
+
+ public RichTooltip getTooltipInfo() {
+ return tooltipInfo;
+ }
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/ResizableIconUIResource.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/ResizableIconUIResource.java
new file mode 100644
index 0000000..652f336
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/ResizableIconUIResource.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import java.awt.*;
+
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+
+public class ResizableIconUIResource implements ResizableIcon, UIResource {
+ private ResizableIcon delegate;
+
+ public ResizableIconUIResource(ResizableIcon delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return delegate.getIconHeight();
+ }
+
+ @Override
+ public int getIconWidth() {
+ return delegate.getIconWidth();
+ }
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ delegate.paintIcon(c, g, x, y);
+ }
+
+ @Override
+ public void setDimension(Dimension newDimension) {
+ delegate.setDimension(newDimension);
+ }
+
+
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/RichTooltipPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/RichTooltipPanelUI.java
new file mode 100644
index 0000000..53bd53c
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/RichTooltipPanelUI.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import javax.swing.plaf.PanelUI;
+
+/**
+ * UI for rich tooltip panel ({@link JRichTooltipPanel}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class RichTooltipPanelUI extends PanelUI {
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/ScrollablePanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/ScrollablePanelUI.java
new file mode 100644
index 0000000..bedffc6
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/ScrollablePanelUI.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common;
+
+import javax.swing.plaf.PanelUI;
+
+import org.pushingpixels.flamingo.api.common.JScrollablePanel;
+
+/**
+ * UI for scrollable panel ({@link JScrollablePanel}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class ScrollablePanelUI extends PanelUI {
+ public abstract void scrollToIfNecessary(int startPosition, int span);
+
+ public abstract boolean isShowingScrollButtons();
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/BasicColorSelectorComponentUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/BasicColorSelectorComponentUI.java
new file mode 100644
index 0000000..43242a5
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/BasicColorSelectorComponentUI.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common.popup;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.swing.SwingRepaintCallback;
+
+/**
+ * Basic UI for color selector component {@link JColorSelectorComponent}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicColorSelectorComponentUI extends ColorSelectorComponentUI {
+ protected JColorSelectorComponent colorSelectorComponent;
+
+ protected ButtonModel buttonModel;
+
+ protected MouseListener mouseListener;
+
+ protected ChangeListener modelChangeListener;
+
+ protected ActionListener actionListener;
+
+ protected Timeline rolloverTimeline;
+
+ protected float rollover;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicColorSelectorComponentUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.colorSelectorComponent = (JColorSelectorComponent) c;
+ this.buttonModel = new DefaultButtonModel();
+
+ installDefaults();
+ installComponents();
+ installListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ uninstallListeners();
+ uninstallComponents();
+ uninstallDefaults();
+
+ c.setLayout(null);
+
+ this.colorSelectorComponent = null;
+ }
+
+ /**
+ * Installs listeners on the associated color selector component.
+ */
+ protected void installListeners() {
+ this.mouseListener = new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (!buttonModel.isRollover()) {
+ colorSelectorComponent
+ .onColorRollover(colorSelectorComponent.getColor());
+ rolloverTimeline.play();
+ }
+ buttonModel.setRollover(true);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (buttonModel.isRollover()) {
+ colorSelectorComponent.onColorRollover(null);
+ rolloverTimeline.playReverse();
+ }
+ buttonModel.setRollover(false);
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ buttonModel.setArmed(true);
+ buttonModel.setPressed(true);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ buttonModel.setPressed(false);
+ buttonModel.setArmed(false);
+ }
+ };
+ this.colorSelectorComponent.addMouseListener(this.mouseListener);
+
+ this.modelChangeListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ colorSelectorComponent.repaint();
+ }
+ };
+ this.buttonModel.addChangeListener(this.modelChangeListener);
+
+ this.actionListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ colorSelectorComponent.onColorSelected(colorSelectorComponent
+ .getColor());
+
+ PopupPanelManager.defaultManager().hidePopups(null);
+ }
+ };
+ this.buttonModel.addActionListener(this.actionListener);
+ }
+
+ /**
+ * Uninstalls listeners from the associated color selector component.
+ */
+ protected void uninstallListeners() {
+ this.buttonModel.removeActionListener(this.actionListener);
+ this.actionListener = null;
+
+ this.buttonModel.removeChangeListener(this.modelChangeListener);
+ this.modelChangeListener = null;
+
+ this.colorSelectorComponent.removeMouseListener(this.mouseListener);
+ this.mouseListener = null;
+ }
+
+ /**
+ * Installs defaults on the associated color selector component.
+ */
+ protected void installDefaults() {
+ this.rolloverTimeline = new Timeline(this);
+ this.rolloverTimeline.addPropertyToInterpolate("rollover", 0.0f, 1.0f);
+ this.rolloverTimeline.addCallback(new SwingRepaintCallback(
+ this.colorSelectorComponent));
+ this.rolloverTimeline.setDuration(150);
+ }
+
+ /**
+ * Uninstalls defaults from the associated color selector component.
+ */
+ protected void uninstallDefaults() {
+ }
+
+ /**
+ * Installs subcomponents on the associated color selector component.
+ */
+ protected void installComponents() {
+ }
+
+ /**
+ * Uninstalls subcomponents from the associated color selector component.
+ */
+ protected void uninstallComponents() {
+ }
+
+ public void setRollover(float rollover) {
+ this.rollover = rollover;
+ }
+
+ @Override
+ public void update(Graphics g, JComponent c) {
+ int w = this.colorSelectorComponent.getWidth();
+ int h = this.colorSelectorComponent.getHeight();
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ Color fillColor = this.colorSelectorComponent.getColor();
+ g2d.setColor(fillColor);
+ g2d.fillRect(0, 0, w, h);
+
+ float[] hsb = new float[3];
+ Color.RGBtoHSB(fillColor.getRed(), fillColor.getGreen(), fillColor
+ .getBlue(), hsb);
+ float brightness = hsb[2] * 0.7f;
+ g2d.setColor(new Color(brightness, brightness, brightness));
+ int ty = this.colorSelectorComponent.isTopOpen() ? 1 : 0;
+ int by = this.colorSelectorComponent.isBottomOpen() ? 1 : 0;
+ g2d.drawRect(0, -ty, w - 1, h - 1 + ty + by);
+
+ if (this.rollover > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver.derive(this.rollover));
+ g2d.setColor(new Color(207, 186, 115));
+ g2d.drawRect(0, 0, w - 1, h - 1);
+ g2d.setColor(new Color(230, 212, 150));
+ g2d.drawRect(1, 1, w - 3, h - 3);
+ }
+
+ g2d.dispose();
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/BasicColorSelectorPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/BasicColorSelectorPanelUI.java
new file mode 100644
index 0000000..f5fbf43
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/BasicColorSelectorPanelUI.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common.popup;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+/**
+ * Basic UI for color selector panel {@link JColorSelectorPanel}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicColorSelectorPanelUI extends ColorSelectorPanelUI {
+ protected JColorSelectorPanel colorSelectorPanel;
+
+ protected JLabel captionLabel;
+
+ protected JPanel colorSelectorContainer;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicColorSelectorPanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.colorSelectorPanel = (JColorSelectorPanel) c;
+
+ installDefaults();
+ installComponents();
+ installListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ uninstallListeners();
+ uninstallComponents();
+ uninstallDefaults();
+
+ c.setLayout(null);
+
+ this.colorSelectorPanel = null;
+ }
+
+ /**
+ * Installs listeners on the associated color selector panel.
+ */
+ protected void installListeners() {
+ }
+
+ /**
+ * Uninstalls listeners from the associated color selector panel.
+ */
+ protected void uninstallListeners() {
+ }
+
+ /**
+ * Installs defaults on the associated color selector panel.
+ */
+ protected void installDefaults() {
+ }
+
+ /**
+ * Uninstalls defaults from the associated color selector panel.
+ */
+ protected void uninstallDefaults() {
+ }
+
+ /**
+ * Installs subcomponents on the associated color selector panel.
+ */
+ protected void installComponents() {
+ this.captionLabel = new JLabel(this.colorSelectorPanel.getCaption());
+ this.captionLabel.setFont(this.captionLabel.getFont().deriveFont(
+ Font.BOLD));
+ this.colorSelectorContainer = this.colorSelectorPanel
+ .getColorSelectionContainer();
+
+ this.colorSelectorPanel.add(this.captionLabel);
+ if (this.colorSelectorContainer != null) {
+ this.colorSelectorPanel.add(this.colorSelectorContainer);
+ }
+
+ this.colorSelectorPanel.setLayout(new PanelLayout());
+ }
+
+ /**
+ * Uninstalls subcomponents from the associated color selector panel.
+ */
+ protected void uninstallComponents() {
+ this.colorSelectorPanel.remove(this.captionLabel);
+ if (this.colorSelectorContainer != null) {
+ this.colorSelectorPanel.remove(this.colorSelectorContainer);
+ }
+ }
+
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Color bg = this.colorSelectorPanel.getBackground();
+ g.setColor(bg);
+ int w = c.getWidth();
+ int h = c.getHeight();
+ g.fillRect(0, 0, w, h);
+
+ Rectangle captionBackground = this.captionLabel.getBounds();
+ this.paintCaptionBackground(g, 0, 0, w, captionBackground.height + 2
+ * getLayoutGap());
+
+ if (this.colorSelectorPanel.isLastPanel()) {
+ paintBottomDivider(g, 0, 0, w, h);
+ }
+ }
+
+ protected void paintBottomDivider(Graphics g, int x, int y, int width,
+ int height) {
+ g.setColor(FlamingoUtilities.getBorderColor());
+ g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+ }
+
+ protected void paintCaptionBackground(Graphics g, int x, int y, int width,
+ int height) {
+ FlamingoUtilities.renderSurface(g, this.colorSelectorPanel,
+ new Rectangle(x, y, width, height), false, true, true);
+ }
+
+ /**
+ * Returns the layout gap for button panel components.
+ *
+ * @return The layout gap for button panel components.
+ */
+ protected int getLayoutGap() {
+ return 4;
+ }
+
+ protected class PanelLayout implements LayoutManager {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return new Dimension(20, 20);
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ int layoutGap = getLayoutGap();
+
+ Dimension labelPrefSize = captionLabel.getPreferredSize();
+ Dimension contPrefSize = colorSelectorContainer.getPreferredSize();
+
+ return new Dimension(Math.max(labelPrefSize.width,
+ contPrefSize.width), 2 * layoutGap + labelPrefSize.height
+ + contPrefSize.height
+ + (colorSelectorPanel.isLastPanel() ? 1 : 0));
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ int layoutGap = getLayoutGap();
+
+ Dimension labelPrefSize = captionLabel.getPreferredSize();
+ int labelWidth = labelPrefSize.width;
+ int labelHeight = labelPrefSize.height;
+ int y = layoutGap;
+ if (captionLabel.getComponentOrientation().isLeftToRight()) {
+ captionLabel.setBounds(layoutGap, y, labelWidth, labelHeight);
+ } else {
+ captionLabel.setBounds(parent.getWidth() - layoutGap
+ - labelWidth, y, labelWidth, labelHeight);
+ }
+ y += labelHeight + layoutGap;
+
+ colorSelectorContainer.setBounds(0, y, parent.getWidth(), parent
+ .getHeight()
+ - y - (colorSelectorPanel.isLastPanel() ? 1 : 0));
+
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/BasicCommandPopupMenuUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/BasicCommandPopupMenuUI.java
new file mode 100644
index 0000000..a2d242c
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/BasicCommandPopupMenuUI.java
@@ -0,0 +1,685 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common.popup;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.popup.*;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager.PopupEvent;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonPanelUI;
+import org.pushingpixels.flamingo.internal.ui.common.CommandButtonLayoutManagerMedium;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+public class BasicCommandPopupMenuUI extends BasicPopupPanelUI {
+ /**
+ * The associated popup menu
+ */
+ protected JCommandPopupMenu popupMenu;
+
+ protected ChangeListener popupMenuChangeListener;
+
+ protected PopupPanelManager.PopupListener popupListener;
+
+ protected ScrollableCommandButtonPanel commandButtonPanel;
+
+ protected JScrollablePanel<JPanel> menuItemsPanel;
+
+ public static final String FORCE_ICON = "flamingo.internal.commandButtonLayoutManagerMedium.forceIcon";
+
+ protected static final CommandButtonDisplayState POPUP_MENU = new CommandButtonDisplayState(
+ "Popup menu", 16) {
+ @Override
+ public CommandButtonLayoutManager createLayoutManager(
+ AbstractCommandButton commandButton) {
+ return new CommandButtonLayoutManagerMedium() {
+ @Override
+ protected float getIconTextGapFactor() {
+ return 2.0f;
+ }
+ };
+ }
+ };
+
+ /**
+ * Popup panel that hosts groups of icons.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected static class ScrollableCommandButtonPanel extends JComponent {
+ /**
+ * Maximum dimension of <code>this</code> popup gallery.
+ */
+ protected Dimension maxDimension;
+
+ /**
+ * The internal panel that hosts the icon command buttons. Is hosted in
+ * the {@link #scroll}.
+ */
+ protected JCommandButtonPanel buttonPanel;
+
+ /**
+ * The maximum number of visible button rows.
+ */
+ protected int maxVisibleButtonRows;
+
+ /**
+ * Scroll panel that hosts {@link #buttonPanel}.
+ */
+ protected JScrollPane scroll;
+
+ /**
+ * Creates new a icon popup panel.
+ *
+ * @param iconPanel
+ * The internal panel that hosts icon command buttons.
+ * @param maxButtonColumns
+ * The maximum number of button columns.
+ * @param maxVisibleButtonRows
+ * The maximum number of visible button rows.
+ */
+ public ScrollableCommandButtonPanel(JCommandButtonPanel iconPanel,
+ int maxButtonColumns, int maxVisibleButtonRows) {
+ this.buttonPanel = iconPanel;
+ this.buttonPanel.setMaxButtonColumns(maxButtonColumns);
+ this.maxVisibleButtonRows = maxVisibleButtonRows;
+
+ int maxButtonWidth = 0;
+ int maxButtonHeight = 0;
+ int groupCount = iconPanel.getGroupCount();
+ for (int i = 0; i < groupCount; i++) {
+ for (AbstractCommandButton button : iconPanel
+ .getGroupButtons(i)) {
+ maxButtonWidth = Math.max(maxButtonWidth, button
+ .getPreferredSize().width);
+ maxButtonHeight = Math.max(maxButtonHeight, button
+ .getPreferredSize().height);
+ }
+ }
+
+ updateMaxDimension();
+
+ this.scroll = new JScrollPane(this.buttonPanel,
+ JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
+ JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ this.scroll.setBorder(new EmptyBorder(0, 0, 0, 0));
+ this.buttonPanel.setBorder(new EmptyBorder(0, 0, 0, 0));
+ this.scroll.setOpaque(false);
+ this.scroll.getViewport().setOpaque(false);
+ this.setLayout(new IconPopupLayout());
+
+ this.add(this.scroll);
+
+ this.setBorder(new Border() {
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return new Insets(0, 0, 1, 0);
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return true;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ g.setColor(FlamingoUtilities.getBorderColor());
+ g.drawLine(x, y + height - 1, x + width, y + height - 1);
+ }
+ });
+ }
+
+ /**
+ * Updates the max dimension of this panel. This method is for internal
+ * use only.
+ */
+ public void updateMaxDimension() {
+ if (this.buttonPanel == null)
+ return;
+ this.buttonPanel.setPreferredSize(null);
+ Dimension prefIconPanelDim = this.buttonPanel.getPreferredSize();
+ // fix for issue 13 - respect the gaps and insets
+ BasicCommandButtonPanelUI panelUI = (BasicCommandButtonPanelUI) buttonPanel
+ .getUI();
+ int titlePanelCount = buttonPanel.isToShowGroupLabels() ? 1 : 0;
+ this.maxDimension = new Dimension(prefIconPanelDim.width, panelUI
+ .getPreferredHeight(this.maxVisibleButtonRows,
+ titlePanelCount));
+ this.setPreferredSize(null);
+ }
+
+ /**
+ * Layout manager for <code>this</code> popup gallery.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class IconPopupLayout implements LayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container parent) {
+ Insets insets = parent.getInsets();
+ int left = insets.left;
+ int right = insets.right;
+ int top = insets.top;
+ int bottom = insets.bottom;
+ scroll.setBounds(left, top, parent.getWidth() - left - right,
+ parent.getHeight() - top - bottom);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return this.preferredLayoutSize(parent);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ Insets insets = parent.getInsets();
+ int left = insets.left;
+ int right = insets.right;
+ int top = insets.top;
+ int bottom = insets.bottom;
+ Dimension controlPanelDim = buttonPanel.getPreferredSize();
+ if (controlPanelDim == null)
+ controlPanelDim = new Dimension(0, 0);
+ int w = Math.min(controlPanelDim.width, maxDimension.width)
+ + left + right;
+ int h = Math.min(controlPanelDim.height, maxDimension.height)
+ + top + bottom;
+ if (h == (maxDimension.height + top + bottom)) {
+ int scrollBarWidth = UIManager.getInt("ScrollBar.width");
+ if (scrollBarWidth == 0) {
+ // Nimbus
+ scrollBarWidth = new JScrollBar(JScrollBar.VERTICAL)
+ .getPreferredSize().width;
+ }
+ w += scrollBarWidth;
+ // h += 5;
+ }
+ return new Dimension(w, h);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicCommandPopupMenuUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicPopupPanelUI#installUI(javax.swing.
+ * JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.popupMenu = (JCommandPopupMenu) c;
+ super.installUI(this.popupMenu);
+
+ this.popupMenu.setLayout(this.createLayoutManager());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.ui.BasicPopupPanelUI#installComponents()
+ */
+ @Override
+ protected void installComponents() {
+ super.installComponents();
+
+ syncComponents();
+ }
+
+ protected void syncComponents() {
+ if (this.popupMenu.hasCommandButtonPanel()) {
+ this.commandButtonPanel = createScrollableButtonPanel();
+ this.popupMenu.add(this.commandButtonPanel);
+ }
+
+ final JPanel menuPanel = this.createMenuPanel();
+ menuPanel.setLayout(new LayoutManager() {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ int height = 0;
+ int width = 0;
+ for (int i = 0; i < parent.getComponentCount(); i++) {
+ Dimension pref = parent.getComponent(i).getPreferredSize();
+ height += pref.height;
+ width = Math.max(width, pref.width);
+ }
+
+ Insets ins = parent.getInsets();
+ return new Dimension(width + ins.left + ins.right, height
+ + ins.top + ins.bottom);
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return preferredLayoutSize(parent);
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ Insets ins = parent.getInsets();
+
+ int topY = ins.top;
+ for (int i = 0; i < parent.getComponentCount(); i++) {
+ Component comp = parent.getComponent(i);
+ Dimension pref = comp.getPreferredSize();
+ comp.setBounds(ins.left, topY, parent.getWidth() - ins.left
+ - ins.right, pref.height);
+ topY += pref.height;
+ }
+ }
+ });
+
+ this.popupMenu.putClientProperty(BasicCommandPopupMenuUI.FORCE_ICON,
+ null);
+ java.util.List<Component> menuComponents = this.popupMenu
+ .getMenuComponents();
+ if (menuComponents != null) {
+ for (Component menuComponent : menuComponents) {
+ menuPanel.add(menuComponent);
+ }
+
+ boolean atLeastOneButtonHasIcon = false;
+ for (Component menuComponent : menuComponents) {
+ if (menuComponent instanceof JCommandMenuButton) {
+ JCommandMenuButton menuButton = (JCommandMenuButton) menuComponent;
+ if (menuButton.getIcon() != null) {
+ atLeastOneButtonHasIcon = true;
+ }
+ }
+ if (menuComponent instanceof JCommandToggleMenuButton) {
+ atLeastOneButtonHasIcon = true;
+ }
+ }
+
+ this.popupMenu.putClientProperty(
+ BasicCommandPopupMenuUI.FORCE_ICON,
+ atLeastOneButtonHasIcon ? Boolean.TRUE : null);
+ for (Component menuComponent : menuComponents) {
+ if (menuComponent instanceof JCommandMenuButton) {
+ JCommandMenuButton menuButton = (JCommandMenuButton) menuComponent;
+ menuButton.putClientProperty(
+ BasicCommandPopupMenuUI.FORCE_ICON,
+ atLeastOneButtonHasIcon ? Boolean.TRUE : null);
+ menuButton.setDisplayState(POPUP_MENU);
+ }
+ if (menuComponent instanceof JCommandToggleMenuButton) {
+ JCommandToggleMenuButton menuButton = (JCommandToggleMenuButton) menuComponent;
+ menuButton.putClientProperty(
+ BasicCommandPopupMenuUI.FORCE_ICON, Boolean.TRUE);
+ menuButton.setDisplayState(POPUP_MENU);
+ }
+ }
+ }
+
+ this.menuItemsPanel = new JScrollablePanel<JPanel>(menuPanel,
+ JScrollablePanel.ScrollType.VERTICALLY);
+ final LayoutManager scrollableLm = this.menuItemsPanel.getLayout();
+ this.menuItemsPanel.setLayout(new LayoutManager() {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ scrollableLm.addLayoutComponent(name, comp);
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ scrollableLm.removeLayoutComponent(comp);
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ Dimension result = menuPanel.getPreferredSize();
+ int maxMenuButtonCount = popupMenu.getMaxVisibleMenuButtons();
+ if ((maxMenuButtonCount < 0)
+ || (maxMenuButtonCount >= menuPanel.getComponentCount())) {
+ return result;
+ }
+ // the assumption is that all menu buttons have the
+ // same height.
+ int singleHeight = menuPanel.getComponent(0).getPreferredSize().height;
+ int width = 0;
+ for (int i = 0; i < menuPanel.getComponentCount(); i++) {
+ width = Math.max(width, menuPanel.getComponent(i)
+ .getPreferredSize().width);
+ }
+ Insets ins = parent.getInsets();
+ // add two for scroller buttons
+ return new Dimension(width + ins.left + ins.right, singleHeight
+ * (maxMenuButtonCount + 2) + ins.top + ins.bottom);
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return this.preferredLayoutSize(parent);
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ scrollableLm.layoutContainer(parent);
+ }
+ });
+ this.popupMenu.add(this.menuItemsPanel);
+ }
+
+ protected ScrollableCommandButtonPanel createScrollableButtonPanel() {
+ return new ScrollableCommandButtonPanel(this.popupMenu
+ .getMainButtonPanel(), this.popupMenu.getMaxButtonColumns(),
+ this.popupMenu.getMaxVisibleButtonRows());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.ui.BasicPopupPanelUI#uninstallComponents()
+ */
+ @Override
+ protected void uninstallComponents() {
+ this.popupMenu.removeAll();
+ super.uninstallComponents();
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.popupMenuChangeListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ popupMenu.removeAll();
+ syncComponents();
+ }
+ };
+ this.popupMenu.addChangeListener(this.popupMenuChangeListener);
+
+ this.popupListener = new PopupPanelManager.PopupListener() {
+ @Override
+ public void popupShown(PopupEvent event) {
+ }
+
+ @Override
+ public void popupHidden(PopupEvent event) {
+ if (event.getSource() instanceof JColorSelectorPopupMenu) {
+ ((JColorSelectorPopupMenu) event.getSource())
+ .getColorSelectorCallback().onColorRollover(null);
+ }
+ }
+ };
+ PopupPanelManager.defaultManager().addPopupListener(this.popupListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.ui.BasicPopupPanelUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.popupMenu.removeChangeListener(this.popupMenuChangeListener);
+ this.popupMenuChangeListener = null;
+
+ PopupPanelManager.defaultManager().addPopupListener(this.popupListener);
+ this.popupListener = null;
+
+ super.uninstallListeners();
+ }
+
+ protected JPanel createMenuPanel() {
+ return new MenuPanel();
+ }
+
+ protected LayoutManager createLayoutManager() {
+ return new PopupMenuLayoutManager();
+ }
+
+ protected class PopupMenuLayoutManager implements LayoutManager {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return null;
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ int height = 0;
+ int width = 0;
+
+ if (commandButtonPanel != null) {
+ width = commandButtonPanel.getPreferredSize().width;
+ height = commandButtonPanel.getPreferredSize().height;
+ }
+ Dimension menuItemsPref = (popupMenu.getMaxVisibleMenuButtons() > 0) ? menuItemsPanel
+ .getPreferredSize()
+ : menuItemsPanel.getView().getPreferredSize();
+ width = Math.max(menuItemsPref.width, width);
+ height += menuItemsPref.height;
+
+ Insets ins = parent.getInsets();
+ return new Dimension(width + ins.left + ins.right, height + ins.top
+ + ins.bottom);
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ Insets ins = parent.getInsets();
+
+ int bottomY = parent.getHeight() - ins.bottom;
+ Dimension menuItemsPref = (popupMenu.getMaxVisibleMenuButtons() > 0) ? menuItemsPanel
+ .getPreferredSize()
+ : menuItemsPanel.getView().getPreferredSize();
+
+ int menuHeight = Math.min(menuItemsPref.height, parent.getHeight());
+ menuItemsPanel.setBounds(ins.left, bottomY - menuHeight,
+ parent.getWidth() - ins.left - ins.right,
+ menuHeight);
+ menuItemsPanel.doLayout();
+ bottomY -= menuHeight;
+
+ if (commandButtonPanel != null) {
+ commandButtonPanel.setBounds(ins.left, ins.top, parent
+ .getWidth()
+ - ins.left - ins.right, bottomY - ins.top);
+ commandButtonPanel.invalidate();
+ commandButtonPanel.validate();
+ commandButtonPanel.doLayout();
+ }
+ }
+ }
+
+ protected static class MenuPanel extends JPanel {
+ @Override
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ JCommandPopupMenu menu = (JCommandPopupMenu) SwingUtilities
+ .getAncestorOfClass(JCommandPopupMenu.class, this);
+ if (Boolean.TRUE.equals(menu.getClientProperty(FORCE_ICON))) {
+ this.paintIconGutterBackground(g);
+ this.paintIconGutterSeparator(g);
+ }
+ }
+
+ protected int getSeparatorX() {
+ JCommandPopupMenu menu = (JCommandPopupMenu) SwingUtilities
+ .getAncestorOfClass(JCommandPopupMenu.class, this);
+ if (!Boolean.TRUE.equals(menu.getClientProperty(FORCE_ICON))) {
+ return -1;
+ }
+ java.util.List<Component> menuComponents = menu.getMenuComponents();
+ if (menuComponents != null) {
+ for (Component menuComponent : menuComponents) {
+ if (menuComponent instanceof JCommandMenuButton
+ || menuComponent instanceof JCommandToggleMenuButton) {
+ AbstractCommandButton button = (AbstractCommandButton) menuComponent;
+ if (!Boolean.TRUE.equals(button
+ .getClientProperty(FORCE_ICON))) {
+ continue;
+ }
+ boolean ltr = button.getComponentOrientation()
+ .isLeftToRight();
+ CommandButtonLayoutManager.CommandButtonLayoutInfo layoutInfo = button
+ .getUI().getLayoutInfo();
+ if (ltr) {
+ int iconRight = layoutInfo.iconRect.x
+ + layoutInfo.iconRect.width;
+ int textLeft = button.getWidth();
+ for (CommandButtonLayoutManager.TextLayoutInfo tli : layoutInfo.textLayoutInfoList) {
+ textLeft = Math.min(textLeft, tli.textRect.x);
+ }
+ return (iconRight + textLeft) / 2;
+ } else {
+ int iconLeft = layoutInfo.iconRect.x;
+ int textRight = 0;
+ for (CommandButtonLayoutManager.TextLayoutInfo tli : layoutInfo.textLayoutInfoList) {
+ textRight = Math.max(textRight, tli.textRect.x
+ + tli.textRect.width);
+ }
+ return (iconLeft + textRight) / 2;
+ }
+ }
+ }
+ }
+ throw new IllegalStateException(
+ "Menu marked to show icons but no menu buttons in it");
+ }
+
+ protected void paintIconGutterSeparator(Graphics g) {
+ CellRendererPane buttonRendererPane = new CellRendererPane();
+ JSeparator rendererSeparator = new JSeparator(JSeparator.VERTICAL);
+
+ buttonRendererPane.setBounds(0, 0, this.getWidth(), this
+ .getHeight());
+ int sepX = this.getSeparatorX();
+ if (this.getComponentOrientation().isLeftToRight()) {
+ buttonRendererPane.paintComponent(g, rendererSeparator, this,
+ sepX, 2, 2, this.getHeight() - 4, true);
+ } else {
+ buttonRendererPane.paintComponent(g, rendererSeparator, this,
+ sepX, 2, 2, this.getHeight() - 4, true);
+ }
+ }
+
+ protected void paintIconGutterBackground(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setComposite(AlphaComposite.SrcOver.derive(0.7f));
+
+ int sepX = this.getSeparatorX();
+ if (this.getComponentOrientation().isLeftToRight()) {
+ g2d.clipRect(0, 0, sepX + 2, this.getHeight());
+ AffineTransform at = AffineTransform.getTranslateInstance(0,
+ this.getHeight());
+ at.rotate(-Math.PI / 2);
+ g2d.transform(at);
+
+ FlamingoUtilities.renderSurface(g2d, this, new Rectangle(0, 0,
+ this.getHeight(), 50), false, false, false);
+ } else {
+ g2d.clipRect(this.getWidth() - sepX, 0, sepX + 2, this
+ .getHeight());
+ AffineTransform at = AffineTransform.getTranslateInstance(0,
+ this.getHeight());
+ at.rotate(-Math.PI / 2);
+ g2d.transform(at);
+
+ FlamingoUtilities.renderSurface(g2d, this, new Rectangle(0,
+ sepX, this.getHeight(), this.getWidth() - sepX), false,
+ false, false);
+ }
+
+ g2d.dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/BasicPopupPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/BasicPopupPanelUI.java
new file mode 100644
index 0000000..3d5c3c8
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/BasicPopupPanelUI.java
@@ -0,0 +1,683 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common.popup;
+
+import java.applet.Applet;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.ComboPopup;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager.PopupEvent;
+import org.pushingpixels.flamingo.api.ribbon.JRibbon;
+import org.pushingpixels.flamingo.internal.ui.ribbon.JRibbonTaskToggleButton;
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuPopupPanel;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+import org.pushingpixels.flamingo.internal.utils.KeyTipManager;
+
+/**
+ * Basic UI for popup panel {@link JPopupPanel}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicPopupPanelUI extends PopupPanelUI {
+ /**
+ * The associated popup panel.
+ */
+ protected JPopupPanel popupPanel;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicPopupPanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.popupPanel = (JPopupPanel) c;
+ super.installUI(this.popupPanel);
+ installDefaults();
+ installComponents();
+ installListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ uninstallListeners();
+ uninstallComponents();
+ uninstallDefaults();
+ super.uninstallUI(this.popupPanel);
+ }
+
+ /**
+ * Installs default settings for the associated command popup menu.
+ */
+ protected void installDefaults() {
+ Color bg = this.popupPanel.getBackground();
+ if (bg == null || bg instanceof UIResource) {
+ this.popupPanel.setBackground(FlamingoUtilities.getColor(
+ Color.lightGray, "PopupPanel.background",
+ "Panel.background"));
+ }
+
+ Border b = this.popupPanel.getBorder();
+ if (b == null || b instanceof UIResource) {
+ Border toSet = UIManager.getBorder("PopupPanel.border");
+ if (toSet == null)
+ toSet = new BorderUIResource.CompoundBorderUIResource(
+ new LineBorder(FlamingoUtilities.getBorderColor()),
+ new EmptyBorder(1, 1, 1, 1));
+ this.popupPanel.setBorder(toSet);
+ }
+ LookAndFeel.installProperty(this.popupPanel, "opaque", Boolean.TRUE);
+ }
+
+ /**
+ * Installs listeners on the associated command popup menu.
+ */
+ protected void installListeners() {
+ initiliazeGlobalListeners();
+ }
+
+ /**
+ * Installs components on the associated command popup menu.
+ */
+ protected void installComponents() {
+ }
+
+ /**
+ * Uninstalls default settings from the associated command popup menu.
+ */
+ protected void uninstallDefaults() {
+ LookAndFeel.uninstallBorder(this.popupPanel);
+ }
+
+ /**
+ * Uninstalls listeners from the associated command popup menu.
+ */
+ protected void uninstallListeners() {
+ }
+
+ /**
+ * Uninstalls subcomponents from the associated command popup menu.
+ */
+ protected void uninstallComponents() {
+ }
+
+ /**
+ * The global listener that tracks the ESC key action on the root panes of
+ * windows that show popup panels.
+ */
+ static PopupPanelManager.PopupListener popupPanelManagerListener;
+
+ /**
+ * Initializes the global listeners.
+ */
+ protected static synchronized void initiliazeGlobalListeners() {
+ if (popupPanelManagerListener != null) {
+ return;
+ }
+
+ popupPanelManagerListener = new PopupPanelEscapeDismisser();
+ PopupPanelManager.defaultManager().addPopupListener(
+ popupPanelManagerListener);
+
+ new WindowTracker();
+ }
+
+ /**
+ * This class is used to trace the changes in the shown popup panels and
+ * install ESC key listener on the matching root pane so that the popup
+ * panels can be dismissed with the ESC key.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected static class PopupPanelEscapeDismisser implements
+ PopupPanelManager.PopupListener {
+ /**
+ * The currently installed action map on the {@link #tracedRootPane}.
+ */
+ private ActionMap newActionMap;
+
+ /**
+ * The currently installed input map on the {@link #tracedRootPane}.
+ */
+ private InputMap newInputMap;
+
+ /**
+ * The last shown popup panel sequence.
+ */
+ List<PopupPanelManager.PopupInfo> lastPathSelected;
+
+ /**
+ * Currently traced root pane. It is the root pane of the originating
+ * component of the first popup panel in the currently shown sequence of
+ * {@link PopupPanelManager}.
+ */
+ private JRootPane tracedRootPane;
+
+ /**
+ * Creates a new tracer for popup panels to be dismissed with ESC key.
+ */
+ public PopupPanelEscapeDismisser() {
+ PopupPanelManager popupPanelManager = PopupPanelManager
+ .defaultManager();
+ this.lastPathSelected = popupPanelManager.getShownPath();
+ if (this.lastPathSelected.size() != 0) {
+ traceRootPane(this.lastPathSelected);
+ }
+ }
+
+ @Override
+ public void popupHidden(PopupEvent event) {
+ PopupPanelManager msm = PopupPanelManager.defaultManager();
+ List<PopupPanelManager.PopupInfo> p = msm.getShownPath();
+
+ if (lastPathSelected.size() != 0 && p.size() == 0) {
+ // if it is the last popup panel to be dismissed, untrace the
+ // root pane
+ untraceRootPane();
+ }
+
+ lastPathSelected = p;
+ }
+
+ /**
+ * Removes the installed maps on the currently traced root pane.
+ */
+ private void untraceRootPane() {
+ if (this.tracedRootPane != null) {
+ removeUIActionMap(this.tracedRootPane, this.newActionMap);
+ removeUIInputMap(this.tracedRootPane, this.newInputMap);
+ }
+ }
+
+ @Override
+ public void popupShown(PopupEvent event) {
+ PopupPanelManager msm = PopupPanelManager.defaultManager();
+ List<PopupPanelManager.PopupInfo> p = msm.getShownPath();
+
+ if (lastPathSelected.size() == 0 && p.size() != 0) {
+ // if it is the first popup panel to be shown, trace the root
+ // panel
+ traceRootPane(p);
+ }
+
+ lastPathSelected = p;
+ }
+
+ /**
+ * Installs the maps on the root pane of the originating component of
+ * the first popup panel of the specified sequence to trace the ESC key
+ * and dismiss the shown popup panels.
+ *
+ * @param shownPath
+ * Popup panel sequence.
+ */
+ private void traceRootPane(List<PopupPanelManager.PopupInfo> shownPath) {
+ JComponent originator = shownPath.get(0).getPopupOriginator();
+ this.tracedRootPane = SwingUtilities.getRootPane(originator);
+
+ if (this.tracedRootPane != null) {
+ newInputMap = new ComponentInputMapUIResource(tracedRootPane);
+ newInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
+ "hidePopupPanel");
+
+ newActionMap = new ActionMapUIResource();
+ newActionMap.put("hidePopupPanel", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // Hide the last sequence popup for every ESC keystroke.
+ // There is special case - if the keytips are shown
+ // for the *second* panel of the app menu popup panel,
+ // do not dismiss the popup
+ List<PopupPanelManager.PopupInfo> popups = PopupPanelManager
+ .defaultManager().getShownPath();
+ if (popups.size() > 0) {
+ PopupPanelManager.PopupInfo lastPopup = popups
+ .get(popups.size() - 1);
+ if (lastPopup.getPopupPanel() instanceof JRibbonApplicationMenuPopupPanel) {
+ JRibbonApplicationMenuPopupPanel appMenuPopupPanel = (JRibbonApplicationMenuPopupPanel) lastPopup
+ .getPopupPanel();
+ KeyTipManager.KeyTipChain currentlyShownKeyTipChain = KeyTipManager
+ .defaultManager()
+ .getCurrentlyShownKeyTipChain();
+ if ((currentlyShownKeyTipChain != null)
+ && (currentlyShownKeyTipChain.chainParentComponent == appMenuPopupPanel
+ .getPanelLevel2()))
+ return;
+ }
+ }
+ PopupPanelManager.defaultManager().hideLastPopup();
+ }
+ });
+
+ addUIInputMap(tracedRootPane, newInputMap);
+ addUIActionMap(tracedRootPane, newActionMap);
+ }
+ }
+
+ /**
+ * Adds the specified input map to the specified component.
+ *
+ * @param c
+ * Component.
+ * @param map
+ * Input map to add.
+ */
+ void addUIInputMap(JComponent c, InputMap map) {
+ InputMap lastNonUI = null;
+ InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
+
+ while (parent != null && !(parent instanceof UIResource)) {
+ lastNonUI = parent;
+ parent = parent.getParent();
+ }
+
+ if (lastNonUI == null) {
+ c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, map);
+ } else {
+ lastNonUI.setParent(map);
+ }
+ map.setParent(parent);
+ }
+
+ /**
+ * Adds the specified action map to the specified component.
+ *
+ * @param c
+ * Component.
+ * @param map
+ * Action map to add.
+ */
+ void addUIActionMap(JComponent c, ActionMap map) {
+ ActionMap lastNonUI = null;
+ ActionMap parent = c.getActionMap();
+
+ while (parent != null && !(parent instanceof UIResource)) {
+ lastNonUI = parent;
+ parent = parent.getParent();
+ }
+
+ if (lastNonUI == null) {
+ c.setActionMap(map);
+ } else {
+ lastNonUI.setParent(map);
+ }
+ map.setParent(parent);
+ }
+
+ /**
+ * Removes the specified input map from the specified component.
+ *
+ * @param c
+ * Component.
+ * @param map
+ * Input map to remove.
+ */
+ void removeUIInputMap(JComponent c, InputMap map) {
+ InputMap im = null;
+ InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
+
+ while (parent != null) {
+ if (parent == map) {
+ if (im == null) {
+ c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, map
+ .getParent());
+ } else {
+ im.setParent(map.getParent());
+ }
+ break;
+ }
+ im = parent;
+ parent = parent.getParent();
+ }
+ }
+
+ /**
+ * Removes the specified action map from the specified component.
+ *
+ * @param c
+ * Component.
+ * @param map
+ * Action map to remove.
+ */
+ void removeUIActionMap(JComponent c, ActionMap map) {
+ ActionMap im = null;
+ ActionMap parent = c.getActionMap();
+
+ while (parent != null) {
+ if (parent == map) {
+ if (im == null) {
+ c.setActionMap(map.getParent());
+ } else {
+ im.setParent(map.getParent());
+ }
+ break;
+ }
+ im = parent;
+ parent = parent.getParent();
+ }
+ }
+ }
+
+ /**
+ * This class is used to dismiss popup panels on the following events:
+ *
+ * <ul>
+ * <li>Mouse click outside any shown popup panel.</li>
+ * <li>Closing, iconifying or deactivation of a top-level window.</li>
+ * <li>Any change in the component hierarchy of a top-level window.</li>
+ * </ul>
+ *
+ * Only one top-level window is tracked at any time. The assumption is that
+ * the {@link PopupPanelManager} only shows popup panels originating from
+ * one top-level window.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected static class WindowTracker implements
+ PopupPanelManager.PopupListener, AWTEventListener,
+ ComponentListener, WindowListener {
+
+ /**
+ * The currently tracked window. It is the window of the originating
+ * component of the first popup panel in the currently shown sequence of
+ * {@link PopupPanelManager}.
+ */
+ Window grabbedWindow;
+
+ /**
+ * Last selected path in the {@link PopupPanelManager}.
+ */
+ List<PopupPanelManager.PopupInfo> lastPathSelected;
+
+ /**
+ * Creates the new window tracker.
+ */
+ public WindowTracker() {
+ PopupPanelManager popupPanelManager = PopupPanelManager
+ .defaultManager();
+ popupPanelManager.addPopupListener(this);
+ this.lastPathSelected = popupPanelManager.getShownPath();
+ if (this.lastPathSelected.size() != 0) {
+ grabWindow(this.lastPathSelected);
+ }
+ }
+
+ /**
+ * Grabs the window of the first popup panel in the specified popup
+ * panel sequence.
+ *
+ * @param shownPath
+ * Sequence of the currently shown popup panels.
+ */
+ void grabWindow(List<PopupPanelManager.PopupInfo> shownPath) {
+ final Toolkit tk = Toolkit.getDefaultToolkit();
+ java.security.AccessController
+ .doPrivileged(new java.security.PrivilegedAction() {
+ @Override
+ public Object run() {
+ tk.addAWTEventListener(WindowTracker.this,
+ AWTEvent.MOUSE_EVENT_MASK
+ | AWTEvent.MOUSE_MOTION_EVENT_MASK
+ | AWTEvent.MOUSE_WHEEL_EVENT_MASK
+ | AWTEvent.WINDOW_EVENT_MASK);
+ return null;
+ }
+ });
+
+ Component invoker = shownPath.get(0).getPopupOriginator();
+ grabbedWindow = invoker instanceof Window ? (Window) invoker
+ : SwingUtilities.getWindowAncestor(invoker);
+ if (grabbedWindow != null) {
+ grabbedWindow.addComponentListener(this);
+ grabbedWindow.addWindowListener(this);
+ }
+ }
+
+ /**
+ * Ungrabs the currently tracked window.
+ */
+ void ungrabWindow() {
+ final Toolkit tk = Toolkit.getDefaultToolkit();
+ // The grab should be removed
+ java.security.AccessController
+ .doPrivileged(new java.security.PrivilegedAction() {
+ @Override
+ public Object run() {
+ tk.removeAWTEventListener(WindowTracker.this);
+ return null;
+ }
+ });
+ if (grabbedWindow != null) {
+ grabbedWindow.removeComponentListener(this);
+ grabbedWindow.removeWindowListener(this);
+ grabbedWindow = null;
+ }
+ }
+
+ @Override
+ public void popupShown(PopupEvent event) {
+ PopupPanelManager msm = PopupPanelManager.defaultManager();
+ List<PopupPanelManager.PopupInfo> p = msm.getShownPath();
+
+ if (lastPathSelected.size() == 0 && p.size() != 0) {
+ // if it is the first popup panel to be shown, grab its window
+ grabWindow(p);
+ }
+
+ lastPathSelected = p;
+ }
+
+ @Override
+ public void popupHidden(PopupEvent event) {
+ PopupPanelManager msm = PopupPanelManager.defaultManager();
+ List<PopupPanelManager.PopupInfo> p = msm.getShownPath();
+
+ if (lastPathSelected.size() != 0 && p.size() == 0) {
+ // if it is the last popup panel to be hidden, ungrab its window
+ ungrabWindow();
+ }
+
+ lastPathSelected = p;
+ }
+
+ @Override
+ public void eventDispatched(AWTEvent ev) {
+ if (!(ev instanceof MouseEvent)) {
+ // We are interested in MouseEvents only
+ return;
+ }
+ MouseEvent me = (MouseEvent) ev;
+ final Component src = me.getComponent();
+ JPopupPanel popupPanelParent = (JPopupPanel) SwingUtilities
+ .getAncestorOfClass(JPopupPanel.class, src);
+ switch (me.getID()) {
+ case MouseEvent.MOUSE_PRESSED:
+ boolean wasCommandButtonPopupShowing = false;
+ if (src instanceof JCommandButton) {
+ wasCommandButtonPopupShowing = ((JCommandButton) src)
+ .getPopupModel().isPopupShowing();
+ }
+
+ if (!wasCommandButtonPopupShowing && (popupPanelParent != null)) {
+ // close all popups until this parent and return
+ PopupPanelManager.defaultManager().hidePopups(
+ popupPanelParent);
+ return;
+ }
+ if (src instanceof JRibbonTaskToggleButton) {
+ JRibbon ribbon = (JRibbon) SwingUtilities
+ .getAncestorOfClass(JRibbon.class, src);
+ if ((ribbon != null)
+ && FlamingoUtilities
+ .isShowingMinimizedRibbonInPopup(ribbon)) {
+ // This will be handled in the action listener installed
+ // on ribbon task toggle buttons in BasicRibbonUI.
+ // There the ribbon popup will be hidden.
+ return;
+ }
+ }
+
+ // if the popup of command button was showing, it will be hidden
+ // in BasicCommandButtonUI.processPopupAction() - via
+ // BasicCommandButtonUI.createPopupActionListener().
+ if (!wasCommandButtonPopupShowing) {
+ // special case - ignore mouse press on an item in a combo popup
+ if (SwingUtilities
+ .getAncestorOfClass(ComboPopup.class, src) == null) {
+ PopupPanelManager.defaultManager().hidePopups(src);
+ }
+ }
+
+ // pass the event so that it gets processed by the controls
+ break;
+
+ case MouseEvent.MOUSE_RELEASED:
+ // special case - mouse release on an item in a combo popup
+ if (SwingUtilities.getAncestorOfClass(ComboPopup.class, src) != null) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ PopupPanelManager.defaultManager().hidePopups(src);
+ }
+ });
+ }
+
+ // pass the event so that it gets processed by the controls
+ break;
+
+ case MouseEvent.MOUSE_WHEEL:
+ if (popupPanelParent != null) {
+ // close all popups until this parent and return
+ PopupPanelManager.defaultManager().hidePopups(
+ popupPanelParent);
+ return;
+ }
+
+ PopupPanelManager.defaultManager().hidePopups(src);
+ break;
+ }
+ }
+
+ /**
+ * Checks whether the specified component lies inside a
+ * {@link JPopupPanel}.
+ *
+ * @param src
+ * Component.
+ * @return <code>true</code> if the specified component lies inside a
+ * {@link JPopupPanel}.
+ */
+ boolean isInPopupPanel(Component src) {
+ for (Component c = src; c != null; c = c.getParent()) {
+ if (c instanceof Applet || c instanceof Window) {
+ break;
+ } else if (c instanceof JPopupPanel) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void componentResized(ComponentEvent e) {
+ PopupPanelManager.defaultManager().hidePopups(null);
+ }
+
+ @Override
+ public void componentMoved(ComponentEvent e) {
+ PopupPanelManager.defaultManager().hidePopups(null);
+ }
+
+ @Override
+ public void componentShown(ComponentEvent e) {
+ PopupPanelManager.defaultManager().hidePopups(null);
+ }
+
+ @Override
+ public void componentHidden(ComponentEvent e) {
+ PopupPanelManager.defaultManager().hidePopups(null);
+ }
+
+ @Override
+ public void windowClosing(WindowEvent e) {
+ PopupPanelManager.defaultManager().hidePopups(null);
+ }
+
+ @Override
+ public void windowClosed(WindowEvent e) {
+ PopupPanelManager.defaultManager().hidePopups(null);
+ }
+
+ @Override
+ public void windowIconified(WindowEvent e) {
+ PopupPanelManager.defaultManager().hidePopups(null);
+ }
+
+ @Override
+ public void windowDeactivated(WindowEvent e) {
+ PopupPanelManager.defaultManager().hidePopups(null);
+ }
+
+ @Override
+ public void windowOpened(WindowEvent e) {
+ }
+
+ @Override
+ public void windowDeiconified(WindowEvent e) {
+ }
+
+ @Override
+ public void windowActivated(WindowEvent e) {
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/ColorSelectorComponentUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/ColorSelectorComponentUI.java
new file mode 100644
index 0000000..440f191
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/ColorSelectorComponentUI.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common.popup;
+
+import javax.swing.plaf.ComponentUI;
+
+/**
+ * UI for command button ({@link JColorSelectorComponent}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class ColorSelectorComponentUI extends ComponentUI {
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/ColorSelectorPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/ColorSelectorPanelUI.java
new file mode 100644
index 0000000..ee77af0
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/ColorSelectorPanelUI.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common.popup;
+
+import javax.swing.plaf.ComponentUI;
+
+/**
+ * UI for command button ({@link JColorSelectorPanel}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class ColorSelectorPanelUI extends ComponentUI {
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/JColorSelectorComponent.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/JColorSelectorComponent.java
new file mode 100644
index 0000000..2639748
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/JColorSelectorComponent.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common.popup;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JComponent;
+import javax.swing.UIManager;
+
+import org.pushingpixels.flamingo.api.common.popup.JColorSelectorPopupMenu;
+
+public class JColorSelectorComponent extends JComponent {
+ private Color color;
+
+ private List<JColorSelectorPopupMenu.ColorSelectorCallback> colorChooserCallbacks;
+
+ private boolean isTopOpen;
+
+ private boolean isBottomOpen;
+
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "ColorSelectorComponentUI";
+
+ public JColorSelectorComponent(Color color,
+ JColorSelectorPopupMenu.ColorSelectorCallback colorChooserCallback) {
+ this.setOpaque(true);
+ this.color = color;
+ this.colorChooserCallbacks = new ArrayList<JColorSelectorPopupMenu.ColorSelectorCallback>();
+ this.colorChooserCallbacks.add(colorChooserCallback);
+
+ this.updateUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JButton#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((ColorSelectorComponentUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicColorSelectorComponentUI.createUI(this));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JButton#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ public Color getColor() {
+ return this.color;
+ }
+
+ public synchronized void addColorSelectorCallback(
+ JColorSelectorPopupMenu.ColorSelectorCallback callback) {
+ this.colorChooserCallbacks.add(callback);
+ }
+
+ public synchronized void onColorSelected(Color selected) {
+ for (JColorSelectorPopupMenu.ColorSelectorCallback callback : this.colorChooserCallbacks) {
+ callback.onColorSelected(selected);
+ }
+ }
+
+ public synchronized void onColorRollover(Color rollover) {
+ for (JColorSelectorPopupMenu.ColorSelectorCallback callback : this.colorChooserCallbacks) {
+ callback.onColorRollover(rollover);
+ }
+ }
+
+ public void setTopOpen(boolean isTopOpen) {
+ this.isTopOpen = isTopOpen;
+ }
+
+ public void setBottomOpen(boolean isBottomOpen) {
+ this.isBottomOpen = isBottomOpen;
+ }
+
+ public boolean isTopOpen() {
+ return this.isTopOpen;
+ }
+
+ public boolean isBottomOpen() {
+ return this.isBottomOpen;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/JColorSelectorPanel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/JColorSelectorPanel.java
new file mode 100644
index 0000000..ac52180
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/JColorSelectorPanel.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common.popup;
+
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+
+public class JColorSelectorPanel extends JPanel {
+ private String caption;
+
+ private JPanel colorSelectionContainer;
+
+ private boolean isLastPanel;
+
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "ColorSelectorPanelUI";
+
+ public JColorSelectorPanel(String caption, JPanel colorSelectionContainer) {
+ this.caption = caption;
+ this.colorSelectionContainer = colorSelectionContainer;
+
+ this.updateUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JButton#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((ColorSelectorPanelUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicColorSelectorPanelUI.createUI(this));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JButton#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ public String getCaption() {
+ return this.caption;
+ }
+
+ public JPanel getColorSelectionContainer() {
+ return this.colorSelectionContainer;
+ }
+
+ public void setLastPanel(boolean isLastPanel) {
+ this.isLastPanel = isLastPanel;
+ }
+
+ public boolean isLastPanel() {
+ return this.isLastPanel;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/PopupPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/PopupPanelUI.java
new file mode 100644
index 0000000..229cbc0
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/common/popup/PopupPanelUI.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.common.popup;
+
+import javax.swing.plaf.PanelUI;
+
+import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
+
+/**
+ * UI for popup panel ({@link JPopupPanel}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class PopupPanelUI extends PanelUI {
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/AbstractBandControlPanel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/AbstractBandControlPanel.java
new file mode 100644
index 0000000..4a2ada1
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/AbstractBandControlPanel.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import javax.swing.JPanel;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
+import org.pushingpixels.flamingo.api.ribbon.JRibbonBand;
+
+/**
+ * Control panel of a single {@link JRibbonBand}. This class is for internal use
+ * only and should not be directly used by the applications.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class AbstractBandControlPanel extends JPanel implements UIResource {
+ private AbstractRibbonBand ribbonBand;
+
+ public AbstractBandControlPanel() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#getUI()
+ */
+ @Override
+ public BandControlPanelUI getUI() {
+ return (BandControlPanelUI) ui;
+ }
+
+ public void setRibbonBand(AbstractRibbonBand ribbonBand) {
+ this.ribbonBand = ribbonBand;
+ }
+
+ public AbstractRibbonBand getRibbonBand() {
+ return this.ribbonBand;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/AbstractBandControlPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/AbstractBandControlPanelUI.java
new file mode 100644
index 0000000..fc2bc1e
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/AbstractBandControlPanelUI.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.BorderUIResource;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.flamingo.api.common.CommandButtonDisplayState;
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.icon.EmptyResizableIcon;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+/**
+ * Basic UI for control panel of ribbon band {@link JBandControlPanel}.
+ *
+ * @author Kirill Grouchnikov
+ */
+abstract class AbstractBandControlPanelUI extends BandControlPanelUI {
+ /**
+ * The associated control panel.
+ */
+ protected AbstractBandControlPanel controlPanel;
+
+ protected JCommandButton dummy;
+
+ public static final String TOP_ROW = "flamingo.internal.ribbonBandControlPanel.topRow";
+
+ public static final String MID_ROW = "flamingo.internal.ribbonBandControlPanel.midRow";
+
+ public static final String BOTTOM_ROW = "flamingo.internal.ribbonBandControlPanel.bottomRow";
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.controlPanel = (AbstractBandControlPanel) c;
+
+ this.dummy = new JCommandButton("Dummy", new EmptyResizableIcon(16));
+ this.dummy.setDisplayState(CommandButtonDisplayState.BIG);
+ this.dummy
+ .setCommandButtonKind(CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION);
+
+ installDefaults();
+ installComponents();
+ installListeners();
+
+ c.setLayout(createLayoutManager());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ uninstallListeners();
+ uninstallComponents();
+ uninstallDefaults();
+
+ c.setLayout(null);
+ this.controlPanel = null;
+ }
+
+ /**
+ * Installs listeners on the associated control panel of a ribbon band.
+ */
+ protected void installListeners() {
+ }
+
+ /**
+ * Uninstalls listeners from the associated control panel of a ribbon band.
+ */
+ protected void uninstallListeners() {
+ }
+
+ /**
+ * Installs components on the associated control panel of a ribbon band.
+ */
+ protected void installComponents() {
+ }
+
+ /**
+ * Uninstalls components from the associated control panel of a ribbon band.
+ */
+ protected void uninstallComponents() {
+ }
+
+ /**
+ * Installs default parameters on the associated control panel of a ribbon
+ * band.
+ */
+ protected void installDefaults() {
+ Color bg = this.controlPanel.getBackground();
+ if (bg == null || bg instanceof UIResource) {
+ this.controlPanel.setBackground(FlamingoUtilities.getColor(
+ Color.lightGray, "ControlPanel.background",
+ "Panel.background"));
+ }
+
+ Border b = this.controlPanel.getBorder();
+ if (b == null || b instanceof UIResource) {
+ Border toSet = UIManager.getBorder("ControlPanel.border");
+ if (toSet == null)
+ new BorderUIResource.EmptyBorderUIResource(1, 2, 1, 2);
+ this.controlPanel.setBorder(toSet);
+ }
+ }
+
+ /**
+ * Uninstalls default parameters from the associated control panel.
+ */
+ protected void uninstallDefaults() {
+ LookAndFeel.uninstallBorder(this.controlPanel);
+ }
+
+ /**
+ * Invoked by <code>installUI</code> to create a layout manager object to
+ * manage the {@link JBandControlPanel}.
+ *
+ * @return a layout manager object
+ */
+ protected abstract LayoutManager createLayoutManager();
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+
+ this.paintBandBackground(graphics, new Rectangle(0, 0, c.getWidth(), c
+ .getHeight()));
+
+ graphics.dispose();
+ }
+
+ /**
+ * Paints band background.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param toFill
+ * Rectangle for the background.
+ */
+ protected void paintBandBackground(Graphics graphics, Rectangle toFill) {
+ graphics.setColor(controlPanel.getBackground());
+ graphics.fillRect(toFill.x, toFill.y, toFill.width, toFill.height);
+ }
+
+ @Override
+ public int getLayoutGap() {
+ return 4;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BandControlPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BandControlPanelUI.java
new file mode 100644
index 0000000..ad88d20
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BandControlPanelUI.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import javax.swing.plaf.PanelUI;
+
+
+/**
+ * UI for control panel of ribbon band ({@link JBandControlPanel}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class BandControlPanelUI extends PanelUI {
+ /**
+ * Returns the layout gap for the controls in the associated control panel.
+ *
+ * @return The layout gap for the controls in the associated control panel.
+ */
+ public abstract int getLayoutGap();
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicBandControlPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicBandControlPanelUI.java
new file mode 100644
index 0000000..e481a86
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicBandControlPanelUI.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.common.CommandButtonDisplayState;
+import org.pushingpixels.flamingo.api.ribbon.*;
+import org.pushingpixels.flamingo.api.ribbon.resize.IconRibbonBandResizePolicy;
+import org.pushingpixels.flamingo.api.ribbon.resize.RibbonBandResizePolicy;
+import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonBandUI.CollapsedButtonPopupPanel;
+
+/**
+ * Basic UI for control panel of ribbon band {@link JBandControlPanel}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicBandControlPanelUI extends AbstractBandControlPanelUI {
+ private JSeparator[] groupSeparators;
+
+ private JLabel[] groupLabels;
+
+ protected ChangeListener changeListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicBandControlPanelUI();
+ }
+
+ /**
+ * Invoked by <code>installUI</code> to create a layout manager object to
+ * manage the {@link JBandControlPanel}.
+ *
+ * @return a layout manager object
+ */
+ @Override
+ protected LayoutManager createLayoutManager() {
+ return new ControlPanelLayout();
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.changeListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ syncGroupHeaders();
+ controlPanel.revalidate();
+ }
+ };
+ ((JBandControlPanel) this.controlPanel)
+ .addChangeListener(this.changeListener);
+ }
+
+ @Override
+ protected void uninstallListeners() {
+ ((JBandControlPanel) this.controlPanel)
+ .removeChangeListener(this.changeListener);
+ this.changeListener = null;
+
+ super.uninstallListeners();
+ }
+
+ @Override
+ protected void installComponents() {
+ super.installComponents();
+
+ this.syncGroupHeaders();
+ }
+
+ @Override
+ protected void uninstallComponents() {
+ if (this.groupSeparators != null) {
+ for (JSeparator groupSeparator : this.groupSeparators) {
+ this.controlPanel.remove(groupSeparator);
+ }
+ }
+ if (this.groupLabels != null) {
+ for (JLabel groupLabel : this.groupLabels) {
+ if (groupLabel != null)
+ this.controlPanel.remove(groupLabel);
+ }
+ }
+
+ super.uninstallComponents();
+ }
+
+ protected void syncGroupHeaders() {
+ if (this.groupSeparators != null) {
+ for (JSeparator groupSeparator : this.groupSeparators) {
+ this.controlPanel.remove(groupSeparator);
+ }
+ }
+ if (this.groupLabels != null) {
+ for (JLabel groupLabel : this.groupLabels) {
+ if (groupLabel != null)
+ this.controlPanel.remove(groupLabel);
+ }
+ }
+
+ int groupCount = ((JBandControlPanel) this.controlPanel)
+ .getControlPanelGroupCount();
+ if (groupCount > 1) {
+ this.groupSeparators = new JSeparator[groupCount - 1];
+ for (int i = 0; i < groupCount - 1; i++) {
+ this.groupSeparators[i] = new JSeparator(JSeparator.VERTICAL);
+ this.controlPanel.add(this.groupSeparators[i]);
+ }
+ }
+ if (groupCount > 0) {
+ this.groupLabels = new JLabel[groupCount];
+ for (int i = 0; i < groupCount; i++) {
+ String title = ((JBandControlPanel) this.controlPanel)
+ .getControlPanelGroupTitle(i);
+ if (title != null) {
+ this.groupLabels[i] = new JLabel(title);
+ this.controlPanel.add(this.groupLabels[i]);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Layout for the control panel of ribbon band.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class ControlPanelLayout implements LayoutManager {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ // The height of ribbon band control panel is
+ // computed based on the preferred height of a command
+ // button in BIG state.
+ int buttonHeight = dummy.getPreferredSize().height;
+ int vGap = getLayoutGap() * 3 / 4;
+ int minusGaps = buttonHeight - 2 * vGap;
+ switch (minusGaps % 3) {
+ case 1:
+ buttonHeight += 2;
+ break;
+ case 2:
+ buttonHeight++;
+ break;
+ }
+
+ Insets ins = c.getInsets();
+
+ // System.out.println("Control panel pref = "
+ // + (buttonHeight + ins.top + ins.bottom));
+
+ return new Dimension(c.getWidth(), buttonHeight + ins.top
+ + ins.bottom);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ return this.preferredLayoutSize(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ // System.out.println("Control panel real = " + c.getHeight());
+
+ AbstractRibbonBand ribbonBand = ((JBandControlPanel) c)
+ .getRibbonBand();
+ RibbonBandResizePolicy currentResizePolicy = ribbonBand
+ .getCurrentResizePolicy();
+ if (currentResizePolicy == null)
+ return;
+
+ boolean ltr = c.getComponentOrientation().isLeftToRight();
+
+ // need place for border
+ Insets ins = c.getInsets();
+ int gap = getLayoutGap();
+ int x = ltr ? ins.left + gap / 2 : c.getWidth() - ins.right - gap
+ / 2;
+ int availableHeight = c.getHeight() - ins.top - ins.bottom;
+
+ if (SwingUtilities.getAncestorOfClass(
+ CollapsedButtonPopupPanel.class, c) != null) {
+ // install the most permissive resize policy on the popup
+ // panel of a collapsed ribbon band
+ List<RibbonBandResizePolicy> resizePolicies = ribbonBand
+ .getResizePolicies();
+ resizePolicies.get(0).install(availableHeight, gap);
+ } else {
+ if (currentResizePolicy instanceof IconRibbonBandResizePolicy) {
+ return;
+ }
+
+ // Installs the resize policy - this updates the display
+ // priority of all the galleries and buttons
+ currentResizePolicy.install(availableHeight, gap);
+ }
+
+ int controlPanelGroupIndex = 0;
+ for (JBandControlPanel.ControlPanelGroup controlPanelGroup : ((JBandControlPanel) controlPanel)
+ .getControlPanelGroups()) {
+ // handle the group separators
+ if (controlPanelGroupIndex > 0) {
+ int prefW = groupSeparators[controlPanelGroupIndex - 1]
+ .getPreferredSize().width;
+ int sepX = ltr ? x - gap + (gap - prefW) / 2 : x + gap / 2
+ - (gap - prefW) / 2;
+ groupSeparators[controlPanelGroupIndex - 1].setBounds(sepX,
+ ins.top, prefW, availableHeight);
+ }
+
+ boolean hasLeadingComponent = false;
+ boolean isCoreContent = controlPanelGroup.isCoreContent();
+ if (isCoreContent) {
+ // how much vertical space is available in each row?
+ int singleRowHeight = availableHeight / 3;
+
+ boolean hasTitle = (controlPanelGroup.getGroupTitle() != null);
+ int maxWidthInCurrColumn = 0;
+ if (hasTitle) {
+ JLabel titleLabel = groupLabels[controlPanelGroupIndex];
+ int pw = titleLabel.getPreferredSize().width;
+ int titleLabelHeight = Math.min(singleRowHeight - gap
+ / 4, titleLabel.getPreferredSize().height);
+ int yNudge = singleRowHeight - titleLabelHeight;
+ int baseline = (titleLabelHeight > 0) ? titleLabel
+ .getBaseline(pw, titleLabelHeight) : 0;
+ if (ltr) {
+ titleLabel.setBounds(x + gap, ins.top + yNudge
+ - titleLabelHeight + baseline, pw,
+ titleLabelHeight);
+ } else {
+ titleLabel.setBounds(x - gap - pw, ins.top + yNudge
+ - titleLabelHeight + baseline, pw,
+ titleLabelHeight);
+ }
+ maxWidthInCurrColumn = gap + pw;
+ }
+ List<JRibbonComponent> ribbonComps = controlPanelGroup
+ .getRibbonComps();
+ Map<JRibbonComponent, Integer> ribbonCompRowSpans = controlPanelGroup
+ .getRibbonCompsRowSpans();
+ List<JRibbonComponent> currColumn = new ArrayList<JRibbonComponent>();
+
+ // if a group has a title, then the core components in that
+ // group will take two rows instead of three
+ int startingRow = hasTitle ? 1 : 0;
+ int rowIndex = startingRow;
+
+ for (int i = 0; i < ribbonComps.size(); i++) {
+ JRibbonComponent coreComp = ribbonComps.get(i);
+ int prefWidth = coreComp.getPreferredSize().width;
+ int rowSpan = ribbonCompRowSpans.get(coreComp);
+
+ // do we need to start a new column?
+ int nextRowIndex = rowIndex + rowSpan;
+ if (nextRowIndex > 3) {
+ if (ltr) {
+ if (hasLeadingComponent)
+ x += gap;
+ x += maxWidthInCurrColumn;
+ } else {
+ if (hasLeadingComponent)
+ x -= gap;
+ x -= maxWidthInCurrColumn;
+ }
+ hasLeadingComponent = true;
+ maxWidthInCurrColumn = 0;
+ rowIndex = startingRow;
+ currColumn.clear();
+ }
+
+ // how much vertical space does a component get?
+ int compHeight = Math.min(rowSpan * singleRowHeight
+ - gap / 4, coreComp.getPreferredSize().height);
+ int yNudge = rowSpan * singleRowHeight - compHeight;
+ int y = rowIndex * singleRowHeight + ins.top;
+
+ if (ltr) {
+ coreComp.setBounds(x, y + yNudge, prefWidth,
+ compHeight);
+ } else {
+ coreComp.setBounds(x - prefWidth, y + yNudge,
+ prefWidth, compHeight);
+ }
+ maxWidthInCurrColumn = Math.max(maxWidthInCurrColumn,
+ prefWidth);
+ currColumn.add(coreComp);
+
+ coreComp.putClientProperty(
+ AbstractBandControlPanelUI.TOP_ROW, rowIndex == 0);
+ coreComp.putClientProperty(
+ AbstractBandControlPanelUI.MID_ROW, (rowIndex > 0)
+ && (rowIndex < 2));
+ coreComp.putClientProperty(
+ AbstractBandControlPanelUI.BOTTOM_ROW, rowIndex == 2);
+
+ // scan the components in this column and make them to
+ // have the same width as the widest component in this
+ // column
+ for (JRibbonComponent comp : currColumn) {
+ Rectangle bounds = comp.getBounds();
+ if (ltr) {
+ comp.setBounds(bounds.x, bounds.y,
+ maxWidthInCurrColumn, bounds.height);
+ } else {
+ comp.setBounds(bounds.x + bounds.width
+ - maxWidthInCurrColumn, bounds.y,
+ maxWidthInCurrColumn, bounds.height);
+ }
+ comp.doLayout();
+ }
+
+ // System.out
+ // .println(rowSpan + ":" + coreComp.getBounds());
+
+ rowIndex += rowSpan;
+ }
+ if ((rowIndex > 0) && (rowIndex <= 3)) {
+ if (ltr) {
+ if (hasLeadingComponent)
+ x += gap;
+ x += maxWidthInCurrColumn;
+ } else {
+ if (hasLeadingComponent)
+ x -= gap;
+ x -= maxWidthInCurrColumn;
+ }
+ hasLeadingComponent = true;
+ }
+ } else {
+ // The galleries
+ for (RibbonElementPriority elementPriority : RibbonElementPriority
+ .values()) {
+ for (JRibbonGallery gallery : controlPanelGroup
+ .getRibbonGalleries(elementPriority)) {
+ int pw = gallery.getPreferredWidth(gallery
+ .getDisplayPriority(), availableHeight);
+ if (ltr) {
+ gallery.setBounds(x, ins.top, pw,
+ availableHeight);
+ if (hasLeadingComponent)
+ x += gap;
+ x += pw;
+ } else {
+ gallery.setBounds(x - pw, ins.top, pw,
+ availableHeight);
+ if (hasLeadingComponent)
+ x -= gap;
+ x -= pw;
+ }
+ hasLeadingComponent = true;
+ }
+ }
+
+ Map<CommandButtonDisplayState, List<AbstractCommandButton>> buttonMap = new HashMap<CommandButtonDisplayState, List<AbstractCommandButton>>();
+ for (RibbonElementPriority elementPriority : RibbonElementPriority
+ .values()) {
+ for (AbstractCommandButton commandButton : controlPanelGroup
+ .getRibbonButtons(elementPriority)) {
+ CommandButtonDisplayState state = commandButton
+ .getDisplayState();
+ if (buttonMap.get(state) == null) {
+ buttonMap.put(state,
+ new ArrayList<AbstractCommandButton>());
+ }
+ buttonMap.get(state).add(commandButton);
+ }
+ }
+
+ List<AbstractCommandButton> bigs = buttonMap
+ .get(CommandButtonDisplayState.BIG);
+ if (bigs != null) {
+ for (AbstractCommandButton bigButton : bigs) {
+ // Big buttons
+ int bigButtonWidth = bigButton.getPreferredSize().width;
+ if (hasLeadingComponent) {
+ if (ltr) {
+ x += gap;
+ } else {
+ x -= gap;
+ }
+ }
+ if (ltr) {
+ bigButton.setBounds(x, ins.top, bigButtonWidth,
+ availableHeight);
+ } else {
+ bigButton.setBounds(x - bigButtonWidth,
+ ins.top, bigButtonWidth,
+ availableHeight);
+ }
+ bigButton.putClientProperty(TOP_ROW, Boolean.FALSE);
+ bigButton.putClientProperty(MID_ROW, Boolean.FALSE);
+ bigButton.putClientProperty(BOTTOM_ROW,
+ Boolean.FALSE);
+ if (ltr) {
+ x += bigButtonWidth;
+ } else {
+ x -= bigButtonWidth;
+ }
+ hasLeadingComponent = true;
+ }
+ }
+
+ // Medium buttons
+ int vGap = gap * 3 / 4;
+ int medSmallButtonHeight = (availableHeight - 2 * vGap) / 3;
+
+ int index3 = 0;
+ int maxWidth3 = 0;
+ List<AbstractCommandButton> mediums = buttonMap
+ .get(CommandButtonDisplayState.MEDIUM);
+ if (mediums != null) {
+ for (AbstractCommandButton mediumButton : mediums) {
+ int medWidth = mediumButton.getPreferredSize().width;
+ maxWidth3 = Math.max(maxWidth3, medWidth);
+
+ if (hasLeadingComponent && (index3 == 0)) {
+ if (ltr) {
+ x += gap;
+ } else {
+ x -= gap;
+ }
+ }
+ int buttonTop = (medSmallButtonHeight + vGap)
+ * index3;
+ int buttonBottom = (medSmallButtonHeight + vGap)
+ * (index3 + 1) - vGap;
+ if (ltr) {
+ mediumButton.setBounds(x, ins.top + buttonTop,
+ medWidth, buttonBottom - buttonTop);
+ } else {
+ mediumButton.setBounds(x - medWidth, ins.top
+ + buttonTop, medWidth, buttonBottom
+ - buttonTop);
+ }
+ mediumButton.putClientProperty(TOP_ROW, index3 == 0);
+ mediumButton.putClientProperty(MID_ROW, index3 == 1);
+ mediumButton.putClientProperty(BOTTOM_ROW, index3 == 2);
+
+ index3++;
+ if (index3 == 3) {
+ // last button in threesome
+ index3 = 0;
+ if (ltr) {
+ x += maxWidth3;
+ } else {
+ x -= maxWidth3;
+ }
+ hasLeadingComponent = true;
+ maxWidth3 = 0;
+ }
+ }
+ }
+ // at this point, maxWidth3 may be non-null. We can safely
+ // add it, since in this case there will be no buttons
+ // left in lowButtons
+ if (maxWidth3 > 0) {
+ if (ltr) {
+ x += maxWidth3;
+ } else {
+ x -= maxWidth3;
+ }
+ hasLeadingComponent = true;
+ }
+
+ index3 = 0;
+ maxWidth3 = 0;
+ List<AbstractCommandButton> smalls = buttonMap
+ .get(CommandButtonDisplayState.SMALL);
+ if (smalls != null) {
+ for (AbstractCommandButton smallButton : smalls) {
+ int lowWidth = smallButton.getPreferredSize().width;
+ maxWidth3 = Math.max(maxWidth3, lowWidth);
+ if (hasLeadingComponent && (index3 == 0)) {
+ if (ltr) {
+ x += gap;
+ } else {
+ x -= gap;
+ }
+ }
+ int buttonTop = (medSmallButtonHeight + vGap)
+ * index3;
+ int buttonBottom = (medSmallButtonHeight + vGap)
+ * (index3 + 1) - vGap;
+ if (ltr) {
+ smallButton.setBounds(x, ins.top + buttonTop,
+ lowWidth, buttonBottom - buttonTop);
+ } else {
+ smallButton.setBounds(x - lowWidth, ins.top
+ + buttonTop, lowWidth, buttonBottom
+ - buttonTop);
+ }
+ smallButton.putClientProperty(TOP_ROW, index3 == 0);
+ smallButton.putClientProperty(MID_ROW, index3 == 1);
+ smallButton.putClientProperty(BOTTOM_ROW, index3 == 2);
+
+ index3++;
+ if (index3 == 3) {
+ // last button in threesome
+ index3 = 0;
+ if (ltr) {
+ x += maxWidth3;
+ } else {
+ x -= maxWidth3;
+ }
+ hasLeadingComponent = true;
+ maxWidth3 = 0;
+ }
+ }
+ }
+ if ((index3 < 3) && (maxWidth3 > 0)) {
+ if (ltr) {
+ x += maxWidth3;
+ } else {
+ x -= maxWidth3;
+ }
+ hasLeadingComponent = true;
+ }
+ }
+ // space for the separator
+ if (ltr) {
+ x += gap * 3 / 2;
+ } else {
+ x -= gap * 3 / 2;
+ }
+ controlPanelGroupIndex++;
+ }
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicFlowBandControlPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicFlowBandControlPanelUI.java
new file mode 100644
index 0000000..71d209e
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicFlowBandControlPanelUI.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.*;
+import java.util.List;
+
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
+import org.pushingpixels.flamingo.api.ribbon.resize.IconRibbonBandResizePolicy;
+import org.pushingpixels.flamingo.api.ribbon.resize.RibbonBandResizePolicy;
+import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonBandUI.CollapsedButtonPopupPanel;
+
+/**
+ * Basic UI for control panel of ribbon band {@link JBandControlPanel}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicFlowBandControlPanelUI extends AbstractBandControlPanelUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicFlowBandControlPanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.AbstractBandControlPanelUI#createLayoutManager
+ * ()
+ */
+ @Override
+ protected LayoutManager createLayoutManager() {
+ return new FlowControlPanelLayout();
+ }
+
+ /**
+ * Layout for the control panel of flow ribbon band.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class FlowControlPanelLayout implements LayoutManager {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ // The height of ribbon band control panel is
+ // computed based on the preferred height of a command
+ // button in BIG state.
+ int buttonHeight = dummy.getPreferredSize().height;
+ int vGap = getLayoutGap() * 3 / 4;
+ int minusGaps = buttonHeight - 2 * vGap;
+ switch (minusGaps % 3) {
+ case 1:
+ buttonHeight += 2;
+ break;
+ case 2:
+ buttonHeight++;
+ break;
+ }
+
+ Insets ins = c.getInsets();
+ return new Dimension(c.getWidth(), buttonHeight + ins.top
+ + ins.bottom);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ return this.preferredLayoutSize(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ JFlowBandControlPanel flowBandControlPanel = (JFlowBandControlPanel) c;
+ AbstractRibbonBand ribbonBand = flowBandControlPanel
+ .getRibbonBand();
+ RibbonBandResizePolicy currentResizePolicy = ribbonBand
+ .getCurrentResizePolicy();
+ if (currentResizePolicy == null)
+ return;
+
+ boolean ltr = c.getComponentOrientation().isLeftToRight();
+ // need place for border
+ Insets ins = c.getInsets();
+ int x = ins.left;
+ int gap = getLayoutGap();
+ int availableHeight = c.getHeight() - ins.top - ins.bottom;
+
+ if (SwingUtilities.getAncestorOfClass(
+ CollapsedButtonPopupPanel.class, c) != null) {
+ List<RibbonBandResizePolicy> resizePolicies = ribbonBand
+ .getResizePolicies();
+ // install the most permissive resize policy on the popup
+ // panel of a collapsed ribbon band
+ resizePolicies.get(0).install(availableHeight, gap);
+ } else {
+ if (currentResizePolicy instanceof IconRibbonBandResizePolicy) {
+ return;
+ }
+
+ // Installs the resize policy
+ currentResizePolicy.install(availableHeight, gap);
+ }
+
+ // compute the max preferred height of the components and the
+ // number of rows
+ int maxHeight = 0;
+ int rowCount = 1;
+ for (JComponent flowComponent : flowBandControlPanel
+ .getFlowComponents()) {
+ Dimension prefSize = flowComponent.getPreferredSize();
+ if ((x + prefSize.width) > (c.getWidth() - ins.right)) {
+ x = ins.left;
+ rowCount++;
+ }
+ x += prefSize.width + gap;
+ maxHeight = Math.max(maxHeight, prefSize.height);
+ }
+ // rowCount++;
+
+ int vGap = (availableHeight - rowCount * maxHeight) / rowCount;
+ if (vGap < 0) {
+ vGap = 2;
+ maxHeight = (availableHeight - vGap * (rowCount - 1))
+ / rowCount;
+ }
+ int y = ins.top + vGap / 2;
+ x = ltr ? ins.left : c.getWidth() - ins.right;
+ int rowIndex = 0;
+ for (JComponent flowComponent : flowBandControlPanel
+ .getFlowComponents()) {
+ Dimension prefSize = flowComponent.getPreferredSize();
+ if (ltr) {
+ if ((x + prefSize.width) > (c.getWidth() - ins.right)) {
+ x = ins.left;
+ y += (maxHeight + vGap);
+ rowIndex++;
+ }
+ } else {
+ if ((x - prefSize.width) < ins.left) {
+ x = c.getWidth() - ins.right;
+ y += (maxHeight + vGap);
+ rowIndex++;
+ }
+ }
+ int height = Math.min(maxHeight, prefSize.height);
+ if (ltr) {
+ flowComponent.setBounds(x, y + (maxHeight - height) / 2,
+ prefSize.width, height);
+ } else {
+ flowComponent.setBounds(x - prefSize.width, y
+ + (maxHeight - height) / 2, prefSize.width, height);
+ }
+ flowComponent.putClientProperty(
+ AbstractBandControlPanelUI.TOP_ROW, rowIndex == 0);
+ flowComponent.putClientProperty(
+ AbstractBandControlPanelUI.MID_ROW, (rowIndex > 0)
+ && (rowIndex < (rowCount - 1)));
+ flowComponent.putClientProperty(
+ AbstractBandControlPanelUI.BOTTOM_ROW, rowIndex == (rowCount - 1));
+ if (ltr) {
+ x += (prefSize.width + gap);
+ } else {
+ x -= (prefSize.width + gap);
+ }
+ }
+
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonBandUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonBandUI.java
new file mode 100644
index 0000000..f417755
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonBandUI.java
@@ -0,0 +1,978 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.popup.*;
+import org.pushingpixels.flamingo.api.ribbon.*;
+import org.pushingpixels.flamingo.api.ribbon.resize.IconRibbonBandResizePolicy;
+import org.pushingpixels.flamingo.api.ribbon.resize.RibbonBandResizePolicy;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonUI;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+import org.pushingpixels.flamingo.internal.utils.RenderingUtils;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.swing.SwingRepaintCallback;
+
+/**
+ * Basic UI for ribbon band {@link JRibbonBand}.
+ *
+ * @author Kirill Grouchnikov
+ * @author Matt Nathan
+ */
+public class BasicRibbonBandUI extends RibbonBandUI {
+ /**
+ * The associated ribbon band.
+ */
+ protected AbstractRibbonBand<AbstractBandControlPanel> ribbonBand;
+
+ /**
+ * The button for collapsed state.
+ */
+ protected JCommandButton collapsedButton;
+
+ /**
+ * The band expand button. Is visible when the
+ * {@link JRibbonBand#getExpandActionListener()} of the associated ribbon
+ * band is not <code>null</code>.
+ */
+ protected AbstractCommandButton expandButton;
+
+ protected float rolloverAmount;
+
+ protected Timeline rolloverTimeline;
+
+ /**
+ * Mouse listener on the associated ribbon band.
+ */
+ protected MouseListener mouseListener;
+
+ /**
+ * Listens to property changes on the associated ribbon band.
+ */
+ protected PropertyChangeListener propertyChangeListener;
+
+ /**
+ * Action listener on the expand button.
+ */
+ protected ActionListener expandButtonActionListener;
+
+ /**
+ * Popup panel that shows the contents of the ribbon band when it is in a
+ * collapsed state.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected static class CollapsedButtonPopupPanel extends JPopupPanel {
+ /**
+ * The main component of <code>this</code> popup panel. Can be
+ * <code>null</code>.
+ */
+ protected Component component;
+
+ /**
+ * Creates popup gallery with the specified component.
+ *
+ * @param component
+ * The main component of the popup gallery.
+ * @param originalSize
+ * The original dimension of the main component.
+ */
+ public CollapsedButtonPopupPanel(Component component,
+ Dimension originalSize) {
+ this.component = component;
+ this.setLayout(new BorderLayout());
+ this.add(component, BorderLayout.CENTER);
+ // System.out.println("Popup dim is " + originalSize);
+ this.setPreferredSize(originalSize);
+ this.setSize(originalSize);
+ }
+
+ /**
+ * Removes the main component of <code>this</code> popup gallery.
+ *
+ * @return The removed main component.
+ */
+ public Component removeComponent() {
+ this.remove(this.component);
+ return this.component;
+ }
+
+ /**
+ * Returns the main component of <code>this</code> popup gallery.
+ *
+ * @return The main component.
+ */
+ public Component getComponent() {
+ return this.component;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicRibbonBandUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ //noinspection unchecked
+ this.ribbonBand = (AbstractRibbonBand<AbstractBandControlPanel>) c;
+
+ this.rolloverTimeline = new Timeline(this);
+ this.rolloverTimeline.addPropertyToInterpolate("rolloverAmount", 0.0f,
+ 1.0f);
+ this.rolloverTimeline.addCallback(new SwingRepaintCallback(
+ this.ribbonBand));
+ this.rolloverTimeline.setDuration(250);
+
+ installDefaults();
+ installComponents();
+ installListeners();
+ c.setLayout(createLayoutManager());
+ AWTRibbonEventListener.install();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ c.setLayout(null);
+ uninstallListeners();
+ uninstallDefaults();
+ uninstallComponents();
+ if (!AWTRibbonEventListener.uninstall()) {
+ // should remove other methods of tracking
+ }
+ }
+
+ /**
+ * Installs default parameters on the associated ribbon band.
+ */
+ protected void installDefaults() {
+ Color bg = this.ribbonBand.getBackground();
+ if (bg == null || bg instanceof UIResource) {
+ this.ribbonBand.setBackground(FlamingoUtilities.getColor(
+ Color.lightGray, "RibbonBand.background",
+ "Panel.background"));
+ }
+
+ Border b = this.ribbonBand.getBorder();
+ if (b == null || b instanceof UIResource) {
+ Border toSet = UIManager.getBorder("RibbonBand.border");
+ if (toSet == null)
+ toSet = new BorderUIResource(new RoundBorder(FlamingoUtilities
+ .getBorderColor(), new Insets(2, 2, 2, 2)));
+ this.ribbonBand.setBorder(toSet);
+ }
+ }
+
+ /**
+ * Installs subcomponents on the associated ribbon band.
+ */
+ protected void installComponents() {
+ this.collapsedButton = new JCommandButton(this.ribbonBand.getTitle(),
+ this.ribbonBand.getIcon());
+ this.collapsedButton.setDisplayState(CommandButtonDisplayState.BIG);
+ this.collapsedButton
+ .setCommandButtonKind(JCommandButton.CommandButtonKind.POPUP_ONLY);
+ this.collapsedButton.setPopupKeyTip(this.ribbonBand
+ .getCollapsedStateKeyTip());
+ this.ribbonBand.add(this.collapsedButton);
+
+ if (this.ribbonBand.getExpandActionListener() != null) {
+ this.expandButton = this.createExpandButton();
+ this.ribbonBand.add(this.expandButton);
+ }
+ }
+
+ /**
+ * Creates the expand button for the associated ribbon band.
+ *
+ * @return Expand button for the associated ribbon band.
+ */
+ protected JCommandButton createExpandButton() {
+ ResizableIcon icon = FlamingoUtilities
+ .getRibbonBandExpandIcon(this.ribbonBand);
+ JCommandButton result = new JCommandButton(null, icon);
+ result.setFlat(true);
+ result.putClientProperty(BasicCommandButtonUI.EMULATE_SQUARE_BUTTON,
+ Boolean.TRUE);
+ result.setBorder(new EmptyBorder(3, 2, 3, 2));
+ result.setActionKeyTip(this.ribbonBand.getExpandButtonKeyTip());
+ result.setActionRichTooltip(this.ribbonBand
+ .getExpandButtonRichTooltip());
+ return result;
+ }
+
+ protected void syncExpandButtonIcon() {
+ this.expandButton.setIcon(FlamingoUtilities
+ .getRibbonBandExpandIcon(this.ribbonBand));
+ }
+
+ /**
+ * Installs listeners on the associated ribbon band.
+ */
+ protected void installListeners() {
+ // without this empty adapter, the global listener never
+ // gets mouse entered events on the ribbon band
+ this.mouseListener = new MouseAdapter() {
+ };
+ this.ribbonBand.addMouseListener(this.mouseListener);
+
+ configureExpandButton();
+
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("title".equals(evt.getPropertyName())) {
+ collapsedButton.setText((String) evt.getNewValue());
+ ribbonBand.repaint();
+ } else if ("icon".equals(evt.getPropertyName())) {
+ collapsedButton.setIcon((ResizableIcon) evt.getNewValue());
+ ribbonBand.repaint();
+ } else if ("expandButtonKeyTip".equals(evt.getPropertyName())) {
+ if (expandButton != null) {
+ expandButton
+ .setActionKeyTip((String) evt.getNewValue());
+ }
+ } else if ("expandButtonRichTooltip".equals(evt.getPropertyName())) {
+ if (expandButton != null) {
+ expandButton.setActionRichTooltip((RichTooltip) evt
+ .getNewValue());
+ }
+ } else if ("collapsedStateKeyTip".equals(evt.getPropertyName())) {
+ if (collapsedButton != null) {
+ collapsedButton.setPopupKeyTip((String) evt
+ .getNewValue());
+ }
+ } else if ("expandActionListener".equals(evt.getPropertyName())) {
+ ActionListener oldListener = (ActionListener) evt
+ .getOldValue();
+ ActionListener newListener = (ActionListener) evt
+ .getNewValue();
+
+ if ((oldListener != null) && (newListener == null)) {
+ // need to remove
+ unconfigureExpandButton();
+ ribbonBand.remove(expandButton);
+ expandButton = null;
+ ribbonBand.revalidate();
+ } else if ((oldListener == null) && (newListener != null)) {
+ // need to add
+ expandButton = createExpandButton();
+ configureExpandButton();
+ ribbonBand.add(expandButton);
+ ribbonBand.revalidate();
+ } else if ((oldListener != null) && (newListener != null)) {
+ // need to reconfigure
+ expandButton.removeActionListener(oldListener);
+ expandButton.addActionListener(newListener);
+ }
+ } else if ("componentOrientation".equals(evt.getPropertyName())) {
+ if (expandButton != null) {
+ syncExpandButtonIcon();
+ }
+ }
+ }
+ };
+ this.ribbonBand.addPropertyChangeListener(this.propertyChangeListener);
+ }
+
+ protected void configureExpandButton() {
+ if (this.expandButton != null) {
+ this.expandButton.addActionListener(this.ribbonBand
+ .getExpandActionListener());
+
+ this.expandButtonActionListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ trackMouseCrossing(false);
+ }
+ });
+ }
+ };
+ this.expandButton
+ .addActionListener(this.expandButtonActionListener);
+ }
+ }
+
+ /**
+ * Uninstalls default parameters from the associated ribbon band.
+ */
+ protected void uninstallDefaults() {
+ LookAndFeel.uninstallBorder(this.ribbonBand);
+ }
+
+ /**
+ * Uninstalls components from the associated ribbon band.
+ */
+ protected void uninstallComponents() {
+ if (this.collapsedButton.isVisible()) {
+ // restore the control panel to the ribbon band.
+ CollapsedButtonPopupPanel popupPanel = (collapsedButton
+ .getPopupCallback() == null) ? null
+ : (CollapsedButtonPopupPanel) collapsedButton
+ .getPopupCallback().getPopupPanel(collapsedButton);
+ if (popupPanel != null) {
+ AbstractRibbonBand bandFromPopup = (AbstractRibbonBand) popupPanel
+ .removeComponent();
+ ribbonBand.setControlPanel(bandFromPopup.getControlPanel());
+ ribbonBand.setPopupRibbonBand(null);
+ collapsedButton.setPopupCallback(null);
+ }
+ }
+
+ this.ribbonBand.remove(this.collapsedButton);
+ this.collapsedButton = null;
+
+ if (this.expandButton != null)
+ this.ribbonBand.remove(this.expandButton);
+
+ this.expandButton = null;
+ this.ribbonBand = null;
+ }
+
+ /**
+ * Uninstalls listeners from the associated ribbon band.
+ */
+ protected void uninstallListeners() {
+ this.ribbonBand
+ .removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+
+ this.ribbonBand.removeMouseListener(this.mouseListener);
+ this.mouseListener = null;
+
+ unconfigureExpandButton();
+ }
+
+ protected void unconfigureExpandButton() {
+ if (this.expandButton != null) {
+ this.expandButton
+ .removeActionListener(this.expandButtonActionListener);
+ this.expandButtonActionListener = null;
+ this.expandButton.removeActionListener(this.ribbonBand
+ .getExpandActionListener());
+ }
+ }
+
+ /**
+ * Invoked by <code>installUI</code> to create a layout manager object to
+ * manage the {@link JCommandButtonStrip}.
+ *
+ * @return a layout manager object
+ */
+ protected LayoutManager createLayoutManager() {
+ return new RibbonBandLayout();
+ }
+
+ /**
+ * Layout for the ribbon band.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class RibbonBandLayout implements LayoutManager {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ Insets ins = c.getInsets();
+ AbstractBandControlPanel controlPanel = ribbonBand
+ .getControlPanel();
+ boolean useCollapsedButton = (controlPanel == null)
+ || (!controlPanel.isVisible());
+ int width = useCollapsedButton ? collapsedButton.getPreferredSize().width
+ : controlPanel.getPreferredSize().width;
+ int height = (useCollapsedButton ? collapsedButton
+ .getPreferredSize().height : controlPanel
+ .getPreferredSize().height)
+ + getBandTitleHeight();
+
+ // System.out.println(ribbonBand.getTitle() + ":" + height);
+
+ // Preferred height of the ribbon band is:
+ // 1. Insets on top and bottom
+ // 2. Preferred height of the control panel
+ // 3. Preferred height of the band title panel
+ // System.out.println("Ribbon band pref = "
+ // + (height + ins.top + ins.bottom));
+
+ return new Dimension(width + 2 + ins.left + ins.right, height
+ + ins.top + ins.bottom);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ Insets ins = c.getInsets();
+ AbstractBandControlPanel controlPanel = ribbonBand
+ .getControlPanel();
+ boolean useCollapsedButton = (controlPanel == null)
+ || (!controlPanel.isVisible());
+ int width = useCollapsedButton ? collapsedButton.getMinimumSize().width
+ : controlPanel.getMinimumSize().width;
+ int height = useCollapsedButton ? collapsedButton.getMinimumSize().height
+ + getBandTitleHeight()
+ : controlPanel.getMinimumSize().height
+ + getBandTitleHeight();
+
+ // System.out.println(useCollapsedButton + ":" + height);
+
+ // Preferred height of the ribbon band is:
+ // 1. Insets on top and bottom
+ // 2. Preferred height of the control panel
+ // 3. Preferred height of the band title panel
+ return new Dimension(width + 2 + ins.left + ins.right, height
+ + ins.top + ins.bottom);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ // System.out.println("Ribbon band real = " + c.getHeight());
+ if (!c.isVisible())
+ return;
+ Insets ins = c.getInsets();
+
+ int availableHeight = c.getHeight() - ins.top - ins.bottom;
+ RibbonBandResizePolicy resizePolicy = ((AbstractRibbonBand) c)
+ .getCurrentResizePolicy();
+
+ if (resizePolicy instanceof IconRibbonBandResizePolicy) {
+ collapsedButton.setVisible(true);
+ int w = collapsedButton.getPreferredSize().width;
+ // System.out.println("Width for collapsed of '"
+ // + ribbonBand.getTitle() + "' is " + w);
+ collapsedButton.setBounds((c.getWidth() - w) / 2, ins.top, w, c
+ .getHeight()
+ - ins.top - ins.bottom);
+
+ // System.out.println("Collapsed " + collapsedButton.getHeight()
+ // + ":" + c.getHeight());
+
+ if (collapsedButton.getPopupCallback() == null) {
+ final AbstractRibbonBand<AbstractBandControlPanel> popupBand = ribbonBand.cloneBand();
+ popupBand.setControlPanel(ribbonBand.getControlPanel());
+ List<RibbonBandResizePolicy> resizePolicies = ribbonBand
+ .getResizePolicies();
+ popupBand.setResizePolicies(resizePolicies);
+ RibbonBandResizePolicy largest = resizePolicies.get(0);
+ popupBand.setCurrentResizePolicy(largest);
+ int gap = popupBand.getControlPanel().getUI()
+ .getLayoutGap();
+ final Dimension size = new Dimension(ins.left + ins.right
+ + gap
+ + largest.getPreferredWidth(availableHeight, gap),
+ ins.top
+ + ins.bottom
+ + Math.max(c.getHeight(), ribbonBand
+ .getControlPanel()
+ .getPreferredSize().height
+ + getBandTitleHeight()));
+ // System.out.println(ribbonBand.getTitle() + ":"
+ // + size.getHeight());
+ collapsedButton.setPopupCallback(new PopupPanelCallback() {
+ @Override
+ public JPopupPanel getPopupPanel(
+ JCommandButton commandButton) {
+ return new CollapsedButtonPopupPanel(popupBand,
+ size);
+ }
+ });
+ // collapsedButton.setGallery(new JPopupGallery(ribbonBand
+ // .getControlPanel(), size));
+ ribbonBand.setControlPanel(null);
+ ribbonBand.setPopupRibbonBand(popupBand);
+ }
+
+ if (expandButton != null)
+ expandButton.setBounds(0, 0, 0, 0);
+
+ return;
+ }
+
+ if (collapsedButton.isVisible()) {
+ // was icon and now is normal band - have to restore the
+ // control panel
+ CollapsedButtonPopupPanel popupPanel = (collapsedButton
+ .getPopupCallback() != null) ? (CollapsedButtonPopupPanel) collapsedButton
+ .getPopupCallback().getPopupPanel(collapsedButton)
+ : null;
+ if (popupPanel != null) {
+ AbstractRibbonBand bandFromPopup = (AbstractRibbonBand) popupPanel
+ .removeComponent();
+ ribbonBand.setControlPanel(bandFromPopup.getControlPanel());
+ ribbonBand.setPopupRibbonBand(null);
+ collapsedButton.setPopupCallback(null);
+ }
+ }
+ collapsedButton.setVisible(false);
+
+ AbstractBandControlPanel controlPanel = ribbonBand
+ .getControlPanel();
+ controlPanel.setVisible(true);
+ controlPanel.setBounds(ins.left, ins.top, c.getWidth() - ins.left
+ - ins.right, c.getHeight() - getBandTitleHeight() - ins.top
+ - ins.bottom);
+ controlPanel.doLayout();
+
+ if (expandButton != null) {
+ int ebpw = expandButton.getPreferredSize().width;
+ int ebph = expandButton.getPreferredSize().height;
+ int maxHeight = getBandTitleHeight() - 4;
+ if (ebph > maxHeight)
+ ebph = maxHeight;
+
+ int expandButtonBottomY = c.getHeight()
+ - (getBandTitleHeight() - ebph) / 2;
+
+ boolean ltr = ribbonBand.getComponentOrientation()
+ .isLeftToRight();
+
+ if (ltr) {
+ expandButton.setBounds(c.getWidth() - ins.right - ebpw,
+ expandButtonBottomY - ebph, ebpw, ebph);
+ } else {
+ expandButton.setBounds(ins.left,
+ expandButtonBottomY - ebph, ebpw, ebph);
+ }
+ }
+ }
+ }
+
+ /**
+ * Event listener to handle global ribbon events. Currently handles:
+ * <ul>
+ * <li>Marking a ribbon band to be hovered when the mouse moves over it.</li>
+ * <li>Mouse wheel events anywhere in the ribbon to rotate the selected
+ * task.</li>
+ * </ul>
+ */
+ private static class AWTRibbonEventListener implements AWTEventListener {
+ private static AWTRibbonEventListener instance;
+ private int installCount = 0;
+ private AbstractRibbonBand lastHovered;
+
+ public static void install() {
+ if (instance == null) {
+ instance = new AWTRibbonEventListener();
+ java.security.AccessController
+ .doPrivileged(new java.security.PrivilegedAction<Object>() {
+ @Override
+ public Object run() {
+ Toolkit
+ .getDefaultToolkit()
+ .addAWTEventListener(
+ instance,
+ AWTEvent.MOUSE_EVENT_MASK
+ | AWTEvent.MOUSE_WHEEL_EVENT_MASK);
+ return null;
+ }
+ });
+ }
+ instance.installCount++;
+ }
+
+ public static boolean uninstall() {
+ if (instance != null) {
+ instance.installCount--;
+ if (instance.installCount == 0) {
+ // really uninstall
+ Toolkit.getDefaultToolkit()
+ .removeAWTEventListener(instance);
+ instance = null;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void eventDispatched(AWTEvent event) {
+ MouseEvent mouseEvent = (MouseEvent) event;
+ if (mouseEvent.getID() == MouseEvent.MOUSE_ENTERED) {
+ Object object = event.getSource();
+ if (!(object instanceof Component)) {
+ return;
+ }
+ Component component = (Component) object;
+ AbstractRibbonBand band = (component instanceof AbstractRibbonBand) ? ((AbstractRibbonBand) component)
+ : (AbstractRibbonBand) SwingUtilities
+ .getAncestorOfClass(AbstractRibbonBand.class,
+ component);
+ setHoveredBand(band);
+ }
+
+ if (mouseEvent.getID() == MouseEvent.MOUSE_WHEEL) {
+ if (PopupPanelManager.defaultManager().getShownPath().size() > 0)
+ return;
+
+ Object object = event.getSource();
+ if (!(object instanceof Component)) {
+ return;
+ }
+ Component component = (Component) object;
+ // get the deepest subcomponent at the event point
+ MouseWheelEvent mouseWheelEvent = (MouseWheelEvent) mouseEvent;
+ Component deepest = SwingUtilities.getDeepestComponentAt(
+ component, mouseWheelEvent.getX(), mouseWheelEvent
+ .getY());
+ JRibbon ribbon = (JRibbon) SwingUtilities.getAncestorOfClass(
+ JRibbon.class, deepest);
+ if (ribbon != null) {
+ // if the mouse wheel scroll has happened inside a ribbon,
+ // ask the UI delegate to handle it
+ ribbon.getUI().handleMouseWheelEvent(
+ (MouseWheelEvent) mouseEvent);
+ }
+ }
+ }
+
+ private void setHoveredBand(AbstractRibbonBand band) {
+ if (lastHovered == band) {
+ return; // nothing to do as we are already over
+ }
+ if (lastHovered != null) {
+ // RibbonBandUI ui = lastHovered.getUI();
+ lastHovered.getUI().trackMouseCrossing(false);
+ }
+ lastHovered = band;
+ if (band != null) {
+ band.getUI().trackMouseCrossing(true);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ RenderingUtils.installDesktopHints(g2d);
+ super.update(g2d, c);
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ Insets ins = ribbonBand.getInsets();
+
+ this.paintBandBackground(graphics, new Rectangle(0, 0, c.getWidth(), c
+ .getHeight()));
+
+ if (!(ribbonBand.getCurrentResizePolicy() instanceof IconRibbonBandResizePolicy)) {
+ String title = ribbonBand.getTitle();
+ int titleHeight = getBandTitleHeight();
+
+ int bandTitleTopY = c.getHeight() - titleHeight;
+
+ this.paintBandTitleBackground(graphics, new Rectangle(0,
+ bandTitleTopY, c.getWidth(), titleHeight), title);
+ boolean ltr = ribbonBand.getComponentOrientation().isLeftToRight();
+ int titleWidth = c.getWidth() - 2 * ins.left - 2 * ins.right;
+ int titleX = 2 * ins.left;
+ if (expandButton != null) {
+ if (ltr) {
+ titleWidth = expandButton.getX() - 2 * ins.right - 2
+ * ins.left;
+ } else {
+ titleWidth = ribbonBand.getWidth() - expandButton.getX()
+ - expandButton.getWidth() - 2 * ins.right - 2
+ * ins.left;
+ titleX = expandButton.getX() + expandButton.getWidth() + 2
+ * ins.left;
+ }
+ }
+ this.paintBandTitle(graphics, new Rectangle(titleX, bandTitleTopY,
+ titleWidth, titleHeight), title);
+ }
+
+ graphics.dispose();
+ }
+
+ /**
+ * Paints band title pane.
+ *
+ * @param g
+ * Graphics context.
+ * @param titleRectangle
+ * Rectangle for the title pane.
+ * @param title
+ * Title string.
+ */
+ protected void paintBandTitle(Graphics g, Rectangle titleRectangle,
+ String title) {
+ // fix for issue 10 - empty ribbon band
+ if (titleRectangle.width <= 0)
+ return;
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setFont(FlamingoUtilities.getFont(this.ribbonBand,
+ "Ribbon.font", "Button.font", "Panel.font"));
+
+ FontMetrics fm = graphics.getFontMetrics();
+ int y = titleRectangle.y - 2 + (titleRectangle.height + fm.getAscent())
+ / 2;
+
+ int currLength = (int) fm.getStringBounds(title, g).getWidth();
+ String titleToPaint = title;
+ while (currLength > titleRectangle.width) {
+ title = title.substring(0, title.length() - 1);
+ titleToPaint = title + "...";
+ currLength = (int) fm.getStringBounds(titleToPaint, g).getWidth();
+ }
+
+ int x = titleRectangle.x + (titleRectangle.width - currLength) / 2;
+ graphics.setColor(this.ribbonBand.getForeground());
+ graphics.drawString(titleToPaint, x, y);
+ graphics.dispose();
+ }
+
+ /**
+ * Paints band title pane.
+ *
+ * @param g
+ * Graphics context.
+ * @param titleRectangle
+ * Rectangle for the title pane.
+ * @param title
+ * Title string.
+ */
+ protected void paintBandTitleBackground(Graphics g,
+ Rectangle titleRectangle, String title) {
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(0.7f + 0.3f * this.rolloverAmount));
+
+ FlamingoUtilities.renderSurface(g2d, this.ribbonBand, titleRectangle,
+ this.rolloverAmount > 0.0f, true, false);
+
+ g2d.dispose();
+ }
+
+ public void setRolloverAmount(float rolloverAmount) {
+ this.rolloverAmount = rolloverAmount;
+ }
+
+ /**
+ * Paints band background.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param toFill
+ * Rectangle for the background.
+ */
+ protected void paintBandBackground(Graphics graphics, Rectangle toFill) {
+ graphics.setColor(ribbonBand.getBackground());
+ graphics.fillRect(toFill.x, toFill.y, toFill.width, toFill.height);
+ }
+
+ @Override
+ public float getRolloverAmount() {
+ return this.rolloverAmount;
+ }
+
+ /**
+ * Returns the height of the ribbon band title area.
+ *
+ * @return The height of the ribbon band title area.
+ */
+ @Override
+ public int getBandTitleHeight() {
+ Font font = FlamingoUtilities.getFont(this.ribbonBand, "Ribbon.font",
+ "Button.font", "Panel.font");
+ if (font == null) {
+ // Nimbus - is that you?
+ font = new JLabel().getFont();
+ }
+ int result = font.getSize() + 5;
+ if (result % 2 == 0)
+ result++;
+ return result;
+ }
+
+ /**
+ * Round border for the ribbon bands.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected static class RoundBorder implements Border {
+ /**
+ * Border color.
+ */
+ protected Color color;
+
+ /**
+ * Border insets.
+ */
+ protected Insets insets;
+
+ /**
+ * Creates the new border.
+ *
+ * @param color
+ * Border color.
+ * @param insets
+ * Border insets.
+ */
+ public RoundBorder(Color color, Insets insets) {
+ this.color = color;
+ this.insets = insets;
+ }
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return this.insets;
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setColor(this.color);
+ g2d.drawRoundRect(x, y, width - 1, height - 1, 3, 3);
+ g2d.dispose();
+ }
+
+ }
+
+ @Override
+ public int getPreferredCollapsedWidth() {
+ return this.collapsedButton.getPreferredSize().width + 2;
+ }
+
+ @Override
+ public void trackMouseCrossing(boolean isMouseIn) {
+ if (isMouseIn) {
+ this.rolloverTimeline.play();
+ } else {
+ this.rolloverTimeline.playReverse();
+ }
+ this.ribbonBand.repaint();
+ }
+
+ /**
+ * This method is for unit tests only and should not be called by the
+ * application code.
+ *
+ * @return The expand button of the matching ribbon band.
+ */
+ @Deprecated
+ public AbstractCommandButton getExpandButton() {
+ return this.expandButton;
+ }
+
+ /**
+ * This method is for unit tests only and should not be called by the
+ * application code.
+ *
+ * @return The expand button of the matching ribbon band.
+ */
+ @Deprecated
+ public AbstractCommandButton getCollapsedButton() {
+ return this.collapsedButton;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonComponentUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonComponentUI.java
new file mode 100644
index 0000000..5cf93af
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonComponentUI.java
@@ -0,0 +1,457 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorConvertOp;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.HorizontalAlignment;
+import org.pushingpixels.flamingo.api.common.icon.FilteredResizableIcon;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.ribbon.JRibbonComponent;
+import org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority;
+
+public class BasicRibbonComponentUI extends RibbonComponentUI {
+ /**
+ * The associated ribbon component.
+ */
+ protected JRibbonComponent ribbonComponent;
+
+ protected JLabel captionLabel;
+
+ protected PropertyChangeListener propertyChangeListener;
+
+ protected ResizableIcon disabledIcon;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicRibbonComponentUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.ribbonComponent = (JRibbonComponent) c;
+ installDefaults();
+ installComponents();
+ installListeners();
+ c.setLayout(createLayoutManager());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ c.setLayout(null);
+ uninstallListeners();
+ uninstallDefaults();
+ uninstallComponents();
+ }
+
+ /**
+ * Installs default parameters on the associated ribbon component.
+ */
+ protected void installDefaults() {
+ if (!this.ribbonComponent.isSimpleWrapper()) {
+ ResizableIcon icon = this.ribbonComponent.getIcon();
+ if (icon != null) {
+ icon.setDimension(new Dimension(16, 16));
+ this.disabledIcon = createDisabledIcon();
+ }
+ }
+
+ this.ribbonComponent.getMainComponent().setOpaque(false);
+ this.ribbonComponent.setOpaque(false);
+ }
+
+ /**
+ * Installs subcomponents on the associated ribbon component.
+ */
+ protected void installComponents() {
+ this.captionLabel = new JLabel(this.ribbonComponent.getCaption());
+ this.captionLabel.setEnabled(this.ribbonComponent.isEnabled());
+ this.ribbonComponent.add(this.captionLabel);
+
+ JComponent mainComponent = this.ribbonComponent.getMainComponent();
+ this.ribbonComponent.add(mainComponent);
+ }
+
+ /**
+ * Installs listeners on the associated ribbon component.
+ */
+ protected void installListeners() {
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("enabled".equals(evt.getPropertyName())) {
+ boolean isEnabled = (Boolean) evt.getNewValue();
+ ribbonComponent.getMainComponent().setEnabled(isEnabled);
+ if (!ribbonComponent.isSimpleWrapper()) {
+ captionLabel.setEnabled(isEnabled);
+ }
+ ribbonComponent.repaint();
+ }
+ if ("caption".equals(evt.getPropertyName())) {
+ captionLabel.setText((String) evt.getNewValue());
+ }
+ if ("displayPriority".equals(evt.getPropertyName())) {
+ ribbonComponent.revalidate();
+ ribbonComponent.doLayout();
+ }
+ }
+ };
+ this.ribbonComponent
+ .addPropertyChangeListener(this.propertyChangeListener);
+
+ }
+
+ /**
+ * Uninstalls default parameters from the associated ribbon component.
+ */
+ protected void uninstallDefaults() {
+ }
+
+ /**
+ * Uninstalls components from the associated ribbon component.
+ */
+ protected void uninstallComponents() {
+ JComponent mainComponent = this.ribbonComponent.getMainComponent();
+ this.ribbonComponent.remove(mainComponent);
+
+ this.ribbonComponent.remove(this.captionLabel);
+ this.captionLabel = null;
+ }
+
+ /**
+ * Uninstalls listeners from the associated ribbon component.
+ */
+ protected void uninstallListeners() {
+ this.ribbonComponent
+ .removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+ }
+
+ @Override
+ public Point getKeyTipAnchorCenterPoint() {
+ if (this.ribbonComponent.isSimpleWrapper()) {
+ return new Point(
+ this.ribbonComponent.getMainComponent().getX() + 10,
+ this.ribbonComponent.getHeight());
+ } else {
+ return new Point(this.captionLabel.getX(), this.ribbonComponent
+ .getHeight());
+ }
+ }
+
+ protected LayoutManager createLayoutManager() {
+ return new ExtComponentLayout();
+ }
+
+ protected class ExtComponentLayout implements LayoutManager {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ Insets ins = ribbonComponent.getInsets();
+ JComponent mainComponent = ribbonComponent.getMainComponent();
+ Dimension minMain = mainComponent.getMinimumSize();
+
+ int width = ins.left;
+ int height = minMain.height;
+ if (isIconVisible(ribbonComponent.getDisplayPriority())) {
+ ResizableIcon icon = ribbonComponent.getIcon();
+ if (icon != null) {
+ width += (icon.getIconWidth() + getLayoutGap());
+ height = Math.max(height, icon.getIconHeight());
+ }
+ }
+ if (isCaptionVisible(ribbonComponent.getDisplayPriority())) {
+ Dimension minCaption = captionLabel.getMinimumSize();
+ width += (minCaption.width + getLayoutGap());
+ height = Math.max(height, minCaption.height);
+ }
+ width += minMain.width;
+ width += ins.right;
+ height += (ins.top + ins.bottom);
+
+ return new Dimension(width, height);
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ return getPreferredSize(ribbonComponent.getDisplayPriority());
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ JRibbonComponent ribbonComp = (JRibbonComponent) parent;
+ Insets ins = ribbonComp.getInsets();
+ int gap = getLayoutGap();
+ int availableHeight = ribbonComp.getHeight() - ins.top - ins.bottom;
+ int availableWidth = ribbonComp.getWidth() - ins.left - ins.right;
+
+ HorizontalAlignment horizAlignment = ribbonComp
+ .getHorizontalAlignment();
+ JComponent mainComp = ribbonComp.getMainComponent();
+ Dimension prefMainDim = mainComp.getPreferredSize();
+ int prefMainWidth = prefMainDim.width;
+ int finalHeight = Math.min(prefMainDim.height, availableHeight);
+ boolean ltr = ribbonComp.getComponentOrientation().isLeftToRight();
+
+ if (ribbonComp.isSimpleWrapper()) {
+ int finalMainWidth = Math.min(availableWidth, prefMainWidth);
+ int offset = availableWidth - prefMainWidth;
+ int topMain = ins.top + (availableHeight - finalHeight) / 2;
+ int x = ltr ? ins.left : ribbonComp.getWidth() - ins.right;
+ switch (horizAlignment) {
+ case LEADING:
+ if (ltr) {
+ mainComp.setBounds(x, topMain, finalMainWidth,
+ finalHeight);
+ } else {
+ mainComp.setBounds(x - finalMainWidth, topMain,
+ finalMainWidth, finalHeight);
+ }
+ break;
+ case TRAILING:
+ if (ltr) {
+ mainComp.setBounds(x + offset, topMain, finalMainWidth,
+ finalHeight);
+ } else {
+ mainComp.setBounds(x - finalMainWidth - offset,
+ topMain, finalMainWidth, finalHeight);
+ }
+ break;
+ case CENTER:
+ if (ltr) {
+ mainComp.setBounds(x + offset / 2, topMain,
+ finalMainWidth, finalHeight);
+ } else {
+ mainComp.setBounds(x - finalMainWidth - offset / 2,
+ topMain, finalMainWidth, finalHeight);
+ }
+ break;
+ case FILL:
+ if (ltr) {
+ mainComp.setBounds(x, topMain, availableWidth,
+ finalHeight);
+ } else {
+ mainComp.setBounds(x - availableWidth, topMain,
+ availableWidth, finalHeight);
+ }
+ break;
+ }
+ mainComp.doLayout();
+ } else {
+ int x = ltr ? ins.left : ribbonComp.getWidth() - ins.right;
+ if (isIconVisible(ribbonComponent.getDisplayPriority())) {
+ if (ribbonComp.getIcon() != null) {
+ // icon is painted separately
+ int iconW = ribbonComp.getIcon().getIconWidth();
+ x = ltr ? x + iconW + gap : x - iconW - gap;
+ }
+ }
+
+ if (isCaptionVisible(ribbonComponent.getDisplayPriority())) {
+ captionLabel.setVisible(true);
+ Dimension prefCaptionDim = captionLabel.getPreferredSize();
+ if (ltr) {
+ captionLabel
+ .setBounds(
+ x,
+ ins.top
+ + (availableHeight - prefCaptionDim.height)
+ / 2, prefCaptionDim.width,
+ prefCaptionDim.height);
+ x += (prefCaptionDim.width + gap);
+ } else {
+ captionLabel
+ .setBounds(
+ x - prefCaptionDim.width,
+ ins.top
+ + (availableHeight - prefCaptionDim.height)
+ / 2, prefCaptionDim.width,
+ prefCaptionDim.height);
+ x -= (prefCaptionDim.width + gap);
+ }
+ } else {
+ captionLabel.setVisible(false);
+ }
+
+ int topMain = ins.top + (availableHeight - finalHeight) / 2;
+ int finalMainWidth = ltr ? Math.min(ribbonComp.getWidth()
+ - ins.right - x, prefMainWidth) : Math.min(
+ x - ins.left, prefMainWidth);
+ int offset = ltr ? ribbonComp.getWidth() - ins.right - x
+ - prefMainWidth : x - prefMainWidth - ins.left;
+
+ switch (horizAlignment) {
+ case LEADING:
+ if (ltr) {
+ mainComp.setBounds(x, topMain, finalMainWidth,
+ finalHeight);
+ } else {
+ mainComp.setBounds(x - finalMainWidth, topMain,
+ finalMainWidth, finalHeight);
+ }
+ break;
+ case TRAILING:
+ if (ltr) {
+ mainComp.setBounds(x + offset, topMain, finalMainWidth,
+ finalHeight);
+ } else {
+ mainComp.setBounds(x - finalMainWidth - offset,
+ topMain, finalMainWidth, finalHeight);
+ }
+ break;
+ case CENTER:
+ if (ltr) {
+ mainComp.setBounds(x + offset / 2, topMain,
+ finalMainWidth, finalHeight);
+ } else {
+ mainComp.setBounds(x - finalMainWidth - offset / 2,
+ topMain, finalMainWidth, finalHeight);
+ }
+ break;
+ case FILL:
+ if (ltr) {
+ mainComp.setBounds(x, topMain, ribbonComp.getWidth()
+ - ins.right - x, finalHeight);
+ } else {
+ mainComp.setBounds(ins.left, topMain, x - ins.left,
+ finalHeight);
+ }
+ break;
+ }
+ mainComp.doLayout();
+ }
+ }
+ }
+
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ JRibbonComponent ribbonComp = (JRibbonComponent) c;
+ if (isIconVisible(this.ribbonComponent.getDisplayPriority())) {
+ Insets ins = ribbonComp.getInsets();
+ ResizableIcon icon = ribbonComp.isEnabled() ? ribbonComp.getIcon()
+ : this.disabledIcon;
+ if (icon != null) {
+ int availableHeight = ribbonComp.getHeight() - ins.top
+ - ins.bottom;
+ int iconY = Math.max(0, ins.top
+ + (availableHeight - icon.getIconHeight()) / 2);
+ if (ribbonComp.getComponentOrientation().isLeftToRight()) {
+ paintIcon(g, ribbonComp, icon, ins.left, iconY);
+ } else {
+ paintIcon(g, ribbonComp, icon, ribbonComp.getWidth()
+ - ins.right - icon.getIconWidth(), iconY);
+ }
+ }
+ }
+ }
+
+ protected void paintIcon(Graphics g, JRibbonComponent ribbonComp,
+ Icon icon, int x, int y) {
+ icon.paintIcon(ribbonComp, g, x, y);
+ }
+
+ protected int getLayoutGap() {
+ return 4;
+ }
+
+ protected ResizableIcon createDisabledIcon() {
+ return new FilteredResizableIcon(this.ribbonComponent.getIcon(),
+ new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY),
+ null));
+ }
+
+ protected boolean isIconVisible(RibbonElementPriority displayPriority) {
+ if (this.ribbonComponent.isSimpleWrapper())
+ return false;
+ return (displayPriority == RibbonElementPriority.TOP)
+ || (displayPriority == RibbonElementPriority.MEDIUM);
+ }
+
+ protected boolean isCaptionVisible(RibbonElementPriority displayPriority) {
+ if (this.ribbonComponent.isSimpleWrapper())
+ return false;
+ return (displayPriority == RibbonElementPriority.TOP);
+ }
+
+ @Override
+ public Dimension getPreferredSize(RibbonElementPriority priority) {
+ Insets ins = ribbonComponent.getInsets();
+ JComponent mainComponent = ribbonComponent.getMainComponent();
+ Dimension prefMain = mainComponent.getPreferredSize();
+
+ int width = ins.left;
+ int height = prefMain.height;
+ if (isIconVisible(priority)) {
+ ResizableIcon icon = ribbonComponent.getIcon();
+ if (icon != null) {
+ width += (icon.getIconWidth() + getLayoutGap());
+ height = Math.max(height, icon.getIconHeight());
+ }
+ }
+ if (isCaptionVisible(priority)) {
+ Dimension prefCaption = captionLabel.getPreferredSize();
+ width += (prefCaption.width + getLayoutGap());
+ height = Math.max(height, prefCaption.height);
+ }
+ width += prefMain.width;
+ width += ins.right;
+ height += (ins.top + ins.bottom);
+
+ return new Dimension(width, height);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonGalleryUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonGalleryUI.java
new file mode 100644
index 0000000..60f29e9
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonGalleryUI.java
@@ -0,0 +1,865 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.JCommandButtonStrip.StripOrientation;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.popup.*;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager.PopupEvent;
+import org.pushingpixels.flamingo.api.ribbon.JRibbonBand;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonUI;
+import org.pushingpixels.flamingo.internal.utils.*;
+
+/**
+ * Basic UI for ribbon gallery {@link JRibbonGallery}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicRibbonGalleryUI extends RibbonGalleryUI {
+ /**
+ * The associated ribbon gallery.
+ */
+ protected JRibbonGallery ribbonGallery;
+
+ /**
+ * The index of the first visible button.
+ */
+ protected int firstVisibleButtonIndex;
+
+ /**
+ * The count of visible buttons.
+ */
+ protected int visibleButtonsInEachRow;
+
+ protected int visibleButtonRowNumber;
+
+ /**
+ * The button that scrolls down the associated {@link #ribbonGallery}.
+ */
+ protected JCommandButton scrollDownButton;
+
+ /**
+ * The button that scrolls up the associated {@link #ribbonGallery}.
+ */
+ protected JCommandButton scrollUpButton;
+
+ /**
+ * The button that shows the associated popup gallery.
+ */
+ protected ExpandCommandButton expandActionButton;
+
+ /**
+ * Contains the scroll down, scroll up and show popup buttons.
+ *
+ * @see #scrollDownButton
+ * @see #scrollUpButton
+ * @see #expandActionButton
+ */
+ protected JCommandButtonStrip buttonStrip;
+
+ /**
+ * Listener on the gallery scroll-down button.
+ */
+ protected ActionListener scrollDownListener;
+
+ /**
+ * Listener on the gallery scroll-up button.
+ */
+ protected ActionListener scrollUpListener;
+
+ /**
+ * Listener on the gallery expand button.
+ */
+ protected ActionListener expandListener;
+
+ /**
+ * Listener on the {@link PopupPanelManager} changes to sync the
+ * {@link JRibbonGallery#setShowingPopupPanel(boolean)} once the popup
+ * gallery is dismissed by the user.
+ */
+ protected PopupPanelManager.PopupListener popupListener;
+
+ /**
+ * Property change listener.
+ */
+ protected PropertyChangeListener propertyChangeListener;
+
+ /**
+ * Ribbon gallery margin.
+ */
+ protected Insets margin;
+
+ /**
+ * Button strip as a UI resource.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected static class JButtonStripUIResource extends JCommandButtonStrip
+ implements UIResource {
+
+ /**
+ * Creates a new UI-resource button strip.
+ */
+ public JButtonStripUIResource() {
+ super();
+ }
+
+ /**
+ * Creates a new UI-resource button strip.
+ *
+ * @param orientation
+ * Orientation for this strip.
+ */
+ public JButtonStripUIResource(StripOrientation orientation) {
+ super(orientation);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicRibbonGalleryUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.ribbonGallery = (JRibbonGallery) c;
+ this.firstVisibleButtonIndex = 0;
+ // this.visibleButtonsCount = 0;
+
+ this.installDefaults();
+ this.installComponents();
+ this.installListeners();
+
+ c.setLayout(createLayoutManager());
+ }
+
+ /**
+ * Installs subcomponents on the associated ribbon gallery.
+ */
+ protected void installComponents() {
+ this.buttonStrip = new JButtonStripUIResource(StripOrientation.VERTICAL);
+ this.buttonStrip.setDisplayState(CommandButtonDisplayState.FIT_TO_ICON);
+ this.ribbonGallery.add(this.buttonStrip);
+
+ this.scrollUpButton = this.createScrollUpButton();
+ this.scrollDownButton = this.createScrollDownButton();
+ this.expandActionButton = this.createExpandButton();
+ this.syncExpandKeyTip();
+
+ this.buttonStrip.add(this.scrollUpButton);
+ this.buttonStrip.add(this.scrollDownButton);
+ this.buttonStrip.add(this.expandActionButton);
+ }
+
+ /**
+ * Creates the scroll-down button.
+ *
+ * @return Scroll-down button.
+ */
+ protected JCommandButton createScrollDownButton() {
+ JCommandButton result = new JCommandButton(new ArrowResizableIcon(9,
+ SwingConstants.SOUTH));
+ result.setFocusable(false);
+ result.setName("RibbonGallery.scrollDownButton");
+ result.setFlat(false);
+ result.putClientProperty(BasicCommandButtonUI.DONT_DISPOSE_POPUPS,
+ Boolean.TRUE);
+ result.setAutoRepeatAction(true);
+ return result;
+ }
+
+ /**
+ * Creates the scroll-up button.
+ *
+ * @return Scroll-up button.
+ */
+ protected JCommandButton createScrollUpButton() {
+ JCommandButton result = new JCommandButton(new ArrowResizableIcon(9,
+ SwingConstants.NORTH));
+ result.setFocusable(false);
+ result.setName("RibbonGallery.scrollUpButton");
+ result.setFlat(false);
+ result.putClientProperty(BasicCommandButtonUI.DONT_DISPOSE_POPUPS,
+ Boolean.TRUE);
+ result.setAutoRepeatAction(true);
+ return result;
+ }
+
+ /**
+ * Creates the expand button.
+ *
+ * @return Expand button.
+ */
+ protected ExpandCommandButton createExpandButton() {
+ ExpandCommandButton result = new ExpandCommandButton(
+ new DoubleArrowResizableIcon(9, SwingConstants.SOUTH));
+ result.getActionModel().setFireActionOnPress(true);
+ result.setFocusable(false);
+ result.setName("RibbonGallery.expandButton");
+ result.setFlat(false);
+ result.putClientProperty(BasicCommandButtonUI.DONT_DISPOSE_POPUPS,
+ Boolean.TRUE);
+ return result;
+ }
+
+ /**
+ * Uninstalls subcomponents from the associated ribbon gallery.
+ */
+ protected void uninstallComponents() {
+ this.buttonStrip.remove(this.scrollUpButton);
+ this.buttonStrip.remove(this.scrollDownButton);
+ this.buttonStrip.remove(this.expandActionButton);
+ this.ribbonGallery.remove(this.buttonStrip);
+ }
+
+ /**
+ * Installs defaults on the associated ribbon gallery.
+ */
+ protected void installDefaults() {
+ this.margin = UIManager.getInsets("RibbonGallery.margin");
+ if (this.margin == null)
+ this.margin = new Insets(3, 3, 3, 3);
+ Border b = this.ribbonGallery.getBorder();
+ if (b == null || b instanceof UIResource) {
+ Border toSet = UIManager.getBorder("RibbonGallery.border");
+ if (toSet == null)
+ toSet = new BorderUIResource.EmptyBorderUIResource(2, 2, 2, 2);
+ this.ribbonGallery.setBorder(toSet);
+ }
+ this.ribbonGallery.setOpaque(false);
+ }
+
+ /**
+ * Uninstalls defaults from the associated ribbon gallery.
+ */
+ protected void uninstallDefaults() {
+ }
+
+ /**
+ * Installs listeners on the associated ribbon gallery.
+ */
+ protected void installListeners() {
+ this.scrollDownListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ scrollOneRowDown();
+ ribbonGallery.revalidate();
+ }
+ };
+
+ this.scrollDownButton.addActionListener(this.scrollDownListener);
+
+ this.scrollUpListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ scrollOneRowUp();
+ ribbonGallery.revalidate();
+ }
+ };
+ this.scrollUpButton.addActionListener(this.scrollUpListener);
+
+ this.expandListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ PopupPanelManager.defaultManager().hidePopups(ribbonGallery);
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ PopupFactory popupFactory = PopupFactory
+ .getSharedInstance();
+
+ JCommandButtonPanel popupButtonPanel = ribbonGallery
+ .getPopupButtonPanel();
+
+ final Point loc = ribbonGallery.getLocationOnScreen();
+ final JCommandPopupMenu popupMenu = new JCommandPopupMenu(
+ popupButtonPanel,
+ ribbonGallery
+ .getPreferredPopupMaxButtonColumns(),
+ ribbonGallery
+ .getPreferredPopupMaxVisibleButtonRows());
+
+ if (ribbonGallery.getPopupCallback() != null) {
+ ribbonGallery.getPopupCallback().popupToBeShown(
+ popupMenu);
+ }
+ popupMenu.applyComponentOrientation(ribbonGallery
+ .getComponentOrientation());
+
+ popupMenu
+ .setCustomizer(new JPopupPanel.PopupPanelCustomizer() {
+ @Override
+ public Rectangle getScreenBounds() {
+ Rectangle scrBounds = ribbonGallery
+ .getGraphicsConfiguration()
+ .getBounds();
+
+ boolean ltr = popupMenu
+ .getComponentOrientation()
+ .isLeftToRight();
+
+ Dimension pref = popupMenu
+ .getPreferredSize();
+ int width = Math.max(pref.width,
+ ribbonGallery.getWidth());
+ int height = pref.height;
+
+ int x = ltr ? loc.x : loc.x + width
+ - pref.width;
+ int y = loc.y;
+
+ // make sure that the popup stays in
+ // bounds
+ if ((x + width) > (scrBounds.x + scrBounds.width)) {
+ x = scrBounds.x + scrBounds.width
+ - width;
+ }
+ if ((y + height) > (scrBounds.y + scrBounds.height)) {
+ y = scrBounds.y + scrBounds.height
+ - height;
+ }
+
+ return new Rectangle(x, y, width,
+ height);
+ }
+ });
+
+ // mark the gallery so that it doesn't try to re-layout
+ // itself.
+ ribbonGallery.setShowingPopupPanel(true);
+
+ // get the popup and show it
+ Dimension pref = popupMenu.getPreferredSize();
+ int width = Math.max(pref.width, ribbonGallery
+ .getWidth());
+
+ boolean ltr = ribbonGallery.getComponentOrientation()
+ .isLeftToRight();
+ int x = ltr ? loc.x : loc.x + ribbonGallery.getWidth()
+ - width;
+ Popup popup = popupFactory.getPopup(ribbonGallery,
+ popupMenu, x, loc.y);
+ ribbonGallery.repaint();
+ PopupPanelManager.defaultManager().addPopup(
+ ribbonGallery, popup, popupMenu);
+
+ // scroll to reveal the selected button
+ if (popupButtonPanel.getSelectedButton() != null) {
+ Rectangle selectionButtonBounds = popupButtonPanel
+ .getSelectedButton().getBounds();
+ popupButtonPanel
+ .scrollRectToVisible(selectionButtonBounds);
+ }
+ }
+ });
+ }
+ };
+
+ this.expandActionButton.addActionListener(this.expandListener);
+
+ this.popupListener = new PopupPanelManager.PopupListener() {
+ @Override
+ public void popupHidden(PopupEvent event) {
+ if (event.getPopupOriginator() == ribbonGallery) {
+ // reset the rollover state for all the buttons
+ // in the gallery
+ for (int i = 0; i < ribbonGallery.getButtonCount(); i++) {
+ ribbonGallery.getButtonAt(i).getActionModel()
+ .setRollover(false);
+ }
+ ribbonGallery.setShowingPopupPanel(false);
+ }
+ }
+
+ @Override
+ public void popupShown(PopupEvent event) {
+ }
+ };
+ PopupPanelManager.defaultManager().addPopupListener(this.popupListener);
+
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("selectedButton".equals(evt.getPropertyName())) {
+ scrollToSelected();
+ ribbonGallery.revalidate();
+ }
+ if ("expandKeyTip".equals(evt.getPropertyName())) {
+ syncExpandKeyTip();
+ }
+ if ("buttonDisplayState".equals(evt.getPropertyName())) {
+ firstVisibleButtonIndex = 0;
+ ribbonGallery.revalidate();
+ }
+ }
+ };
+ this.ribbonGallery
+ .addPropertyChangeListener(this.propertyChangeListener);
+ }
+
+ /**
+ * Uninstalls listeners from the associated ribbon gallery.
+ */
+ protected void uninstallListeners() {
+ this.scrollDownButton.removeActionListener(this.scrollDownListener);
+ this.scrollDownListener = null;
+
+ this.scrollUpButton.removeActionListener(this.scrollUpListener);
+ this.scrollUpListener = null;
+
+ this.expandActionButton.removeActionListener(this.expandListener);
+ this.expandListener = null;
+
+ PopupPanelManager.defaultManager().removePopupListener(
+ this.popupListener);
+ this.popupListener = null;
+
+ this.ribbonGallery
+ .removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ c.setLayout(null);
+
+ this.uninstallListeners();
+ this.uninstallDefaults();
+ this.uninstallComponents();
+
+ this.ribbonGallery = null;
+ }
+
+ /**
+ * Invoked by <code>installUI</code> to create a layout manager object to
+ * manage the {@link JCommandButtonStrip}.
+ *
+ * @return a layout manager object
+ */
+ protected LayoutManager createLayoutManager() {
+ return new RibbonGalleryLayout();
+ }
+
+ /**
+ * Layout for the ribbon gallery.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class RibbonGalleryLayout implements LayoutManager {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ return new Dimension(ribbonGallery.getPreferredWidth(ribbonGallery
+ .getDisplayPriority(), c.getHeight()), c.getHeight());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ return this.preferredLayoutSize(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ int width = c.getWidth();
+ int height = c.getHeight();
+
+ Insets borderInsets = ribbonGallery.getInsets();
+
+ int galleryHeight = height - margin.top - margin.bottom;
+ int buttonHeight = galleryHeight - borderInsets.top
+ - borderInsets.bottom;
+
+ visibleButtonRowNumber = 1;
+ CommandButtonDisplayState galleryButtonDisplayState = ribbonGallery
+ .getButtonDisplayState();
+ if (galleryButtonDisplayState == CommandButtonDisplayState.SMALL) {
+ buttonHeight /= 3;
+ visibleButtonRowNumber = 3;
+ }
+
+ boolean ltr = c.getComponentOrientation().isLeftToRight();
+ int scrollerButtonHeight = galleryHeight / 3;
+ int scrollerButtonWidth = 15;
+ int buttonX = ltr ? width - scrollerButtonWidth - margin.right
+ : margin.left;
+
+ scrollDownButton.setPreferredSize(new Dimension(
+ scrollerButtonWidth, scrollerButtonHeight));
+ scrollUpButton.setPreferredSize(new Dimension(scrollerButtonWidth,
+ scrollerButtonHeight));
+ // special case (if available height doesn't divide 3)
+ expandActionButton.setPreferredSize(new Dimension(
+ scrollerButtonWidth, galleryHeight - 2
+ * scrollerButtonHeight));
+ buttonStrip.setBounds(buttonX, margin.top, scrollerButtonWidth,
+ galleryHeight);
+ buttonStrip.doLayout();
+
+ if (!ribbonGallery.isShowingPopupPanel()) {
+ // hide all buttons and compute the button width
+ int maxButtonWidth = buttonHeight;
+ if (galleryButtonDisplayState == JRibbonBand.BIG_FIXED_LANDSCAPE) {
+ maxButtonWidth = maxButtonWidth * 5 / 4;
+ }
+ for (int i = 0; i < ribbonGallery.getButtonCount(); i++) {
+ JCommandToggleButton currButton = ribbonGallery
+ .getButtonAt(i);
+ currButton.setVisible(false);
+ }
+
+ int gap = getLayoutGap();
+
+ // compute how many buttons can fit in each row
+ visibleButtonsInEachRow = 0;
+ int availableButtonsSpace = ltr ? buttonX - margin.left : width
+ - buttonX - scrollerButtonWidth - margin.right;
+ while (true) {
+ // gap on the left, gap in between every two adjacent
+ // buttons and gap on the right
+ int neededSpace = visibleButtonsInEachRow * maxButtonWidth
+ + (visibleButtonsInEachRow + 1) * gap;
+ if (neededSpace > availableButtonsSpace) {
+ visibleButtonsInEachRow--;
+ break;
+ }
+ visibleButtonsInEachRow++;
+ }
+
+ // System.out.println("Visible in each row " +
+ // visibleButtonsInEachRow);
+
+ // compute how many pixels we can distribute among the visible
+ // buttons
+ int neededSpace = visibleButtonsInEachRow * maxButtonWidth
+ + (visibleButtonsInEachRow + 1) * gap;
+ int startX = ltr ? margin.left + gap : width - margin.right
+ - gap;
+ int availableWidth = ltr ? buttonX - margin.right : width
+ - buttonX - scrollerButtonWidth - margin.left;
+ int toAddToButtonWidth = (availableWidth - neededSpace)
+ / visibleButtonsInEachRow;
+
+ // compute how many buttons can fit in the available horizontal
+ // space
+ int lastVisibleButtonIndex = firstVisibleButtonIndex
+ + visibleButtonRowNumber * visibleButtonsInEachRow - 1;
+ lastVisibleButtonIndex = Math.min(lastVisibleButtonIndex,
+ ribbonGallery.getButtonCount() - 1);
+ int currCountInRow = 0;
+ int buttonY = margin.top + borderInsets.top;
+ int singleButtonWidth = maxButtonWidth + toAddToButtonWidth;
+ for (int i = firstVisibleButtonIndex; i <= lastVisibleButtonIndex; i++) {
+ JCommandToggleButton currButton = ribbonGallery
+ .getButtonAt(i);
+
+ // show button and set bounds
+ currButton.setVisible(true);
+ if (ltr) {
+ currButton.setBounds(startX, buttonY,
+ singleButtonWidth, buttonHeight);
+ startX += (singleButtonWidth + gap);
+ } else {
+ currButton.setBounds(startX - singleButtonWidth,
+ buttonY, singleButtonWidth, buttonHeight);
+ startX -= (singleButtonWidth + gap);
+ }
+ currCountInRow++;
+ if (currCountInRow == visibleButtonsInEachRow) {
+ currCountInRow = 0;
+ if (ltr) {
+ startX = margin.left + gap;
+ } else {
+ startX = width - margin.right - gap;
+ }
+ buttonY += buttonHeight;
+ }
+ }
+ if (ribbonGallery.getButtonCount() == 0) {
+ scrollDownButton.setEnabled(false);
+ scrollUpButton.setEnabled(false);
+ expandActionButton.setEnabled(false);
+ } else {
+ // Scroll down button is enabled when the last button is not
+ // showing
+ scrollDownButton.setEnabled(!ribbonGallery.getButtonAt(
+ ribbonGallery.getButtonCount() - 1).isVisible());
+ // Scroll up button is enabled when the first button is not
+ // showing
+ scrollUpButton.setEnabled(!ribbonGallery.getButtonAt(0)
+ .isVisible());
+ expandActionButton.setEnabled(true);
+ }
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+
+ this.paintRibbonGalleryBorder(graphics);
+
+ graphics.dispose();
+ }
+
+ /**
+ * Paints ribbon gallery border.
+ *
+ * @param graphics
+ * Graphics context.
+ */
+ protected void paintRibbonGalleryBorder(Graphics graphics) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.setColor(FlamingoUtilities.getBorderColor());
+ Shape outerContour = FlamingoUtilities.getRibbonGalleryOutline(
+ this.margin.left, this.ribbonGallery.getWidth() - margin.right,
+ this.margin.top, this.ribbonGallery.getHeight()
+ - this.margin.bottom, 2);
+ if (this.ribbonGallery.getComponentOrientation().isLeftToRight()) {
+ g2d.clipRect(0, 0, this.ribbonGallery.getWidth() - margin.right
+ - buttonStrip.getWidth() / 2, this.ribbonGallery
+ .getHeight());
+ } else {
+ g2d.clipRect(margin.left + buttonStrip.getWidth() / 2, 0,
+ this.ribbonGallery.getWidth() - margin.left
+ - buttonStrip.getWidth() / 2, this.ribbonGallery
+ .getHeight());
+ }
+ g2d.draw(outerContour);
+ g2d.dispose();
+ }
+
+ /**
+ * Returns the layout gap for the controls in the associated ribbon gallery.
+ *
+ * @return The layout gap for the controls in the associated ribbon gallery.
+ */
+ protected int getLayoutGap() {
+ return 4;
+ }
+
+ /**
+ * Returns the preferred width of the ribbon gallery for the specified
+ * parameters.
+ *
+ * @param buttonCount
+ * Button count.
+ * @param availableHeight
+ * Available height in pixels.
+ * @return The preferred width of the ribbon gallery for the specified
+ * parameters.
+ */
+ public int getPreferredWidth(int buttonCount, int availableHeight) {
+ Insets borderInsets = ribbonGallery.getInsets();
+
+ int galleryHeight = availableHeight - margin.top - margin.bottom;
+ int buttonHeight = galleryHeight - borderInsets.top
+ - borderInsets.bottom;
+
+ // start at the left margin
+ int result = margin.left;
+ // add all the gallery buttons - based on the display state
+ CommandButtonDisplayState galleryButtonDisplayState = ribbonGallery
+ .getButtonDisplayState();
+ if (galleryButtonDisplayState == CommandButtonDisplayState.SMALL) {
+ result += buttonCount * buttonHeight / 3;
+ }
+ if (galleryButtonDisplayState == JRibbonBand.BIG_FIXED) {
+ result += buttonCount * buttonHeight;
+ }
+ if (galleryButtonDisplayState == JRibbonBand.BIG_FIXED_LANDSCAPE) {
+ result += buttonCount * buttonHeight * 5 / 4;
+ }
+ // and the gaps between them (including before first and after last)
+ result += (buttonCount + 1) * getLayoutGap();
+ // and the control button strip width
+ result += 15;
+ // and the gap to the right margin
+ result += margin.right;
+
+ // System.out.println(buttonCount + "/" + availableHeight + "/"
+ // + buttonHeight + " --> " + result);
+ return result;
+ }
+
+ /**
+ * Scrolls the contents of this ribbon gallery one row down.
+ */
+ protected void scrollOneRowDown() {
+ this.firstVisibleButtonIndex += this.visibleButtonsInEachRow;
+ // int buttonCount = this.ribbonGallery.getButtonCount();
+ // // compute the last visible button
+ // this.lastVisibleButtonIndex = this.firstVisibleButtonIndex
+ // + this.visibleButtonsCount - 1;
+ // if (this.lastVisibleButtonIndex >= buttonCount)
+ // this.lastVisibleButtonIndex = buttonCount - 1;
+ }
+
+ /**
+ * Scrolls the contents of this ribbon gallery one row up.
+ */
+ protected void scrollOneRowUp() {
+ this.firstVisibleButtonIndex -= this.visibleButtonsInEachRow;
+ // this.firstVisibleButtonIndex -= this.visibleButtonsCount;
+ // // int buttonCount = this.ribbonGallery.getButtonCount();
+ // // compute the last visible button
+ // this.lastVisibleButtonIndex = this.firstVisibleButtonIndex
+ // + this.visibleButtonsCount - 1;
+ // // // update the last visible index so there's overlap between the
+ // rows
+ // // this.lastVisibleButtonIndex = this.firstVisibleButtonIndex;
+ // // this.firstVisibleButtonIndex = this.lastVisibleButtonIndex
+ // // - this.visibleButtonsCount + 1;
+ // // if (this.firstVisibleButtonIndex < 0)
+ // // this.firstVisibleButtonIndex = 0;
+ }
+
+ /**
+ * Scrolls the contents of this ribbon gallery to reveal the currently
+ * selected button.
+ */
+ protected void scrollToSelected() {
+ JCommandToggleButton selected = this.ribbonGallery.getSelectedButton();
+ if (selected == null)
+ return;
+ int selIndex = -1;
+ for (int i = 0; i < this.ribbonGallery.getButtonCount(); i++) {
+ if (this.ribbonGallery.getButtonAt(i) == selected) {
+ selIndex = i;
+ break;
+ }
+ }
+ if (selIndex < 0)
+ return;
+
+ // is already shown?
+ if ((selIndex >= this.firstVisibleButtonIndex)
+ && (selIndex < (this.firstVisibleButtonIndex + this.visibleButtonRowNumber
+ * this.visibleButtonsInEachRow)))
+ return;
+
+ // not visible?
+ if (this.visibleButtonsInEachRow <= 0)
+ return;
+
+ while (true) {
+ if (selIndex < this.firstVisibleButtonIndex) {
+ // need to scroll up
+ this.scrollOneRowUp();
+ } else {
+ this.scrollOneRowDown();
+ }
+ if ((selIndex >= this.firstVisibleButtonIndex)
+ && (selIndex < (this.firstVisibleButtonIndex + this.visibleButtonRowNumber
+ * this.visibleButtonsInEachRow))) {
+ return;
+ }
+ }
+ }
+
+ protected void syncExpandKeyTip() {
+ this.expandActionButton.setActionKeyTip(this.ribbonGallery
+ .getExpandKeyTip());
+ }
+
+ @KeyTipManager.HasNextKeyTipChain
+ protected static class ExpandCommandButton extends JCommandButton {
+ public ExpandCommandButton(ResizableIcon icon) {
+ super(icon);
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonTaskToggleButtonUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonTaskToggleButtonUI.java
new file mode 100644
index 0000000..117ead9
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonTaskToggleButtonUI.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicGraphicsUtils;
+
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager.PopupEvent;
+import org.pushingpixels.flamingo.api.ribbon.JRibbon;
+import org.pushingpixels.flamingo.api.ribbon.RibbonContextualTaskGroup;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandToggleButtonUI;
+import org.pushingpixels.flamingo.internal.utils.*;
+
+/**
+ * Basic UI for toggle button of ribbon tasks {@link JRibbonTaskToggleButton}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicRibbonTaskToggleButtonUI extends BasicCommandToggleButtonUI {
+ protected PopupPanelManager.PopupListener popupListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicRibbonTaskToggleButtonUI();
+ }
+
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ Font f = this.commandButton.getFont();
+ if (f == null || f instanceof UIResource) {
+ this.commandButton.setFont(FlamingoUtilities.getFont(null,
+ "Ribbon.font", "Button.font", "Panel.font"));
+ }
+
+ Border border = this.commandButton.getBorder();
+ if (border == null || border instanceof UIResource) {
+ Border toInstall = UIManager
+ .getBorder("RibbonTaskToggleButton.border");
+ if (toInstall == null)
+ toInstall = new BorderUIResource.EmptyBorderUIResource(1, 12,
+ 1, 12);
+ this.commandButton.setBorder(toInstall);
+ }
+
+ this.commandButton.setFlat(true);
+ this.commandButton.setOpaque(false);
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.popupListener = new PopupPanelManager.PopupListener() {
+ @Override
+ public void popupShown(PopupEvent event) {
+ if (event.getSource() == commandButton) {
+ commandButton.getActionModel()
+ .setSelected(isTaskSelected());
+ }
+ }
+
+ @Override
+ public void popupHidden(PopupEvent event) {
+ if (event.getSource() == commandButton) {
+ commandButton.getActionModel()
+ .setSelected(isTaskSelected());
+ }
+ }
+
+ private boolean isTaskSelected() {
+ JRibbon ribbon = (JRibbon) SwingUtilities.getAncestorOfClass(
+ JRibbon.class, commandButton);
+ if (ribbon == null)
+ return false;
+
+ return ribbon.getSelectedTask() == ((JRibbonTaskToggleButton) commandButton)
+ .getRibbonTask();
+ }
+ };
+ PopupPanelManager.defaultManager().addPopupListener(this.popupListener);
+ }
+
+ @Override
+ protected void uninstallListeners() {
+ PopupPanelManager.defaultManager().removePopupListener(
+ this.popupListener);
+ this.popupListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ RenderingUtils.installDesktopHints(g2d);
+ this.layoutInfo = this.layoutManager.getLayoutInfo(this.commandButton,
+ g);
+ this.paintButtonBackground(g2d, new Rectangle(0, 0, c.getWidth(), c
+ .getHeight() + 10));
+ this.paintText(g2d);
+ g2d.dispose();
+ }
+
+ protected void paintText(Graphics g) {
+ FontMetrics fm = g.getFontMetrics();
+ String toPaint = this.commandButton.getText();
+
+ // compute the insets
+ int fullInsets = this.commandButton.getInsets().left;
+ int pw = this.getPreferredSize(this.commandButton).width;
+ int mw = this.getMinimumSize(this.commandButton).width;
+ int w = this.commandButton.getWidth();
+ int h = this.commandButton.getHeight();
+ int insets = fullInsets - (pw - w) * (fullInsets - 2) / (pw - mw);
+
+ // and the text rectangle
+ Rectangle textRect = new Rectangle(insets,
+ 1 + (h - fm.getHeight()) / 2, w - 2 * insets, fm.getHeight());
+
+ // show the first characters that fit into the available text rectangle
+ while (true) {
+ if (toPaint.length() == 0)
+ break;
+ int strWidth = fm.stringWidth(toPaint);
+ if (strWidth <= textRect.width)
+ break;
+ toPaint = toPaint.substring(0, toPaint.length() - 1);
+ }
+ BasicGraphicsUtils.drawString(g, toPaint, -1, textRect.x, textRect.y
+ + fm.getAscent());
+ }
+
+ /**
+ * Paints the button background.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param toFill
+ * Rectangle to fill.
+ */
+ @Override
+ protected void paintButtonBackground(Graphics graphics, Rectangle toFill) {
+ JRibbon ribbon = (JRibbon) SwingUtilities.getAncestorOfClass(
+ JRibbon.class, this.commandButton);
+
+ this.buttonRendererPane.setBounds(toFill.x, toFill.y, toFill.width,
+ toFill.height);
+ ButtonModel model = this.rendererButton.getModel();
+ model.setEnabled(this.commandButton.isEnabled());
+ model.setSelected(false);
+ // System.out.println(toggleTabButton.getText() + ":"
+ // + toggleTabButton.isSelected());
+
+ // selected task toggle button should not have any background if
+ // the ribbon is minimized and it is not shown in a popup
+ boolean displayAsSelected = this.commandButton.getActionModel()
+ .isSelected();
+ model.setRollover(displayAsSelected
+ || this.commandButton.getActionModel().isRollover());
+ model.setPressed(false);
+ if (model.isRollover()) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ // partial translucency if it is not selected
+ if (!this.commandButton.getActionModel().isSelected()) {
+ g2d.setComposite(AlphaComposite.SrcOver.derive(0.4f));
+ }
+ g2d.translate(toFill.x, toFill.y);
+
+ Color contextualGroupHueColor = ((JRibbonTaskToggleButton) this.commandButton)
+ .getContextualGroupHueColor();
+ boolean isContextualTask = (contextualGroupHueColor != null);
+ if (!isContextualTask) {
+ Shape clip = g2d.getClip();
+ g2d.clip(FlamingoUtilities.getRibbonTaskToggleButtonOutline(
+ toFill.width, toFill.height, 2));
+ this.buttonRendererPane.paintComponent(g2d,
+ this.rendererButton, this.commandButton, toFill.x
+ - toFill.width / 2, toFill.y - toFill.height
+ / 2, 2 * toFill.width, 2 * toFill.height, true);
+ g2d.setColor(FlamingoUtilities.getBorderColor().darker());
+ g2d.setClip(clip);
+ g2d.draw(FlamingoUtilities.getRibbonTaskToggleButtonOutline(
+ toFill.width, toFill.height + 1, 2));
+ } else {
+ // draw to an offscreen image, colorize and draw the colorized
+ // image
+ BufferedImage offscreen = FlamingoUtilities.getBlankImage(
+ toFill.width, toFill.height);
+ Graphics2D offscreenGraphics = offscreen.createGraphics();
+ Shape clip = g2d.getClip();
+ offscreenGraphics.clip(FlamingoUtilities
+ .getRibbonTaskToggleButtonOutline(toFill.width,
+ toFill.height, 2));
+ this.buttonRendererPane.paintComponent(offscreenGraphics,
+ this.rendererButton, this.commandButton, toFill.x
+ - toFill.width / 2, toFill.y - toFill.height
+ / 2, 2 * toFill.width, 2 * toFill.height, true);
+ offscreenGraphics.setColor(FlamingoUtilities.getBorderColor()
+ .darker());
+ offscreenGraphics.setClip(clip);
+ offscreenGraphics.draw(FlamingoUtilities
+ .getRibbonTaskToggleButtonOutline(toFill.width,
+ toFill.height + 1, 2));
+ offscreenGraphics.dispose();
+
+ ColorShiftFilter filter = new ColorShiftFilter(
+ contextualGroupHueColor,
+ RibbonContextualTaskGroup.HUE_ALPHA);
+ BufferedImage colorized = filter.filter(offscreen, null);
+ g2d.drawImage(colorized, 0, 0, null);
+ }
+ g2d.dispose();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicButtonUI#getPreferredSize(javax.swing.JComponent
+ * )
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ JRibbonTaskToggleButton b = (JRibbonTaskToggleButton) c;
+
+ Icon icon = b.getIcon();
+ String text = b.getText();
+
+ Font font = b.getFont();
+ FontMetrics fm = b.getFontMetrics(font);
+
+ Rectangle iconR = new Rectangle();
+ Rectangle textR = new Rectangle();
+ Rectangle viewR = new Rectangle(Short.MAX_VALUE, Short.MAX_VALUE);
+
+ SwingUtilities.layoutCompoundLabel(b, fm, text, icon,
+ SwingUtilities.CENTER, b.getHorizontalAlignment(),
+ SwingUtilities.CENTER, SwingUtilities.CENTER, viewR, iconR,
+ textR, (text == null ? 0 : 6));
+
+ Rectangle r = iconR.union(textR);
+
+ Insets insets = b.getInsets();
+ r.width += insets.left + insets.right;
+ r.height += insets.top + insets.bottom;
+
+ return r.getSize();
+ }
+
+ @Override
+ public Dimension getMinimumSize(JComponent c) {
+ JRibbonTaskToggleButton b = (JRibbonTaskToggleButton) c;
+
+ Icon icon = b.getIcon();
+ String text = "Www";
+
+ Font font = b.getFont();
+ FontMetrics fm = b.getFontMetrics(font);
+
+ Rectangle iconR = new Rectangle();
+ Rectangle textR = new Rectangle();
+ Rectangle viewR = new Rectangle(Short.MAX_VALUE, Short.MAX_VALUE);
+
+ SwingUtilities.layoutCompoundLabel(b, fm, text, icon,
+ SwingUtilities.CENTER, b.getHorizontalAlignment(),
+ SwingUtilities.CENTER, SwingUtilities.CENTER, viewR, iconR,
+ textR, (text == null ? 0 : 6));
+
+ Rectangle r = iconR.union(textR);
+
+ Insets insets = b.getInsets();
+ r.width += 4;
+ r.height += insets.top + insets.bottom;
+
+ return r.getSize();
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonUI.java
new file mode 100755
index 0000000..a30479c
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/BasicRibbonUI.java
@@ -0,0 +1,2176 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.geom.Arc2D;
+import java.awt.geom.GeneralPath;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicGraphicsUtils;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager.PopupEvent;
+import org.pushingpixels.flamingo.api.ribbon.*;
+import org.pushingpixels.flamingo.api.ribbon.resize.RibbonBandResizePolicy;
+import org.pushingpixels.flamingo.api.ribbon.resize.RibbonBandResizeSequencingPolicy;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonUI;
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuButton;
+import org.pushingpixels.flamingo.internal.utils.*;
+
+/**
+ * Basic UI for ribbon {@link JRibbon}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicRibbonUI extends RibbonUI {
+ /**
+ * Client property marking the ribbon component to indicate whether the task
+ * bar and contextual task group headers should be shown on the title pane
+ * of the window. This is only relevant for the {@link JRibbonFrame}.
+ */
+ public static final String IS_USING_TITLE_PANE = "ribbon.internal.isUsingTitlePane";
+
+ public static final String HELP_PANEL_COMPONENTS = "HelpPanelComponents";
+
+ private static final String JUST_MINIMIZED = "ribbon.internal.justMinimized";
+
+ /**
+ * The associated ribbon.
+ */
+ protected JRibbon ribbon;
+
+ /**
+ * Mouse wheel listener to switch between ribbon tasks.
+ */
+ // protected MouseWheelListener mouseWheelListener;
+ /**
+ * Taskbar panel.
+ */
+ protected JPanel taskBarPanel;
+
+ protected JScrollablePanel<BandHostPanel> bandScrollablePanel;
+
+ protected JScrollablePanel<TaskToggleButtonsHostPanel> taskToggleButtonsScrollablePanel;
+
+ protected JRibbonApplicationMenuButton applicationMenuButton;
+
+ protected JComponent helpPanel;
+ protected JCommandButton helpButton;
+
+ /**
+ * Map of toggle buttons of all tasks.
+ */
+ protected Map<RibbonTask, JRibbonTaskToggleButton> taskToggleButtons;
+
+ /**
+ * Button group for task toggle buttons.
+ */
+ protected CommandToggleButtonGroup taskToggleButtonGroup;
+
+ /**
+ * Change listener.
+ */
+ protected ChangeListener ribbonChangeListener;
+
+ /**
+ * Property change listener.
+ */
+ protected PropertyChangeListener propertyChangeListener;
+
+ protected ContainerListener ribbonContainerListener;
+
+ protected ComponentListener ribbonComponentListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicRibbonUI();
+ }
+
+ /**
+ * Creates a new basic ribbon UI delegate.
+ */
+ public BasicRibbonUI() {
+ this.taskToggleButtons = new HashMap<RibbonTask, JRibbonTaskToggleButton>();
+ this.taskToggleButtonGroup = new CommandToggleButtonGroup();
+ this.taskToggleButtonGroup.setAllowsClearingSelection(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.ribbon = (JRibbon) c;
+ installDefaults();
+ installComponents();
+ installListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ uninstallListeners();
+ uninstallComponents();
+ uninstallDefaults();
+
+ this.ribbon = null;
+ }
+
+ /**
+ * Installs listeners on the associated ribbon.
+ */
+ protected void installListeners() {
+ // this.mouseWheelListener = new MouseWheelListener() {
+ // public void mouseWheelMoved(MouseWheelEvent e) {
+ // handleMouseWheelEvent(e);
+ // }
+ // };
+ // this.taskToggleButtonsScrollablePanel.getView().addMouseWheelListener(
+ // this.mouseWheelListener);
+ //
+ this.ribbonChangeListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ syncRibbonState();
+ }
+ };
+ this.ribbon.addChangeListener(this.ribbonChangeListener);
+
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("selectedTask".equals(evt.getPropertyName())) {
+ RibbonTask old = (RibbonTask) evt.getOldValue();
+ final RibbonTask curr = (RibbonTask) evt.getNewValue();
+ if ((old != null) && (taskToggleButtons.get(old) != null)) {
+ taskToggleButtons.get(old).getActionModel()
+ .setSelected(false);
+ }
+ if ((curr != null) && (taskToggleButtons.get(curr) != null)) {
+ taskToggleButtons.get(curr).getActionModel()
+ .setSelected(true);
+ }
+
+ if (isShowingScrollsForTaskToggleButtons()
+ && (curr != null)) {
+ // scroll selected task as necessary so that it's
+ // visible
+ JRibbonTaskToggleButton toggleButton = taskToggleButtons
+ .get(curr);
+ if (toggleButton != null) {
+ scrollAndRevealTaskToggleButton(toggleButton);
+ }
+ }
+
+ // Special case for showing key tips of ribbon tasks.
+ // When a ribbon task is selected with a key tip, its
+ // showing and layout is deferred as a separate Runnable
+ // on EDT. When the key chain for that task is created,
+ // the command buttons are not at their final size yet
+ // and no key tips are shown.
+ // Here we schedule yet another Runnable
+ // to recompute all keytips if the
+ // originator is a task toggle button.
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ KeyTipManager ktm = KeyTipManager.defaultManager();
+ if (ktm.isShowingKeyTips()) {
+ KeyTipManager.KeyTipChain chain = ktm
+ .getCurrentlyShownKeyTipChain();
+ if (chain.chainParentComponent == taskToggleButtons
+ .get(curr)) {
+ ktm.refreshCurrentChain();
+ }
+ }
+ }
+ });
+ }
+ if ("applicationMenuRichTooltip".equals(evt.getPropertyName())) {
+ syncApplicationMenuTips();
+ }
+ if ("applicationMenuKeyTip".equals(evt.getPropertyName())) {
+ syncApplicationMenuTips();
+ }
+ if ("applicationMenu".equals(evt.getPropertyName())) {
+ ribbon.revalidate();
+ ribbon.doLayout();
+ ribbon.repaint();
+ Window windowAncestor = SwingUtilities
+ .getWindowAncestor(ribbon);
+ if (windowAncestor instanceof JRibbonFrame) {
+ FlamingoUtilities
+ .updateRibbonFrameIconImages((JRibbonFrame) windowAncestor);
+ }
+ }
+ if ("minimized".equals(evt.getPropertyName())) {
+ PopupPanelManager.defaultManager().hidePopups(null);
+ ribbon.revalidate();
+ ribbon.doLayout();
+ ribbon.repaint();
+ }
+ }
+ };
+ this.ribbon.addPropertyChangeListener(this.propertyChangeListener);
+
+ this.ribbonContainerListener = new ContainerAdapter() {
+ @Override
+ public void componentAdded(ContainerEvent e) {
+ if (isUsingTitlePane())
+ return;
+ Component added = e.getComponent();
+ if (added != applicationMenuButton) {
+ ribbon.setComponentZOrder(applicationMenuButton, ribbon
+ .getComponentCount() - 1);
+ }
+ }
+ };
+ this.ribbon.addContainerListener(this.ribbonContainerListener);
+
+ this.ribbonComponentListener = new ComponentAdapter() {
+ @Override
+ public void componentResized(ComponentEvent e) {
+ KeyTipManager.defaultManager().hideAllKeyTips();
+ }
+ };
+ this.ribbon.addComponentListener(this.ribbonComponentListener);
+ }
+
+ /**
+ * Uninstalls listeners from the associated ribbon.
+ */
+ protected void uninstallListeners() {
+ // this.taskToggleButtonsScrollablePanel.getView()
+ // .removeMouseWheelListener(this.mouseWheelListener);
+ // this.mouseWheelListener = null;
+ //
+ this.ribbon.removeChangeListener(this.ribbonChangeListener);
+ this.ribbonChangeListener = null;
+
+ this.ribbon.removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+
+ this.ribbon.removeContainerListener(this.ribbonContainerListener);
+ this.ribbonContainerListener = null;
+
+ this.ribbon.removeComponentListener(this.ribbonComponentListener);
+ this.ribbonComponentListener = null;
+ }
+
+ /**
+ * Installs defaults on the associated ribbon.
+ */
+ protected void installDefaults() {
+ Border b = this.ribbon.getBorder();
+ if (b == null || b instanceof UIResource) {
+ Border toSet = UIManager.getBorder("Ribbon.border");
+ if (toSet == null)
+ toSet = new BorderUIResource.EmptyBorderUIResource(1, 2, 1, 2);
+ this.ribbon.setBorder(toSet);
+ }
+ }
+
+ /**
+ * Uninstalls defaults from the associated ribbon.
+ */
+ protected void uninstallDefaults() {
+ }
+
+ /**
+ * Installs subcomponents on the associated ribbon.
+ */
+ protected void installComponents() {
+ // taskbar panel
+ this.taskBarPanel = new TaskbarPanel();
+ this.taskBarPanel.setName("JRibbon Task Bar");
+ this.taskBarPanel.setLayout(createTaskbarLayoutManager());
+ this.ribbon.add(this.taskBarPanel);
+
+ // band scrollable panel
+ BandHostPanel bandHostPanel = createBandHostPanel();
+ bandHostPanel.setLayout(createBandHostPanelLayoutManager());
+ this.bandScrollablePanel = new JScrollablePanel<BandHostPanel>(
+ bandHostPanel, JScrollablePanel.ScrollType.HORIZONTALLY);
+ this.bandScrollablePanel.setScrollOnRollover(false);
+ this.ribbon.add(this.bandScrollablePanel);
+
+ // task toggle buttons scrollable panel
+ TaskToggleButtonsHostPanel taskToggleButtonsHostPanel = createTaskToggleButtonsHostPanel();
+ taskToggleButtonsHostPanel
+ .setLayout(createTaskToggleButtonsHostPanelLayoutManager());
+ this.taskToggleButtonsScrollablePanel = new JScrollablePanel<TaskToggleButtonsHostPanel>(
+ taskToggleButtonsHostPanel,
+ JScrollablePanel.ScrollType.HORIZONTALLY);
+ this.taskToggleButtonsScrollablePanel.setScrollOnRollover(false);
+ this.taskToggleButtonsScrollablePanel
+ .addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ // need to repaint the entire ribbon since scrolling
+ // the task toggle buttons affects the contour outline
+ // of the ribbon
+ ribbon.repaint();
+ }
+ });
+ this.ribbon.add(this.taskToggleButtonsScrollablePanel);
+
+ this.ribbon.setLayout(createLayoutManager());
+
+ this.syncRibbonState();
+
+ this.applicationMenuButton = new JRibbonApplicationMenuButton(
+ this.ribbon);
+ this.syncApplicationMenuTips();
+ this.ribbon.add(applicationMenuButton);
+ Window windowAncestor = SwingUtilities.getWindowAncestor(this.ribbon);
+ if (windowAncestor instanceof JRibbonFrame) {
+ FlamingoUtilities
+ .updateRibbonFrameIconImages((JRibbonFrame) windowAncestor);
+ }
+ }
+
+ protected LayoutManager createTaskToggleButtonsHostPanelLayoutManager() {
+ return new TaskToggleButtonsHostPanelLayout();
+ }
+
+ protected TaskToggleButtonsHostPanel createTaskToggleButtonsHostPanel() {
+ return new TaskToggleButtonsHostPanel();
+ }
+
+ protected BandHostPanel createBandHostPanel() {
+ return new BandHostPanel();
+ }
+
+ protected LayoutManager createBandHostPanelLayoutManager() {
+ return new BandHostPanelLayout();
+ }
+
+ /**
+ * Uninstalls subcomponents from the associated ribbon.
+ */
+ protected void uninstallComponents() {
+ this.taskBarPanel.removeAll();
+ this.taskBarPanel.setLayout(null);
+ this.ribbon.remove(this.taskBarPanel);
+
+ BandHostPanel bandHostPanel = this.bandScrollablePanel.getView();
+ bandHostPanel.removeAll();
+ bandHostPanel.setLayout(null);
+ this.ribbon.remove(this.bandScrollablePanel);
+
+ TaskToggleButtonsHostPanel taskToggleButtonsHostPanel = this.taskToggleButtonsScrollablePanel
+ .getView();
+ taskToggleButtonsHostPanel.removeAll();
+ taskToggleButtonsHostPanel.setLayout(null);
+ this.ribbon.remove(this.taskToggleButtonsScrollablePanel);
+
+ this.ribbon.remove(this.applicationMenuButton);
+ if (this.helpPanel != null)
+ this.ribbon.remove(this.helpPanel);
+
+ this.ribbon.setLayout(null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ RenderingUtils.installDesktopHints(g2d);
+ super.update(g2d, c);
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ this.paintBackground(g);
+
+ if (!ribbon.isMinimized()) {
+ Insets ins = c.getInsets();
+ int extraHeight = getTaskToggleButtonHeight();
+ if (!this.isUsingTitlePane())
+ extraHeight += getTaskbarHeight();
+ this.paintTaskArea(g, 0, ins.top + extraHeight, c.getWidth(), c
+ .getHeight()
+ - extraHeight - ins.top - ins.bottom);
+ } else {
+ this.paintMinimizedRibbonSeparator(g);
+ }
+ }
+
+ protected void paintMinimizedRibbonSeparator(Graphics g) {
+ Color borderColor = FlamingoUtilities.getBorderColor();
+ g.setColor(borderColor);
+ Insets ins = ribbon.getInsets();
+ g.drawLine(0, ribbon.getHeight() - ins.bottom, ribbon.getWidth(),
+ ribbon.getHeight() - ins.bottom);
+ }
+
+ /**
+ * Paints the ribbon background.
+ *
+ * @param g
+ * Graphics context.
+ */
+ protected void paintBackground(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ g2d.setColor(FlamingoUtilities.getColor(Color.lightGray,
+ "Panel.background"));
+ g2d.fillRect(0, 0, this.ribbon.getWidth(), this.ribbon.getHeight());
+
+ g2d.dispose();
+ }
+
+ /**
+ * Paints the task border.
+ *
+ * @param g
+ * Graphics context.
+ * @param x
+ * Left X of the tasks band bounds.
+ * @param y
+ * Top Y of the tasks band bounds.
+ * @param width
+ * Width of the tasks band bounds.
+ * @param height
+ * Height of the tasks band bounds.
+ */
+ protected void paintTaskArea(Graphics g, int x, int y, int width, int height) {
+ if (ribbon.getTaskCount() == 0)
+ return;
+
+ JRibbonTaskToggleButton selectedTaskButton = this.taskToggleButtons
+ .get(this.ribbon.getSelectedTask());
+ Rectangle selectedTaskButtonBounds = selectedTaskButton.getBounds();
+ Point converted = SwingUtilities.convertPoint(selectedTaskButton
+ .getParent(), selectedTaskButtonBounds.getLocation(),
+ this.ribbon);
+ // System.out.println("Painted " + selectedTaskButtonBounds.x + "->" +
+ // converted.x);
+ Rectangle taskToggleButtonsViewportBounds = taskToggleButtonsScrollablePanel
+ .getView().getParent().getBounds();
+ taskToggleButtonsViewportBounds.setLocation(SwingUtilities
+ .convertPoint(taskToggleButtonsScrollablePanel,
+ taskToggleButtonsViewportBounds.getLocation(),
+ this.ribbon));
+ int startSelectedX = Math.max(converted.x + 1,
+ (int) taskToggleButtonsViewportBounds.getMinX());
+ startSelectedX = Math.min(startSelectedX,
+ (int) taskToggleButtonsViewportBounds.getMaxX());
+ int endSelectedX = Math.min(converted.x
+ + selectedTaskButtonBounds.width - 1,
+ (int) taskToggleButtonsViewportBounds.getMaxX());
+ endSelectedX = Math.max(endSelectedX,
+ (int) taskToggleButtonsViewportBounds.getMinX());
+ Shape outerContour = FlamingoUtilities.getRibbonBorderOutline(x + 1, x
+ + width - 3, startSelectedX, endSelectedX, converted.y, y, y
+ + height, 2);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setColor(FlamingoUtilities.getBorderColor());
+ g2d.draw(outerContour);
+
+ // check whether the currently selected task is a contextual task
+ RibbonTask selected = this.ribbon.getSelectedTask();
+ RibbonContextualTaskGroup contextualGroup = selected
+ .getContextualGroup();
+ if (contextualGroup != null) {
+ // paint a small gradient directly below the task area
+ Insets ins = this.ribbon.getInsets();
+ int topY = ins.top + getTaskbarHeight();
+ int bottomY = topY + 5;
+ Color hueColor = contextualGroup.getHueColor();
+ Paint paint = new GradientPaint(0, topY, FlamingoUtilities
+ .getAlphaColor(hueColor,
+ (int) (255 * RibbonContextualTaskGroup.HUE_ALPHA)),
+ 0, bottomY, FlamingoUtilities.getAlphaColor(hueColor, 0));
+ g2d.setPaint(paint);
+ g2d.clip(outerContour);
+ g2d.fillRect(0, topY, width, bottomY - topY + 1);
+ }
+
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.RibbonUI#getContextualGroupTabBounds(org
+ * .jvnet.flamingo.ribbon.RibbonContextualTaskGroup)
+ */
+ @Override
+ public Rectangle getContextualTaskGroupBounds(
+ RibbonContextualTaskGroup group) {
+ Rectangle rect = null;
+ for (int j = 0; j < group.getTaskCount(); j++) {
+ JRibbonTaskToggleButton button = taskToggleButtons.get(group
+ .getTask(j));
+ if (rect == null)
+ rect = button.getBounds();
+ else
+ rect = rect.union(button.getBounds());
+ }
+ int buttonGap = getTabButtonGap();
+ Point location = SwingUtilities.convertPoint(
+ taskToggleButtonsScrollablePanel.getView(), rect.getLocation(),
+ ribbon);
+ return new Rectangle(location.x - buttonGap / 3, location.y - 1,
+ rect.width + buttonGap * 2 / 3 - 1, rect.height + 1);
+ }
+
+ /**
+ * Returns the layout gap for the bands in the associated ribbon.
+ *
+ * @return The layout gap for the bands in the associated ribbon.
+ */
+ protected int getBandGap() {
+ return 2;
+ }
+
+ /**
+ * Returns the layout gap for the tab buttons in the associated ribbon.
+ *
+ * @return The layout gap for the tab buttons in the associated ribbon.
+ */
+ protected int getTabButtonGap() {
+ return 6;
+ }
+
+ /**
+ * Invoked by <code>installUI</code> to create a layout manager object to
+ * manage the {@link JRibbon}.
+ *
+ * @return a layout manager object
+ */
+ protected LayoutManager createLayoutManager() {
+ return new RibbonLayout();
+ }
+
+ /**
+ * Invoked by <code>installUI</code> to create a layout manager object to
+ * manage the {@link JRibbon} taskbar.
+ *
+ * @return a layout manager object
+ */
+ protected LayoutManager createTaskbarLayoutManager() {
+ return new TaskbarLayout();
+ }
+
+ /**
+ * Returns the height of the taskbar area.
+ *
+ * @return The height of the taskbar area.
+ */
+ public int getTaskbarHeight() {
+ return 24;
+ }
+
+ /**
+ * Returns the height of the task toggle button area.
+ *
+ * @return The height of the task toggle button area.
+ */
+ public int getTaskToggleButtonHeight() {
+ return 22;
+ }
+
+ /**
+ * Layout for the ribbon.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class RibbonLayout implements LayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ Insets ins = c.getInsets();
+ int maxPrefBandHeight = 0;
+ boolean isRibbonMinimized = ribbon.isMinimized();
+ if (!isRibbonMinimized) {
+ if (ribbon.getTaskCount() > 0) {
+ RibbonTask selectedTask = ribbon.getSelectedTask();
+ for (AbstractRibbonBand<?> ribbonBand : selectedTask
+ .getBands()) {
+ int bandPrefHeight = ribbonBand.getPreferredSize().height;
+ Insets bandInsets = ribbonBand.getInsets();
+ maxPrefBandHeight = Math.max(maxPrefBandHeight,
+ bandPrefHeight + bandInsets.top
+ + bandInsets.bottom);
+ }
+ }
+ }
+
+ int extraHeight = getTaskToggleButtonHeight();
+ if (!isUsingTitlePane())
+ extraHeight += getTaskbarHeight();
+ int prefHeight = maxPrefBandHeight + extraHeight + ins.top
+ + ins.bottom;
+ // System.out.println("Ribbon pref = " + prefHeight);
+ return new Dimension(c.getWidth(), prefHeight);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ // go over all ribbon bands and sum the width
+ // of ribbon buttons (of collapsed state)
+ Insets ins = c.getInsets();
+ int width = 0;
+ int maxMinBandHeight = 0;
+ int gap = getBandGap();
+
+ int extraHeight = getTaskToggleButtonHeight();
+ if (!isUsingTitlePane())
+ extraHeight += getTaskbarHeight();
+
+ if (ribbon.getTaskCount() > 0) {
+ boolean isRibbonMinimized = ribbon.isMinimized();
+ // minimum is when all the tasks are collapsed
+ RibbonTask selectedTask = ribbon.getSelectedTask();
+ for (AbstractRibbonBand ribbonBand : selectedTask.getBands()) {
+ int bandPrefHeight = ribbonBand.getMinimumSize().height;
+ Insets bandInsets = ribbonBand.getInsets();
+ RibbonBandUI bandUI = ribbonBand.getUI();
+ width += bandUI.getPreferredCollapsedWidth();
+ if (!isRibbonMinimized) {
+ maxMinBandHeight = Math.max(maxMinBandHeight,
+ bandPrefHeight + bandInsets.top
+ + bandInsets.bottom);
+ }
+ }
+ // add inter-band gaps
+ width += gap * (selectedTask.getBandCount() - 1);
+ } else {
+ // fix for issue 44 (empty ribbon)
+ width = 50;
+ }
+ return new Dimension(width, maxMinBandHeight + extraHeight
+ + ins.top + ins.bottom);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ // System.out.println("Ribbon real = " + c.getHeight());
+
+ Insets ins = c.getInsets();
+ int tabButtonGap = getTabButtonGap();
+
+ boolean ltr = ribbon.getComponentOrientation().isLeftToRight();
+
+ // the top row - task bar components
+ int width = c.getWidth();
+ int taskbarHeight = getTaskbarHeight();
+ int y = ins.top;
+
+ boolean isUsingTitlePane = isUsingTitlePane();
+ // handle taskbar only if it is not marked
+ if (!isUsingTitlePane) {
+ taskBarPanel.removeAll();
+ for (Component regComp : ribbon.getTaskbarComponents()) {
+ taskBarPanel.add(regComp);
+ }
+ // taskbar takes all available width
+ taskBarPanel.setBounds(ins.left, ins.top, width - ins.left
+ - ins.right, taskbarHeight);
+ y += taskbarHeight;
+ } else {
+ taskBarPanel.setBounds(0, 0, 0, 0);
+ }
+
+ int taskToggleButtonHeight = getTaskToggleButtonHeight();
+
+ int x = ltr ? ins.left : width - ins.right;
+ // the application menu button
+ int appMenuButtonSize = taskbarHeight + taskToggleButtonHeight;
+ if (!isUsingTitlePane) {
+ applicationMenuButton
+ .setVisible(ribbon.getApplicationMenu() != null);
+ if (ribbon.getApplicationMenu() != null) {
+ if (ltr) {
+ applicationMenuButton.setBounds(x, ins.top,
+ appMenuButtonSize, appMenuButtonSize);
+ } else {
+ applicationMenuButton.setBounds(x - appMenuButtonSize,
+ ins.top, appMenuButtonSize, appMenuButtonSize);
+ }
+ }
+ } else {
+ applicationMenuButton.setVisible(false);
+ }
+ x = ltr ? x + 2 : x - 2;
+ if (FlamingoUtilities.getApplicationMenuButton(SwingUtilities
+ .getWindowAncestor(ribbon)) != null) {
+ x = ltr ? x + appMenuButtonSize : x - appMenuButtonSize;
+ }
+
+ // the help button
+ if (helpPanel != null) {
+ Dimension preferred = helpPanel.getPreferredSize();
+ if (ltr) {
+ helpPanel.setBounds(width - ins.right - preferred.width,
+ y, preferred.width, preferred.height);
+ } else {
+ helpPanel.setBounds(ins.left, y, preferred.width,
+ preferred.height);
+ }
+ }
+
+ // task buttons
+ if (ltr) {
+ int taskButtonsWidth = (helpPanel != null) ? (helpPanel
+ .getX()
+ - tabButtonGap - x) : (c.getWidth() - ins.right - x);
+ taskToggleButtonsScrollablePanel.setBounds(x, y,
+ taskButtonsWidth, taskToggleButtonHeight);
+ } else {
+ int taskButtonsWidth = (helpPanel != null) ? (x - tabButtonGap
+ - helpPanel.getX() - helpPanel.getWidth())
+ : (x - ins.left);
+ taskToggleButtonsScrollablePanel.setBounds(
+ x - taskButtonsWidth, y, taskButtonsWidth,
+ taskToggleButtonHeight);
+ }
+
+ TaskToggleButtonsHostPanel taskToggleButtonsHostPanel = taskToggleButtonsScrollablePanel
+ .getView();
+ int taskToggleButtonsHostPanelMinWidth = taskToggleButtonsHostPanel
+ .getMinimumSize().width;
+ taskToggleButtonsHostPanel.setPreferredSize(new Dimension(
+ taskToggleButtonsHostPanelMinWidth,
+ taskToggleButtonsScrollablePanel.getBounds().height));
+ taskToggleButtonsScrollablePanel.doLayout();
+
+ y += taskToggleButtonHeight;
+
+ int extraHeight = taskToggleButtonHeight;
+ if (!isUsingTitlePane)
+ extraHeight += taskbarHeight;
+
+ if (bandScrollablePanel.getParent() == ribbon) {
+ if (!ribbon.isMinimized() && (ribbon.getTaskCount() > 0)) {
+ // y += ins.top;
+ Insets bandInsets = (ribbon.getSelectedTask()
+ .getBandCount() == 0) ? new Insets(0, 0, 0, 0)
+ : ribbon.getSelectedTask().getBand(0).getInsets();
+ bandScrollablePanel.setBounds(1 + ins.left, y
+ + bandInsets.top, c.getWidth() - 2 * ins.left - 2
+ * ins.right - 1, c.getHeight() - extraHeight
+ - ins.top - ins.bottom - bandInsets.top
+ - bandInsets.bottom);
+ // System.out.println("Scrollable : "
+ // + bandScrollablePanel.getBounds());
+ BandHostPanel bandHostPanel = bandScrollablePanel.getView();
+ int bandHostPanelMinWidth = bandHostPanel.getMinimumSize().width;
+ bandHostPanel.setPreferredSize(new Dimension(
+ bandHostPanelMinWidth, bandScrollablePanel
+ .getBounds().height));
+ bandScrollablePanel.doLayout();
+ bandHostPanel.doLayout();
+ } else {
+ bandScrollablePanel.setBounds(0, 0, 0, 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * Layout for the task bar.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class TaskbarLayout implements LayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ Insets ins = c.getInsets();
+ int pw = 0;
+ int gap = getBandGap();
+ for (Component regComp : ribbon.getTaskbarComponents()) {
+ pw += regComp.getPreferredSize().width;
+ pw += gap;
+ }
+ return new Dimension(pw + ins.left + ins.right, getTaskbarHeight()
+ + ins.top + ins.bottom);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ return this.preferredLayoutSize(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ Insets ins = c.getInsets();
+ int gap = getBandGap();
+
+ boolean ltr = c.getComponentOrientation().isLeftToRight();
+
+ if (ltr) {
+ int x = ins.left + 1;
+ if (applicationMenuButton.isVisible()) {
+ x += (applicationMenuButton.getX() + applicationMenuButton
+ .getWidth());
+ }
+
+ for (Component regComp : ribbon.getTaskbarComponents()) {
+ int pw = regComp.getPreferredSize().width;
+ regComp.setBounds(x, ins.top + 1, pw, c.getHeight()
+ - ins.top - ins.bottom - 2);
+ x += (pw + gap);
+ }
+ } else {
+ int x = c.getWidth() - ins.right - 1;
+ if (applicationMenuButton.isVisible()) {
+ x = applicationMenuButton.getX() - 1;
+ }
+
+ for (Component regComp : ribbon.getTaskbarComponents()) {
+ int pw = regComp.getPreferredSize().width;
+ regComp.setBounds(x - pw, ins.top + 1, pw, c.getHeight()
+ - ins.top - ins.bottom - 2);
+ x -= (pw + gap);
+ }
+ }
+ }
+ }
+
+ /**
+ * The taskbar panel that holds the {@link JRibbon#getTaskbarComponents()}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class TaskbarPanel extends JPanel {
+ /**
+ * Creates the new taskbar panel.
+ */
+ public TaskbarPanel() {
+ super();
+ this.setOpaque(false);
+ this.setBorder(new EmptyBorder(1, 0, 1, 0));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
+ */
+ @Override
+ protected void paintComponent(Graphics g) {
+ Shape contour = getOutline(this);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ RenderingUtils.installDesktopHints(g2d);
+
+ if (contour != null) {
+ g2d.setComposite(AlphaComposite.SrcOver.derive(0.6f));
+ g2d.setColor(FlamingoUtilities.getColor(Color.lightGray
+ .brighter(), "Panel.background"));
+ g2d.fill(contour);
+ g2d.setColor(FlamingoUtilities.getBorderColor().darker());
+ g2d.draw(contour);
+ }
+
+ boolean ltr = getComponentOrientation().isLeftToRight();
+ int maxX = 0;
+ int minX = getWidth();
+ if (this.getComponentCount() == 0) {
+ maxX = 1;
+ minX = getWidth() - 1;
+ if (applicationMenuButton.isVisible()) {
+ maxX += applicationMenuButton.getX()
+ + applicationMenuButton.getWidth();
+ minX = applicationMenuButton.getX() - 1;
+ }
+ } else {
+ for (int i = 0; i < this.getComponentCount(); i++) {
+ Component taskBarComp = this.getComponent(i);
+ maxX = Math.max(maxX, taskBarComp.getX()
+ + taskBarComp.getWidth());
+ minX = Math.min(minX, taskBarComp.getX());
+ }
+ }
+ int height = getHeight();
+ if (ltr) {
+ g2d.drawLine(maxX, height - 1, getWidth(), height - 1);
+ } else {
+ g2d.drawLine(0, height - 1, minX, height - 1);
+ }
+
+ //int contourMaxX = (contour != null) ? (int) contour.getBounds2D()
+ // .getMaxX() + 6 : 6;
+ int contourMinX = (contour != null) ? (int) contour.getBounds2D()
+ .getMinX() - 6 : 6;
+
+ // contextual task group headers
+ if (!isShowingScrollsForTaskToggleButtons()) {
+ g2d.setComposite(AlphaComposite.SrcOver);
+ // the taskbar panel is not at the zero X coordinate of the
+ // ribbon
+ g2d.translate(-this.getBounds().x, 0);
+ for (int i = 0; i < ribbon.getContextualTaskGroupCount(); i++) {
+ RibbonContextualTaskGroup taskGroup = ribbon
+ .getContextualTaskGroup(i);
+ if (!ribbon.isVisible(taskGroup))
+ continue;
+ Rectangle taskGroupBounds = getContextualTaskGroupBounds(taskGroup);
+
+ Color hueColor = taskGroup.getHueColor();
+ Paint paint = new GradientPaint(
+ 0,
+ 0,
+ FlamingoUtilities.getAlphaColor(hueColor, 0),
+ 0,
+ height,
+ FlamingoUtilities
+ .getAlphaColor(
+ hueColor,
+ (int) (255 * RibbonContextualTaskGroup.HUE_ALPHA)));
+ // translucent gradient paint
+ g2d.setPaint(paint);
+ int startX = ltr ? taskGroupBounds.x : Math.min(
+ contourMinX, taskGroupBounds.x);
+ int width = ltr ? taskGroupBounds.x + taskGroupBounds.width
+ - startX : Math.min(taskGroupBounds.x
+ + taskGroupBounds.width, contourMinX)
+ - startX;
+
+ if (width > 0) {
+ g2d.fillRect(startX, 0, width, height);
+ // and a solid line at the bottom
+ g2d.setColor(hueColor);
+ g2d.drawLine(startX + 1, height - 1, startX + width,
+ height - 1);
+
+ // task group title
+ g2d.setColor(FlamingoUtilities.getColor(Color.black,
+ "Button.foreground"));
+ FontMetrics fm = this.getFontMetrics(ribbon.getFont());
+ int yOffset = (height + fm.getHeight()) / 2
+ - fm.getDescent();
+ int availableTextWidth = width - 10;
+ String titleToShow = taskGroup.getTitle();
+ if (fm.stringWidth(titleToShow) > availableTextWidth) {
+ while (true) {
+ if (titleToShow.length() == 0)
+ break;
+ if (fm.stringWidth(titleToShow + "...") <= availableTextWidth)
+ break;
+ titleToShow = titleToShow.substring(0,
+ titleToShow.length() - 1);
+ }
+ titleToShow += "...";
+ }
+ if (ltr) {
+ BasicGraphicsUtils.drawString(g2d, titleToShow, -1,
+ startX + 5, yOffset);
+ } else {
+ BasicGraphicsUtils.drawString(g2d, titleToShow, -1,
+ startX + width - 5
+ - fm.stringWidth(titleToShow),
+ yOffset);
+ }
+
+ // separator lines
+ Color color = FlamingoUtilities.getBorderColor();
+ g2d.setPaint(new GradientPaint(0, 0, FlamingoUtilities
+ .getAlphaColor(color, 0), 0, height, color));
+ // left line
+ g2d.drawLine(startX, 0, startX, height);
+ // right line
+ g2d.drawLine(startX + width, 0, startX + width, height);
+ }
+ }
+ }
+
+ g2d.dispose();
+
+ }
+
+ /**
+ * Returns the outline of this taskbar panel.
+ *
+ * @param taskbarPanel
+ * the taskbar panel to outline
+ * @return The outline of this taskbar panel.
+ */
+ protected Shape getOutline(TaskbarPanel taskbarPanel) {
+ double height = this.getHeight() - 1;
+ boolean ltr = taskbarPanel.getComponentOrientation()
+ .isLeftToRight();
+ if (this.getComponentCount() == 0) {
+ if (applicationMenuButton.isVisible()) {
+ // no taskbar components
+ if (ltr) {
+ int x = 1;
+ if (applicationMenuButton.isVisible()) {
+ x += applicationMenuButton.getX()
+ + applicationMenuButton.getWidth();
+ }
+ return new Arc2D.Double(x - 1 - 2 * height, 0,
+ 2 * height, 2 * height, 0, 90, Arc2D.OPEN);
+ } else {
+ int x = taskbarPanel.getWidth() - 1;
+ if (applicationMenuButton.isVisible()) {
+ x = applicationMenuButton.getX() - 1;
+ }
+ return new Arc2D.Double(x + 1, 0, 2 * height,
+ 2 * height, 90, 90, Arc2D.OPEN);
+ }
+ } else {
+ return null;
+ }
+ } else {
+ int minX = this.getWidth();
+ int maxX = 0;
+ for (int i = 0; i < this.getComponentCount(); i++) {
+ Component taskBarComp = this.getComponent(i);
+ minX = Math.min(minX, taskBarComp.getX());
+ maxX = Math.max(maxX, taskBarComp.getX()
+ + taskBarComp.getWidth());
+ }
+
+ float radius = (float) height / 2.0f;
+
+ GeneralPath outline = new GeneralPath();
+
+ if (ltr) {
+ // top left corner
+ if (applicationMenuButton.isVisible()) {
+ outline.moveTo(minX + 5 - 2 * radius, 0);
+ } else {
+ outline.moveTo(minX - 1, 0);
+ }
+ // top right corner
+ outline.lineTo(maxX, 0);
+ // right arc
+ outline.append(new Arc2D.Double(maxX - radius, 0, height,
+ height, 90, -180, Arc2D.OPEN), true);
+ // bottom left corner
+ outline.lineTo(minX - 1, height);
+ if (applicationMenuButton.isVisible()) {
+ // left arc
+ outline.append(new Arc2D.Double(minX - 1 - 2 * height,
+ 0, 2 * height, 2 * height, 0, 90, Arc2D.OPEN),
+ true);
+ } else {
+ outline.lineTo(minX - 1, 0);
+ }
+ } else {
+ // top right corner
+ if (applicationMenuButton.isVisible()) {
+ outline.moveTo(maxX - 5 + 2 * radius, 0);
+ } else {
+ outline.moveTo(maxX - 1, 0);
+ }
+ // top left corner
+ outline.lineTo(minX, 0);
+ // left arc
+ outline.append(new Arc2D.Double(minX - radius, 0, height,
+ height, 90, 180, Arc2D.OPEN), true);
+ // bottom right corner
+ outline.lineTo(maxX - 1, height);
+ if (applicationMenuButton.isVisible()) {
+ outline.append(new Arc2D.Double(maxX - 1, 0,
+ 2 * height, 2 * height, 180, -90, Arc2D.OPEN),
+ true);
+ } else {
+ outline.lineTo(maxX + 1, 0);
+ }
+
+ }
+
+ return outline;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#getPreferredSize()
+ */
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension result = super.getPreferredSize();
+ return new Dimension(result.width + result.height / 2,
+ result.height);
+ }
+ }
+
+ protected static class BandHostPanel extends JPanel {
+ }
+
+ /**
+ * Layout for the band host panel.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class BandHostPanelLayout implements LayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ // Insets ins = c.getInsets();
+ int maxPrefBandHeight = 0;
+ if (ribbon.getTaskCount() > 0) {
+ RibbonTask selectedTask = ribbon.getSelectedTask();
+ for (AbstractRibbonBand<?> ribbonBand : selectedTask.getBands()) {
+ int bandPrefHeight = ribbonBand.getPreferredSize().height;
+ Insets bandInsets = ribbonBand.getInsets();
+ maxPrefBandHeight = Math
+ .max(maxPrefBandHeight, bandPrefHeight
+ + bandInsets.top + bandInsets.bottom);
+ }
+ }
+
+ return new Dimension(c.getWidth(), maxPrefBandHeight);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ // go over all ribbon bands and sum the width
+ // of ribbon buttons (of collapsed state)
+ // Insets ins = c.getInsets();
+ int width = 0;
+ int maxMinBandHeight = 0;
+ int gap = getBandGap();
+
+ // minimum is when all the tasks are collapsed
+ RibbonTask selectedTask = ribbon.getSelectedTask();
+ // System.out.println(selectedTask.getTitle() + " min width");
+ for (AbstractRibbonBand ribbonBand : selectedTask.getBands()) {
+ int bandPrefHeight = ribbonBand.getMinimumSize().height;
+ Insets bandInsets = ribbonBand.getInsets();
+ RibbonBandUI bandUI = ribbonBand.getUI();
+ int preferredCollapsedWidth = bandUI
+ .getPreferredCollapsedWidth()
+ + bandInsets.left + bandInsets.right;
+ width += preferredCollapsedWidth;
+ // System.out.println("\t" + ribbonBand.getTitle() + ":" +
+ // preferredCollapsedWidth);
+ maxMinBandHeight = Math.max(maxMinBandHeight, bandPrefHeight
+ // + bandInsets.top + bandInsets.bottom
+ );
+ }
+ // add inter-band gaps
+ width += gap * (selectedTask.getBandCount() + 1);
+ // System.out.println("\t" + gap + "*" +
+ // (selectedTask.getBandCount() + 1));
+
+ // System.out.println(selectedTask.getTitle() + " min width:" +
+ // width);
+
+ // System.out.println("Returning min height of " +
+ // maxMinBandHeight);
+
+ return new Dimension(width, maxMinBandHeight);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ // System.err.println("Layout of band host panel " + c.getWidth() +
+ // ":" + c.getHeight());
+ int bandGap = getBandGap();
+
+ // the top row - task bar components
+ int x;
+ int y = 0;
+
+ RibbonTask selectedTask = ribbon.getSelectedTask();
+ if (selectedTask == null)
+ return;
+
+ // check that the resize policies are still consistent
+ for (AbstractRibbonBand<?> band : selectedTask.getBands()) {
+ FlamingoUtilities.checkResizePoliciesConsistency(band);
+ }
+
+ // start with the most "permissive" resize policy for each band
+ for (AbstractRibbonBand<?> band : selectedTask.getBands()) {
+ List<RibbonBandResizePolicy> policies = band
+ .getResizePolicies();
+ RibbonBandResizePolicy last = policies.get(0);
+ band.setCurrentResizePolicy(last);
+ }
+
+ int availableBandHeight = c.getHeight();
+ int availableWidth = c.getWidth();
+ if (selectedTask.getBandCount() > 0) {
+ RibbonBandResizeSequencingPolicy resizeSequencingPolicy = selectedTask
+ .getResizeSequencingPolicy();
+ resizeSequencingPolicy.reset();
+ AbstractRibbonBand<?> currToTakeFrom = resizeSequencingPolicy
+ .next();
+ while (true) {
+ // check whether all bands have the current resize
+ // policy as their last (most restrictive) registered policy
+ boolean noMore = true;
+ for (AbstractRibbonBand<?> band : selectedTask.getBands()) {
+ RibbonBandResizePolicy currentResizePolicy = band
+ .getCurrentResizePolicy();
+ List<RibbonBandResizePolicy> resizePolicies = band
+ .getResizePolicies();
+ if (currentResizePolicy != resizePolicies
+ .get(resizePolicies.size() - 1)) {
+ noMore = false;
+ break;
+ }
+ }
+ if (noMore)
+ break;
+
+ // get the current preferred width of the bands
+ int totalWidth = 0;
+ // System.out.println("Iteration");
+ for (AbstractRibbonBand<?> ribbonBand : selectedTask
+ .getBands()) {
+ RibbonBandResizePolicy currentResizePolicy = ribbonBand
+ .getCurrentResizePolicy();
+
+ Insets ribbonBandInsets = ribbonBand.getInsets();
+ AbstractBandControlPanel controlPanel = ribbonBand
+ .getControlPanel();
+ if (controlPanel == null) {
+ controlPanel = ribbonBand.getPopupRibbonBand()
+ .getControlPanel();
+ }
+ Insets controlPanelInsets = controlPanel.getInsets();
+ int controlPanelGap = controlPanel.getUI()
+ .getLayoutGap();
+ int ribbonBandHeight = availableBandHeight
+ - ribbonBandInsets.top
+ - ribbonBandInsets.bottom;
+ int availableHeight = ribbonBandHeight
+ - ribbonBand.getUI().getBandTitleHeight();
+ if (controlPanel != null) {
+ availableHeight = availableHeight
+ - controlPanelInsets.top
+ - controlPanelInsets.bottom;
+ }
+ int preferredWidth = currentResizePolicy
+ .getPreferredWidth(availableHeight,
+ controlPanelGap)
+ + ribbonBandInsets.left
+ + ribbonBandInsets.right;
+ totalWidth += preferredWidth + bandGap;
+ // System.out.println("\t"
+ // + ribbonBand.getTitle()
+ // + ":"
+ // + currentResizePolicy.getClass()
+ // .getSimpleName() + ":" + preferredWidth
+ // + " under " + availableHeight + " with "
+ // + controlPanel.getComponentCount()
+ // + " children");
+ }
+ // System.out.println("\t:Total:" + totalWidth + "("
+ // + availableWidth + ")");
+ // System.out.println("\n");
+ if (totalWidth < availableWidth)
+ break;
+
+ // try to take from the currently rotating band
+ List<RibbonBandResizePolicy> policies = currToTakeFrom
+ .getResizePolicies();
+ int currPolicyIndex = policies.indexOf(currToTakeFrom
+ .getCurrentResizePolicy());
+ if (currPolicyIndex == (policies.size() - 1)) {
+ // nothing to take
+ } else {
+ currToTakeFrom.setCurrentResizePolicy(policies
+ .get(currPolicyIndex + 1));
+ }
+ currToTakeFrom = resizeSequencingPolicy.next();
+ }
+ }
+
+ boolean ltr = c.getComponentOrientation().isLeftToRight();
+ x = ltr ? 1 : c.getWidth() - 1;
+ // System.out.println("Will get [" + availableWidth + "]:");
+ for (AbstractRibbonBand<?> ribbonBand : selectedTask.getBands()) {
+ Insets ribbonBandInsets = ribbonBand.getInsets();
+ RibbonBandResizePolicy currentResizePolicy = ribbonBand
+ .getCurrentResizePolicy();
+ AbstractBandControlPanel controlPanel = ribbonBand
+ .getControlPanel();
+ if (controlPanel == null) {
+ controlPanel = ribbonBand.getPopupRibbonBand()
+ .getControlPanel();
+ }
+ Insets controlPanelInsets = controlPanel.getInsets();
+ int controlPanelGap = controlPanel.getUI().getLayoutGap();
+ int ribbonBandHeight = availableBandHeight;
+ // - ribbonBandInsets.top - ribbonBandInsets.bottom;
+ int availableHeight = ribbonBandHeight - ribbonBandInsets.top
+ - ribbonBandInsets.bottom
+ - ribbonBand.getUI().getBandTitleHeight();
+ if (controlPanelInsets != null) {
+ availableHeight = availableHeight - controlPanelInsets.top
+ - controlPanelInsets.bottom;
+ }
+
+ int requiredBandWidth = currentResizePolicy.getPreferredWidth(
+ availableHeight, controlPanelGap)
+ + ribbonBandInsets.left + ribbonBandInsets.right;
+
+ if (ltr) {
+ ribbonBand.setBounds(x, y, requiredBandWidth,
+ ribbonBandHeight);
+ } else {
+ ribbonBand.setBounds(x - requiredBandWidth, y,
+ requiredBandWidth, ribbonBandHeight);
+ }
+
+ // System.out.println("\t" + ribbonBand.getTitle() + ":"
+ // + currentResizePolicy.getClass().getSimpleName() + ":"
+ // + requiredBandWidth + " under " + ribbonBandHeight);
+
+ if (ribbonBand.getHeight() > 0) {
+ ribbonBand.doLayout();
+ }
+
+ if (ltr) {
+ x += (requiredBandWidth + bandGap);
+ } else {
+ x -= (requiredBandWidth + bandGap);
+ }
+
+ }
+ // System.out.println();
+ }
+ }
+
+ protected class TaskToggleButtonsHostPanel extends JPanel {
+ public static final String IS_SQUISHED = "flamingo.internal.ribbon.taskToggleButtonsHostPanel.isSquished";
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ this.paintContextualTaskGroupsOutlines(g);
+ if (Boolean.TRUE.equals(this.getClientProperty(IS_SQUISHED))) {
+ this.paintTaskOutlines(g);
+ }
+ }
+
+ protected void paintTaskOutlines(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ Color color = FlamingoUtilities.getBorderColor();
+ Paint paint = new GradientPaint(0, 0, FlamingoUtilities
+ .getAlphaColor(color, 0), 0, getHeight(), color);
+ g2d.setPaint(paint);
+
+ Set<RibbonTask> tasksWithTrailingSeparators = new HashSet<RibbonTask>();
+ // add all regular tasks except the last
+ for (int i = 0; i < ribbon.getTaskCount() - 1; i++) {
+ RibbonTask task = ribbon.getTask(i);
+ tasksWithTrailingSeparators.add(task);
+ // System.out.println("Added " + task.getTitle());
+ }
+ // add all tasks of visible contextual groups except last task in
+ // each group
+ for (int i = 0; i < ribbon.getContextualTaskGroupCount(); i++) {
+ RibbonContextualTaskGroup group = ribbon
+ .getContextualTaskGroup(i);
+ if (ribbon.isVisible(group)) {
+ for (int j = 0; j < group.getTaskCount() - 1; j++) {
+ RibbonTask task = group.getTask(j);
+ tasksWithTrailingSeparators.add(task);
+ // System.out.println("Added " + task.getTitle());
+ }
+ }
+ }
+
+ for (RibbonTask taskWithTrailingSeparator : tasksWithTrailingSeparators) {
+ JRibbonTaskToggleButton taskToggleButton = taskToggleButtons
+ .get(taskWithTrailingSeparator);
+ Rectangle bounds = taskToggleButton.getBounds();
+ int x = bounds.x + bounds.width + getTabButtonGap() / 2 - 1;
+ g2d.drawLine(x, 0, x, getHeight());
+ // System.out.println(taskWithTrailingSeparator.getTitle() + ":"
+ // + x);
+ }
+
+ g2d.dispose();
+ }
+
+ /**
+ * Paints the outline of the contextual task groups.
+ *
+ * @param g
+ * Graphics context.
+ */
+ protected void paintContextualTaskGroupsOutlines(Graphics g) {
+ for (int i = 0; i < ribbon.getContextualTaskGroupCount(); i++) {
+ RibbonContextualTaskGroup group = ribbon
+ .getContextualTaskGroup(i);
+ if (!ribbon.isVisible(group))
+ continue;
+ // go over all the tasks in this group and compute the union
+ // of bounds of the matching tab buttons
+ Rectangle rect = getContextualTaskGroupBounds(group);
+ rect.setLocation(SwingUtilities.convertPoint(ribbon, rect
+ .getLocation(), taskToggleButtonsScrollablePanel
+ .getView()));
+ this.paintContextualTaskGroupOutlines(g, group, rect);
+ }
+ }
+
+ /**
+ * Paints the outline of the specified contextual task group.
+ *
+ * @param g
+ * Graphics context.
+ * @param group
+ * Contextual task group.
+ * @param groupBounds
+ * Contextual task group bounds.
+ */
+ protected void paintContextualTaskGroupOutlines(Graphics g,
+ RibbonContextualTaskGroup group, Rectangle groupBounds) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ Color color = FlamingoUtilities.getBorderColor();
+
+ Paint paint = new GradientPaint(0, groupBounds.y, color, 0,
+ groupBounds.y + groupBounds.height, FlamingoUtilities
+ .getAlphaColor(color, 0));
+ g2d.setPaint(paint);
+ // left line
+ int x = groupBounds.x;
+ g2d.drawLine(x, groupBounds.y, x, groupBounds.y
+ + groupBounds.height);
+ // right line
+ x = groupBounds.x + groupBounds.width;
+ g2d.drawLine(x, groupBounds.y, x, groupBounds.y
+ + groupBounds.height);
+
+ g2d.dispose();
+ }
+
+ // @Override
+ // protected void paintComponent(Graphics g) {
+ // //g.setColor(new Color(255, 200, 200));
+ // //g.fillRect(0, 0, getWidth(), getHeight());
+ // // g.setColor(Color.blue.darker());
+ // // g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
+ // //System.err.println(System.currentTimeMillis() + ": tt-repaint");
+ // }
+ //
+ // @Override
+ // protected void paintBorder(Graphics g) {
+ // }
+ //
+ // @Override
+ // public void setBounds(int x, int y, int width, int height) {
+ // System.out.println("Host : " + x + ":" + y + ":" + width + ":"
+ // + height);
+ // super.setBounds(x, y, width, height);
+ // }
+ }
+
+ /**
+ * Layout for the band host panel.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class TaskToggleButtonsHostPanelLayout implements LayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ int tabButtonGap = getTabButtonGap();
+ int taskToggleButtonHeight = getTaskToggleButtonHeight();
+
+ int totalTaskButtonsWidth = 0;
+ List<RibbonTask> visibleTasks = getCurrentlyShownRibbonTasks();
+ for (RibbonTask task : visibleTasks) {
+ JRibbonTaskToggleButton tabButton = taskToggleButtons.get(task);
+ int pw = tabButton.getPreferredSize().width;
+ totalTaskButtonsWidth += (pw + tabButtonGap);
+ }
+
+ return new Dimension(totalTaskButtonsWidth, taskToggleButtonHeight);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ int tabButtonGap = getTabButtonGap();
+ int taskToggleButtonHeight = getTaskToggleButtonHeight();
+
+ int totalTaskButtonsWidth = 0;
+ List<RibbonTask> visibleTasks = getCurrentlyShownRibbonTasks();
+ for (RibbonTask task : visibleTasks) {
+ JRibbonTaskToggleButton tabButton = taskToggleButtons.get(task);
+ int pw = tabButton.getMinimumSize().width;
+ totalTaskButtonsWidth += (pw + tabButtonGap);
+ }
+
+ return new Dimension(totalTaskButtonsWidth, taskToggleButtonHeight);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ int y = 0;
+ int tabButtonGap = getTabButtonGap();
+ int taskToggleButtonHeight = getTaskToggleButtonHeight();
+
+ int totalPrefWidth = 0;
+ int totalMinWidth = 0;
+ List<RibbonTask> visibleTasks = getCurrentlyShownRibbonTasks();
+ Map<JRibbonTaskToggleButton, Integer> diffMap = new HashMap<JRibbonTaskToggleButton, Integer>();
+ int totalDiff = 0;
+ for (RibbonTask task : visibleTasks) {
+ JRibbonTaskToggleButton tabButton = taskToggleButtons.get(task);
+ int pw = tabButton.getPreferredSize().width;
+ int mw = tabButton.getMinimumSize().width;
+ diffMap.put(tabButton, pw - mw);
+ totalDiff += (pw - mw);
+ totalPrefWidth += pw;
+ totalMinWidth += mw;
+ }
+ totalPrefWidth += tabButtonGap * visibleTasks.size();
+ totalMinWidth += tabButtonGap * visibleTasks.size();
+
+ boolean ltr = c.getComponentOrientation().isLeftToRight();
+
+ // do we have enough width?
+ if (totalPrefWidth <= c.getWidth()) {
+ // compute bounds for the tab buttons
+ int x = ltr ? 0 : c.getWidth();
+ for (RibbonTask task : visibleTasks) {
+ JRibbonTaskToggleButton tabButton = taskToggleButtons
+ .get(task);
+ int pw = tabButton.getPreferredSize().width;
+ if (ltr) {
+ tabButton.setBounds(x, y + 1, pw,
+ taskToggleButtonHeight - 1);
+ x += (pw + tabButtonGap);
+ } else {
+ tabButton.setBounds(x - pw, y + 1, pw,
+ taskToggleButtonHeight - 1);
+ x -= (pw + tabButtonGap);
+ }
+ tabButton.setActionRichTooltip(null);
+ }
+ ((JComponent) c).putClientProperty(
+ TaskToggleButtonsHostPanel.IS_SQUISHED, null);
+ } else {
+ if (totalMinWidth > c.getWidth()) {
+ throw new IllegalStateException(
+ "Available width not enough to host minimized task tab buttons");
+ }
+ int x = ltr ? 0 : c.getWidth();
+ // how much do we need to take from each toggle button?
+ int toDistribute = totalPrefWidth - c.getWidth() + 2;
+ for (RibbonTask task : visibleTasks) {
+ JRibbonTaskToggleButton tabButton = taskToggleButtons
+ .get(task);
+ int pw = tabButton.getPreferredSize().width;
+ int delta = (toDistribute * diffMap.get(tabButton) / totalDiff);
+ int finalWidth = pw - delta;
+ if (ltr) {
+ tabButton.setBounds(x, y + 1, finalWidth,
+ taskToggleButtonHeight - 1);
+ x += (finalWidth + tabButtonGap);
+ } else {
+ tabButton.setBounds(x - finalWidth, y + 1, finalWidth,
+ taskToggleButtonHeight - 1);
+ x -= (finalWidth + tabButtonGap);
+ }
+ // show the tooltip with the full title
+ RichTooltip tooltip = new RichTooltip();
+ tooltip.setTitle(task.getTitle());
+ tabButton.setActionRichTooltip(tooltip);
+ }
+ ((JComponent) c).putClientProperty(
+ TaskToggleButtonsHostPanel.IS_SQUISHED, Boolean.TRUE);
+ }
+ }
+ }
+
+ protected void syncRibbonState() {
+ // remove all existing ribbon bands
+ BandHostPanel bandHostPanel = this.bandScrollablePanel.getView();
+ bandHostPanel.removeAll();
+
+ // remove all the existing task toggle buttons
+ TaskToggleButtonsHostPanel taskToggleButtonsHostPanel = this.taskToggleButtonsScrollablePanel
+ .getView();
+ taskToggleButtonsHostPanel.removeAll();
+
+ // remove the help button
+ if (this.helpPanel != null) {
+ this.ribbon.remove(this.helpPanel);
+ this.helpPanel = null;
+ this.helpButton = null;
+ }
+
+ // go over all visible ribbon tasks and create a toggle button
+ // for each one of them
+ List<RibbonTask> visibleTasks = this.getCurrentlyShownRibbonTasks();
+ final RibbonTask selectedTask = this.ribbon.getSelectedTask();
+ for (final RibbonTask task : visibleTasks) {
+ final JRibbonTaskToggleButton taskToggleButton = new JRibbonTaskToggleButton(
+ task);
+ taskToggleButton.setKeyTip(task.getKeyTip());
+ // wire listener to select the task when the button is
+ // selected
+ taskToggleButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ scrollAndRevealTaskToggleButton(taskToggleButton);
+
+ ribbon.setSelectedTask(task);
+
+ // System.out.println("Button click on "
+ // + task.getTitle() + ", ribbon minimized? "
+ // + ribbon.isMinimized());
+
+ if (ribbon.isMinimized()) {
+ if (Boolean.TRUE.equals(ribbon
+ .getClientProperty(JUST_MINIMIZED))) {
+ ribbon.putClientProperty(JUST_MINIMIZED,
+ null);
+ return;
+ }
+
+ // special case - do we have this task currently
+ // shown in a popup?
+ List<PopupPanelManager.PopupInfo> popups = PopupPanelManager
+ .defaultManager().getShownPath();
+ if (popups.size() > 0) {
+ for (PopupPanelManager.PopupInfo popup : popups) {
+ if (popup.getPopupOriginator() == taskToggleButton) {
+ // hide all popups and return (hides
+ // the task popup and does not
+ // show any additional popup).
+ PopupPanelManager.defaultManager()
+ .hidePopups(null);
+ return;
+ }
+ }
+ }
+
+ PopupPanelManager.defaultManager().hidePopups(
+ null);
+ ribbon.remove(bandScrollablePanel);
+
+ int prefHeight = bandScrollablePanel.getView()
+ .getPreferredSize().height;
+ Insets ins = ribbon.getInsets();
+ prefHeight += ins.top + ins.bottom;
+ AbstractRibbonBand band = (ribbon
+ .getSelectedTask().getBandCount() > 0) ? ribbon
+ .getSelectedTask().getBand(0)
+ : null;
+ if (band != null) {
+ Insets bandIns = band.getInsets();
+ prefHeight += bandIns.top + bandIns.bottom;
+ }
+
+ // System.out.println(prefHeight
+ // + ":"
+ // + bandScrollablePanel.getView()
+ // .getComponentCount());
+
+ JPopupPanel popupPanel = new BandHostPopupPanel(
+ bandScrollablePanel, new Dimension(
+ ribbon.getWidth(), prefHeight));
+
+ int x = ribbon.getLocationOnScreen().x;
+ int y = ribbon.getLocationOnScreen().y
+ + ribbon.getHeight();
+
+ // make sure that the popup stays in bounds
+
+ // first, get the graphics config of where the mouse pointer curently is at
+ PointerInfo pi = MouseInfo.getPointerInfo();
+ GraphicsConfiguration mgc = null;
+ if (pi != null) {
+ GraphicsDevice gd = pi.getDevice();
+ for (GraphicsConfiguration gc : gd.getConfigurations()) {
+ if (gc.getBounds().contains(pi.getLocation())) {
+ mgc = gc;
+ break;
+ }
+ }
+ }
+ if (mgc == null) {
+ mgc = ribbon.getGraphicsConfiguration();
+ }
+ Rectangle scrBounds = mgc.getBounds();
+ int pw = popupPanel.getPreferredSize().width;
+
+ // falling off the left? trim and shift
+ if (x < scrBounds.x) {
+ pw = pw - scrBounds.x + x;
+ x = scrBounds.x;
+ }
+
+ // falling off the right? trim it's width
+ if ((x + pw) > (scrBounds.x + scrBounds.width)) {
+ pw = scrBounds.width + scrBounds.x - x;
+ }
+
+ // falling off the bottom? flip it above the tabs
+ int ph = popupPanel.getPreferredSize().height;
+ if ((y + ph) > (scrBounds.y + scrBounds.height)) {
+ y = ribbon.getLocationOnScreen().y - prefHeight;
+ }
+
+ // get the popup and show it
+ popupPanel.setPreferredSize(new Dimension(
+ pw, prefHeight));
+ Popup popup = PopupFactory.getSharedInstance()
+ .getPopup(taskToggleButton, popupPanel,
+ x, y);
+ PopupPanelManager.PopupListener tracker = new PopupPanelManager.PopupListener() {
+ @Override
+ public void popupShown(PopupEvent event) {
+ JComponent originator = event
+ .getPopupOriginator();
+ if (originator instanceof JRibbonTaskToggleButton) {
+ bandScrollablePanel.doLayout();
+ bandScrollablePanel.repaint();
+ }
+ }
+
+ @Override
+ public void popupHidden(PopupEvent event) {
+ JComponent originator = event
+ .getPopupOriginator();
+ if (originator instanceof JRibbonTaskToggleButton) {
+ ribbon.add(bandScrollablePanel);
+ PopupPanelManager.defaultManager()
+ .removePopupListener(this);
+ ribbon.revalidate();
+ ribbon.doLayout();
+ ribbon.repaint();
+ }
+ }
+ };
+ PopupPanelManager.defaultManager()
+ .addPopupListener(tracker);
+ PopupPanelManager.defaultManager().addPopup(
+ taskToggleButton, popup, popupPanel);
+ }
+ }
+ });
+ }
+ });
+ // wire listener to toggle ribbon minimization on double
+ // mouse click
+ taskToggleButton.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if ((ribbon.getSelectedTask() == task)
+ && (e.getClickCount() == 2)) {
+ boolean wasMinimized = ribbon.isMinimized();
+ ribbon.setMinimized(!wasMinimized);
+ if (!wasMinimized) {
+ // fix for issue 69 - mark the ribbon as
+ // "just minimized" to prevent the action handler
+ // of the toggle button to show the ribbon in
+ // popup mode
+ ribbon.putClientProperty(JUST_MINIMIZED,
+ Boolean.TRUE);
+ }
+ }
+ }
+ });
+ // set the background hue color on the tab buttons
+ // of tasks in contextual groups
+ if (task.getContextualGroup() != null) {
+ taskToggleButton.setContextualGroupHueColor(task
+ .getContextualGroup().getHueColor());
+ }
+
+ taskToggleButton.putClientProperty(
+ BasicCommandButtonUI.DONT_DISPOSE_POPUPS, Boolean.TRUE);
+
+ this.taskToggleButtonGroup.add(taskToggleButton);
+ taskToggleButtonsHostPanel.add(taskToggleButton);
+ this.taskToggleButtons.put(task, taskToggleButton);
+ }
+
+ JRibbonTaskToggleButton toSelect = this.taskToggleButtons
+ .get(selectedTask);
+ if (toSelect != null) {
+ toSelect.getActionModel().setSelected(true);
+ }
+
+ for (int i = 0; i < this.ribbon.getTaskCount(); i++) {
+ RibbonTask task = this.ribbon.getTask(i);
+ for (AbstractRibbonBand band : task.getBands()) {
+ bandHostPanel.add(band);
+ band.setVisible(selectedTask == task);
+ }
+ }
+ for (int i = 0; i < this.ribbon.getContextualTaskGroupCount(); i++) {
+ RibbonContextualTaskGroup taskGroup = this.ribbon
+ .getContextualTaskGroup(i);
+ for (int j = 0; j < taskGroup.getTaskCount(); j++) {
+ RibbonTask task = taskGroup.getTask(j);
+ for (AbstractRibbonBand band : task.getBands()) {
+ bandHostPanel.add(band);
+ band.setVisible(selectedTask == task);
+ }
+ }
+ }
+
+ ActionListener helpListener = this.ribbon.getHelpActionListener();
+ List<Component> helpComponents = (List<Component>) ribbon.getClientProperty(HELP_PANEL_COMPONENTS);
+ if (helpListener != null || helpComponents != null) {
+ helpPanel = Box.createHorizontalBox();
+ if (helpComponents != null) {
+ for (Component comp : helpComponents) {
+ helpPanel.add(comp);
+ helpPanel.add(Box.createHorizontalStrut(3)); // FIXME make resolution independent
+ }
+ }
+
+ if (helpListener != null) {
+ this.helpButton = new JCommandButton("", this.ribbon.getHelpIcon());
+ this.helpButton.setActionRichTooltip(this.ribbon.getHelpRichTooltip());
+ this.helpButton.setDisplayState(CommandButtonDisplayState.SMALL);
+ this.helpButton.setCommandButtonKind(CommandButtonKind.ACTION_ONLY);
+ this.helpButton.getActionModel().addActionListener(helpListener);
+
+ helpPanel.add(helpButton);
+ }
+
+ this.ribbon.add(this.helpPanel);
+ }
+
+ this.ribbon.revalidate();
+ this.ribbon.repaint();
+ }
+
+ /**
+ * Returns the list of currently shown ribbon tasks. This method is for
+ * internal use only.
+ *
+ * @return The list of currently shown ribbon tasks.
+ */
+ protected List<RibbonTask> getCurrentlyShownRibbonTasks() {
+ List<RibbonTask> result = new ArrayList<RibbonTask>();
+
+ // add all regular tasks
+ for (int i = 0; i < this.ribbon.getTaskCount(); i++) {
+ RibbonTask task = this.ribbon.getTask(i);
+ result.add(task);
+ }
+ // add all tasks of visible contextual groups
+ for (int i = 0; i < this.ribbon.getContextualTaskGroupCount(); i++) {
+ RibbonContextualTaskGroup group = this.ribbon
+ .getContextualTaskGroup(i);
+ if (this.ribbon.isVisible(group)) {
+ for (int j = 0; j < group.getTaskCount(); j++) {
+ RibbonTask task = group.getTask(j);
+ result.add(task);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ protected boolean isUsingTitlePane() {
+ return Boolean.TRUE.equals(ribbon
+ .getClientProperty(IS_USING_TITLE_PANE));
+ }
+
+ protected void syncApplicationMenuTips() {
+ this.applicationMenuButton.setPopupRichTooltip(this.ribbon
+ .getApplicationMenuRichTooltip());
+ this.applicationMenuButton.setPopupKeyTip(this.ribbon
+ .getApplicationMenuKeyTip());
+ }
+
+ @Override
+ public boolean isShowingScrollsForTaskToggleButtons() {
+ return this.taskToggleButtonsScrollablePanel.isShowingScrollButtons();
+ }
+
+ @Override
+ public boolean isShowingScrollsForBands() {
+ return this.bandScrollablePanel.isShowingScrollButtons();
+ }
+
+ public Map<RibbonTask, JRibbonTaskToggleButton> getTaskToggleButtons() {
+ return Collections.unmodifiableMap(taskToggleButtons);
+ }
+
+ protected static class BandHostPopupPanel extends JPopupPanel {
+ /**
+ * The main component of <code>this</code> popup panel. Can be
+ * <code>null</code>.
+ */
+ // protected Component component;
+ public BandHostPopupPanel(Component component, Dimension originalSize) {
+ // this.component = component;
+ this.setLayout(new BorderLayout());
+ this.add(component, BorderLayout.CENTER);
+ // System.out.println("Popup dim is " + originalSize);
+ this.setPreferredSize(originalSize);
+ this.setSize(originalSize);
+ }
+ }
+
+ @Override
+ public void handleMouseWheelEvent(MouseWheelEvent e) {
+ // no mouse wheel scrolling when the ribbon is minimized
+ if (ribbon.isMinimized())
+ return;
+
+ // get the visible tasks
+ final List<RibbonTask> visibleTasks = getCurrentlyShownRibbonTasks();
+ if (visibleTasks.size() == 0)
+ return;
+
+ int delta = e.getWheelRotation();
+ if (delta == 0)
+ return;
+
+ // find the index of the currently selected task
+ int currSelectedTaskIndex = visibleTasks.indexOf(ribbon
+ .getSelectedTask());
+
+ // compute the next task
+ if (!ribbon.getComponentOrientation().isLeftToRight())
+ delta = -delta;
+ int newSelectedTaskIndex = currSelectedTaskIndex
+ + ((delta > 0) ? 1 : -1);
+ if (newSelectedTaskIndex < 0)
+ return;
+ if (newSelectedTaskIndex >= visibleTasks.size())
+ return;
+
+ final int indexToSet = newSelectedTaskIndex;
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ ribbon
+ .setCursor(Cursor
+ .getPredefinedCursor(Cursor.WAIT_CURSOR));
+ ribbon.setSelectedTask(visibleTasks.get(indexToSet));
+ ribbon.setCursor(Cursor
+ .getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ });
+ }
+
+ protected void scrollAndRevealTaskToggleButton(
+ final JRibbonTaskToggleButton taskToggleButton) {
+ // scroll the viewport of the scrollable panel
+ // so that the button is fully viewed.
+ Point loc = SwingUtilities.convertPoint(taskToggleButton.getParent(),
+ taskToggleButton.getLocation(),
+ taskToggleButtonsScrollablePanel.getView());
+ taskToggleButtonsScrollablePanel.scrollToIfNecessary(loc.x,
+ taskToggleButton.getWidth());
+ }
+
+ @Override
+ public synchronized void setApplicationIcon(ResizableIcon applicationIcon) {
+ super.setApplicationIcon(applicationIcon);
+ applicationMenuButton.setIcon(applicationIcon);
+ }
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/CommandButtonLayoutManagerBigFixed.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/CommandButtonLayoutManagerBigFixed.java
new file mode 100644
index 0000000..ef11c42
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/CommandButtonLayoutManagerBigFixed.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.util.ArrayList;
+
+import javax.swing.JSeparator;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+public class CommandButtonLayoutManagerBigFixed implements
+ CommandButtonLayoutManager {
+
+ @Override
+ public int getPreferredIconSize() {
+ return 32;
+ }
+
+ @Override
+ public Dimension getPreferredSize(AbstractCommandButton commandButton) {
+ Insets borderInsets = commandButton.getInsets();
+ int bx = borderInsets.left + borderInsets.right;
+ int by = borderInsets.top + borderInsets.bottom;
+ FontMetrics fm = commandButton.getFontMetrics(commandButton.getFont());
+ JSeparator jsep = new JSeparator(JSeparator.VERTICAL);
+ int layoutVGap = FlamingoUtilities.getVLayoutGap(commandButton);
+
+ // icon, label
+ int fillTitleWidth = fm.stringWidth(commandButton.getText());
+
+ int widthFull = Math.max(this.getPreferredIconSize(), fillTitleWidth);
+
+ int heightFull = by + this.getPreferredIconSize() + layoutVGap
+ + jsep.getPreferredSize().width;
+ if (commandButton.getText() != null) {
+ heightFull += fm.getHeight();
+ }
+
+ widthFull = Math.max(widthFull, heightFull);
+ return new Dimension(bx + widthFull, heightFull);
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ }
+
+ @Override
+ public Point getKeyTipAnchorCenterPoint(AbstractCommandButton commandButton) {
+ // center of the bottom edge
+ return new Point(commandButton.getWidth() / 2, commandButton
+ .getHeight());
+ }
+
+ @Override
+ public CommandButtonLayoutInfo getLayoutInfo(
+ AbstractCommandButton commandButton, Graphics g) {
+ CommandButtonLayoutInfo result = new CommandButtonLayoutInfo();
+
+ result.actionClickArea = new Rectangle(0, 0, 0, 0);
+ result.popupClickArea = new Rectangle(0, 0, 0, 0);
+
+ Insets ins = commandButton.getInsets();
+
+ result.iconRect = new Rectangle();
+ result.popupActionRect = new Rectangle();
+
+ int width = commandButton.getWidth();
+ int height = commandButton.getHeight();
+
+ int x = ins.left;
+ int y = ins.top;
+
+ FontMetrics fm = g.getFontMetrics();
+ int labelHeight = fm.getAscent() + fm.getDescent();
+
+ JCommandButton.CommandButtonKind buttonKind = (commandButton instanceof JCommandButton) ? ((JCommandButton) commandButton)
+ .getCommandButtonKind()
+ : JCommandButton.CommandButtonKind.ACTION_ONLY;
+
+ result.isTextInActionArea = false;
+ if (buttonKind == JCommandButton.CommandButtonKind.ACTION_ONLY) {
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width;
+ result.actionClickArea.height = height;
+ result.isTextInActionArea = true;
+ }
+ if (buttonKind == JCommandButton.CommandButtonKind.POPUP_ONLY) {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+ }
+
+ JSeparator jsep = new JSeparator(JSeparator.VERTICAL);
+ // int layoutGap = FlamingoUtilities.getLayoutGap(commandButton);
+
+ ResizableIcon buttonIcon = commandButton.getIcon();
+
+ if (commandButton.getText() == null) {
+ y = ins.top
+ + (height - ins.top - ins.bottom - buttonIcon
+ .getIconHeight()) / 2;
+ }
+ result.iconRect.x = (width - buttonIcon.getIconWidth()) / 2;
+ result.iconRect.y = y;
+ result.iconRect.width = buttonIcon.getIconWidth();
+ result.iconRect.height = buttonIcon.getIconHeight();
+ y += buttonIcon.getIconHeight();
+
+ y += jsep.getPreferredSize().width;
+
+ TextLayoutInfo lineLayoutInfo = new TextLayoutInfo();
+ lineLayoutInfo.text = commandButton.getText();
+ lineLayoutInfo.textRect = new Rectangle();
+
+ int labelWidth = (int) fm.getStringBounds(commandButton.getText(), g)
+ .getWidth();
+
+ lineLayoutInfo.textRect.x = ins.left
+ + (width - labelWidth - ins.left - ins.right) / 2;
+ lineLayoutInfo.textRect.y = y;
+ lineLayoutInfo.textRect.width = labelWidth;
+ lineLayoutInfo.textRect.height = labelHeight;
+
+ result.textLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.textLayoutInfoList.add(lineLayoutInfo);
+
+ return result;
+ }
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/CommandButtonLayoutManagerBigFixedLandscape.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/CommandButtonLayoutManagerBigFixedLandscape.java
new file mode 100644
index 0000000..0bf4440
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/CommandButtonLayoutManagerBigFixedLandscape.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.util.ArrayList;
+
+import javax.swing.JSeparator;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+public class CommandButtonLayoutManagerBigFixedLandscape implements
+ CommandButtonLayoutManager {
+
+ @Override
+ public int getPreferredIconSize() {
+ return 32;
+ }
+
+ @Override
+ public Dimension getPreferredSize(AbstractCommandButton commandButton) {
+ Insets borderInsets = commandButton.getInsets();
+ int bx = borderInsets.left + borderInsets.right;
+ int by = borderInsets.top + borderInsets.bottom;
+ FontMetrics fm = commandButton.getFontMetrics(commandButton.getFont());
+ JSeparator jsep = new JSeparator(JSeparator.VERTICAL);
+ int layoutVGap = FlamingoUtilities.getVLayoutGap(commandButton);
+
+ // icon, label
+ int fillTitleWidth = fm.stringWidth(commandButton.getText());
+
+ int widthFull = Math.max(this.getPreferredIconSize(), fillTitleWidth);
+
+ int heightFull = by + this.getPreferredIconSize() + layoutVGap
+ + jsep.getPreferredSize().width;
+ if (commandButton.getText() != null) {
+ heightFull += fm.getHeight();
+ }
+
+ widthFull = Math.max(widthFull, heightFull * 5 / 4);
+ return new Dimension(bx + widthFull, heightFull);
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ }
+
+ @Override
+ public Point getKeyTipAnchorCenterPoint(AbstractCommandButton commandButton) {
+ // center of the bottom edge
+ return new Point(commandButton.getWidth() / 2, commandButton
+ .getHeight());
+ }
+
+ @Override
+ public CommandButtonLayoutInfo getLayoutInfo(
+ AbstractCommandButton commandButton, Graphics g) {
+ CommandButtonLayoutInfo result = new CommandButtonLayoutInfo();
+
+ result.actionClickArea = new Rectangle(0, 0, 0, 0);
+ result.popupClickArea = new Rectangle(0, 0, 0, 0);
+
+ Insets ins = commandButton.getInsets();
+
+ result.iconRect = new Rectangle();
+ result.popupActionRect = new Rectangle();
+
+ int width = commandButton.getWidth();
+ int height = commandButton.getHeight();
+
+ int x = ins.left;
+ int y = ins.top;
+
+ FontMetrics fm = g.getFontMetrics();
+ int labelHeight = fm.getAscent() + fm.getDescent();
+
+ JCommandButton.CommandButtonKind buttonKind = (commandButton instanceof JCommandButton) ? ((JCommandButton) commandButton)
+ .getCommandButtonKind()
+ : JCommandButton.CommandButtonKind.ACTION_ONLY;
+
+ result.isTextInActionArea = false;
+ if (buttonKind == JCommandButton.CommandButtonKind.ACTION_ONLY) {
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width;
+ result.actionClickArea.height = height;
+ result.isTextInActionArea = true;
+ }
+ if (buttonKind == JCommandButton.CommandButtonKind.POPUP_ONLY) {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+ }
+
+ JSeparator jsep = new JSeparator(JSeparator.VERTICAL);
+ // int layoutGap = FlamingoUtilities.getLayoutGap(commandButton);
+
+ ResizableIcon buttonIcon = commandButton.getIcon();
+
+ if (commandButton.getText() == null) {
+ y = ins.top
+ + (height - ins.top - ins.bottom - buttonIcon
+ .getIconHeight()) / 2;
+ }
+ result.iconRect.x = (width - buttonIcon.getIconWidth()) / 2;
+ result.iconRect.y = y;
+ result.iconRect.width = buttonIcon.getIconWidth();
+ result.iconRect.height = buttonIcon.getIconHeight();
+ y += buttonIcon.getIconHeight();
+
+ y += jsep.getPreferredSize().width;
+
+ TextLayoutInfo lineLayoutInfo = new TextLayoutInfo();
+ lineLayoutInfo.text = commandButton.getText();
+ lineLayoutInfo.textRect = new Rectangle();
+
+ int labelWidth = (int) fm.getStringBounds(commandButton.getText(), g)
+ .getWidth();
+
+ lineLayoutInfo.textRect.x = ins.left
+ + (width - labelWidth - ins.left - ins.right) / 2;
+ lineLayoutInfo.textRect.y = y;
+ lineLayoutInfo.textRect.width = labelWidth;
+ lineLayoutInfo.textRect.height = labelHeight;
+
+ result.textLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.textLayoutInfoList.add(lineLayoutInfo);
+
+ return result;
+ }
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JBandControlPanel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JBandControlPanel.java
new file mode 100644
index 0000000..397c95c
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JBandControlPanel.java
@@ -0,0 +1,554 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.util.*;
+
+import javax.swing.UIManager;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.ribbon.*;
+
+/**
+ * Control panel of a single {@link JRibbonBand}. This class is for internal use
+ * only and should not be directly used by the applications.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JBandControlPanel extends AbstractBandControlPanel implements
+ UIResource {
+ public static class ControlPanelGroup {
+ public String groupTitle;
+
+ /**
+ * Indication whether <code>this</code> control panel has galleries.
+ */
+ private boolean hasGalleries;
+
+ /**
+ * Number of galleries in <code>this</code> control panel.
+ */
+ private int galleryCount;
+
+ /**
+ * Mapping from priority to galleries.
+ */
+ private Map<RibbonElementPriority, List<JRibbonGallery>> ribbonGalleries;
+
+ /**
+ * Mapping from gallery to priority.
+ */
+ private Map<JRibbonGallery, RibbonElementPriority> ribbonGalleriesPriorities;
+
+ /**
+ * Mapping from priority to ribbon buttons.
+ */
+ private Map<RibbonElementPriority, List<AbstractCommandButton>> ribbonButtons;
+
+ /**
+ * Mapping from ribbon button to priority.
+ */
+ private Map<AbstractCommandButton, RibbonElementPriority> ribbonButtonsPriorities;
+
+ private List<JRibbonComponent> coreComps;
+
+ private Map<JRibbonComponent, Integer> coreCompRowSpans;
+
+ public ControlPanelGroup(String groupTitle) {
+ this.groupTitle = groupTitle;
+ this.ribbonButtons = new HashMap<RibbonElementPriority, java.util.List<AbstractCommandButton>>();
+ this.ribbonButtonsPriorities = new HashMap<AbstractCommandButton, RibbonElementPriority>();
+ this.ribbonGalleries = new HashMap<RibbonElementPriority, java.util.List<JRibbonGallery>>();
+ this.ribbonGalleriesPriorities = new HashMap<JRibbonGallery, RibbonElementPriority>();
+ this.hasGalleries = false;
+ this.galleryCount = 0;
+ this.coreComps = new ArrayList<JRibbonComponent>();
+ this.coreCompRowSpans = new HashMap<JRibbonComponent, Integer>();
+ }
+
+ public String getGroupTitle() {
+ return groupTitle;
+ }
+
+ public void setGroupTitle(String newTitle) {
+ if ((this.groupTitle == null) && (newTitle != null)) {
+ throw new IllegalArgumentException(
+ "Cannot set a title for an unnamed group");
+ }
+ if ((this.groupTitle != null) && (newTitle == null)) {
+ throw new IllegalArgumentException(
+ "Cannot remove a title from a named group");
+ }
+ this.groupTitle = newTitle;
+ }
+
+ public boolean isCoreContent() {
+ return !this.coreComps.isEmpty();
+ }
+
+ /**
+ * Adds a new ribbon button to <code>this</code> control panel.
+ *
+ * @param ribbonButton
+ * Ribbon button to add.
+ * @param priority
+ * Ribbon button priority.
+ */
+ public synchronized void addCommandButton(
+ AbstractCommandButton ribbonButton,
+ RibbonElementPriority priority) {
+ if (this.groupTitle != null) {
+ throw new UnsupportedOperationException(
+ "Can't add command buttons to ribbon band group with non-null title");
+ }
+ if (this.isCoreContent()) {
+ throw new UnsupportedOperationException(
+ "Ribbon band groups do not support mixing JRibbonComponents and custom Flamingo components");
+ }
+ if (!this.ribbonButtons.containsKey(priority)) {
+ this.ribbonButtons.put(priority,
+ new LinkedList<AbstractCommandButton>());
+ }
+ List<AbstractCommandButton> al = this.ribbonButtons.get(priority);
+ al.add(ribbonButton);
+
+ this.ribbonButtonsPriorities.put(ribbonButton, priority);
+
+ ribbonButton.setDisplayState(CommandButtonDisplayState.BIG);
+ }
+
+ /**
+ * Adds a new in-ribbon gallery to <code>this</code> control panel.
+ *
+ * @param ribbonGallery
+ * Ribbon gallery to add.
+ * @param priority
+ * Ribbon gallery priority.
+ */
+ public synchronized void addRibbonGallery(JRibbonGallery ribbonGallery,
+ RibbonElementPriority priority) {
+ if (this.groupTitle != null) {
+ throw new UnsupportedOperationException(
+ "Can't add galleries to ribbon band group with non-null title");
+ }
+ if (this.isCoreContent()) {
+ throw new UnsupportedOperationException(
+ "Ribbon band groups do not support mixing JRibbonComponents and custom Flamingo components");
+ }
+ // check the name
+ if (!this.ribbonGalleries.containsKey(priority)) {
+ this.ribbonGalleries.put(priority,
+ new LinkedList<JRibbonGallery>());
+ }
+ List<JRibbonGallery> al = this.ribbonGalleries.get(priority);
+ al.add(ribbonGallery);
+
+ this.ribbonGalleriesPriorities.put(ribbonGallery, priority);
+
+ ribbonGallery.setDisplayPriority(RibbonElementPriority.TOP);
+
+ this.hasGalleries = true;
+ this.galleryCount++;
+ }
+
+ /**
+ * Sets new priority of a ribbon button in <code>this</code> control
+ * panel.
+ *
+ * @param ribbonButton
+ * Gallery button.
+ * @param newPriority
+ * New priority for the specified ribbon button.
+ */
+ public synchronized void setPriority(JCommandButton ribbonButton,
+ RibbonElementPriority newPriority) {
+ RibbonElementPriority oldPriority = this.ribbonButtonsPriorities
+ .get(ribbonButton);
+ if (newPriority == oldPriority)
+ return;
+
+ this.ribbonButtons.get(oldPriority).remove(ribbonButton);
+
+ if (!this.ribbonButtons.containsKey(newPriority)) {
+ this.ribbonButtons.put(newPriority,
+ new ArrayList<AbstractCommandButton>());
+ }
+ this.ribbonButtons.get(newPriority).add(ribbonButton);
+ }
+
+ /**
+ * Sets new priority of an in-ribbon gallery in <code>this</code>
+ * control panel.
+ *
+ * @param ribbonGallery
+ * In-ribbon gallery.
+ * @param newPriority
+ * New priority for the specified in-ribbon gallery.
+ */
+ public synchronized void setPriority(JRibbonGallery ribbonGallery,
+ RibbonElementPriority newPriority) {
+ RibbonElementPriority oldPriority = this.ribbonGalleriesPriorities
+ .get(ribbonGallery);
+ if (newPriority == oldPriority)
+ return;
+
+ this.ribbonGalleries.get(oldPriority).remove(ribbonGallery);
+
+ if (!this.ribbonGalleries.containsKey(newPriority)) {
+ this.ribbonGalleries.put(newPriority,
+ new ArrayList<JRibbonGallery>());
+ }
+ this.ribbonGalleries.get(newPriority).add(ribbonGallery);
+ }
+
+ public void addRibbonComponent(JRibbonComponent comp, int rowSpan) {
+ if (!this.ribbonButtonsPriorities.isEmpty()
+ || !this.ribbonGalleries.isEmpty()) {
+ throw new UnsupportedOperationException(
+ "Ribbon band groups do not support mixing JRibbonComponents and custom Flamingo components");
+ }
+ comp.setOpaque(false);
+ this.coreComps.add(comp);
+ this.coreCompRowSpans.put(comp, rowSpan);
+ }
+
+ /**
+ * Retrieves all ribbon buttons of specified priority from
+ * <code>this</code> control panel.
+ *
+ * @param priority
+ * Priority.
+ * @return All ribbon buttons of specified priority from
+ * <code>this</code> control panel.
+ */
+ public List<AbstractCommandButton> getRibbonButtons(
+ RibbonElementPriority priority) {
+ List<AbstractCommandButton> result = this.ribbonButtons
+ .get(priority);
+ if (result == null)
+ return EMPTY_GALLERY_BUTTONS_LIST;
+ return result;
+ }
+
+ /**
+ * Retrieves all in-ribbon galleries of specified priority from
+ * <code>this</code> control panel.
+ *
+ * @param priority
+ * Priority.
+ * @return All in-ribbon galleries of specified priority from
+ * <code>this</code> control panel.
+ */
+ public List<JRibbonGallery> getRibbonGalleries(
+ RibbonElementPriority priority) {
+ List<JRibbonGallery> result = this.ribbonGalleries.get(priority);
+ if (result == null)
+ return EMPTY_RIBBON_GALLERIES_LIST;
+ return result;
+ }
+
+ /**
+ * Returns indication whether <code>this</code> control panel has
+ * in-ribbon galleries.
+ *
+ * @return <code>true</code> if <code>this</code> control panel has
+ * in-ribbon galleries, <code>false</code> otherwise.
+ */
+ public boolean hasRibbonGalleries() {
+ return this.hasGalleries;
+ }
+
+ /**
+ * Returns the number of in-ribbon galleries in <code>this</code>
+ * control panel.
+ *
+ * @return Number of in-ribbon galleries in <code>this</code> control
+ * panel.
+ */
+ public int getRibbonGalleriesCount() {
+ return this.galleryCount;
+ }
+
+ public List<JRibbonComponent> getRibbonComps() {
+ return this.coreComps;
+ }
+
+ public Map<JRibbonComponent, Integer> getRibbonCompsRowSpans() {
+ return this.coreCompRowSpans;
+ }
+ }
+
+ /**
+ * Maps from gallery name to gallery.
+ */
+ private Map<String, JRibbonGallery> galleryNameMap;
+
+ private LinkedList<ControlPanelGroup> controlPanelGroups;
+
+ /**
+ * Empty list of buttons.
+ */
+ public static final List<AbstractCommandButton> EMPTY_GALLERY_BUTTONS_LIST = new LinkedList<AbstractCommandButton>();
+
+ /**
+ * Empty list of galleries.
+ */
+ public static final List<JRibbonGallery> EMPTY_RIBBON_GALLERIES_LIST = new LinkedList<JRibbonGallery>();
+
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "BandControlPanelUI";
+
+ /**
+ * Creates a control panel for specified ribbon band.
+ *
+ */
+ public JBandControlPanel() {
+ super();
+
+ this.controlPanelGroups = new LinkedList<ControlPanelGroup>();
+ this.galleryNameMap = new HashMap<String, JRibbonGallery>();
+ }
+
+ /**
+ * Sets the new UI delegate.
+ *
+ * @param ui
+ * New UI delegate.
+ */
+ public void setUI(BandControlPanelUI ui) {
+ super.setUI(ui);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((BandControlPanelUI) UIManager.getUI(this));
+ } else {
+ setUI(new BasicBandControlPanelUI());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#getUI()
+ */
+ @Override
+ public BandControlPanelUI getUI() {
+ return (BandControlPanelUI) ui;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /**
+ * Adds a new ribbon button to <code>this</code> control panel.
+ *
+ * @param ribbonButton
+ * Ribbon button to add.
+ * @param priority
+ * Ribbon button priority.
+ */
+ public synchronized void addCommandButton(
+ AbstractCommandButton ribbonButton, RibbonElementPriority priority) {
+ if (this.controlPanelGroups.size() == 0)
+ this.startGroup();
+
+ this.controlPanelGroups.getLast().addCommandButton(ribbonButton,
+ priority);
+
+ super.add(ribbonButton);
+ }
+
+ /**
+ * Adds a new in-ribbon gallery to <code>this</code> control panel.
+ *
+ * @param ribbonGallery
+ * Ribbon gallery to add.
+ * @param priority
+ * Ribbon gallery priority.
+ */
+ public synchronized void addRibbonGallery(JRibbonGallery ribbonGallery,
+ RibbonElementPriority priority) {
+ // check the name
+ String galleryName = ribbonGallery.getName();
+ if ((galleryName == null) || (galleryName.isEmpty())) {
+ throw new IllegalArgumentException(
+ "Ribbon gallery name null or empty");
+ }
+ if (this.galleryNameMap.containsKey(galleryName)) {
+ throw new IllegalArgumentException(
+ "Another riboon gallery with the same name already exists");
+ }
+
+ if (this.controlPanelGroups.size() == 0)
+ this.startGroup();
+
+ this.controlPanelGroups.getLast().addRibbonGallery(ribbonGallery,
+ priority);
+
+ this.galleryNameMap.put(galleryName, ribbonGallery);
+
+ super.add(ribbonGallery);
+ }
+
+ /**
+ * Sets new priority of a ribbon button in <code>this</code> control panel.
+ *
+ * @param ribbonButton
+ * Gallery button.
+ * @param newPriority
+ * New priority for the specified ribbon button.
+ */
+ public synchronized void setPriority(JCommandButton ribbonButton,
+ RibbonElementPriority newPriority) {
+ if (this.controlPanelGroups.size() == 0)
+ this.startGroup();
+
+ this.controlPanelGroups.getLast()
+ .setPriority(ribbonButton, newPriority);
+ }
+
+ /**
+ * Sets new priority of an in-ribbon gallery in <code>this</code> control
+ * panel.
+ *
+ * @param ribbonGallery
+ * In-ribbon gallery.
+ * @param newPriority
+ * New priority for the specified in-ribbon gallery.
+ */
+ public synchronized void setPriority(JRibbonGallery ribbonGallery,
+ RibbonElementPriority newPriority) {
+ if (this.controlPanelGroups.size() == 0)
+ this.startGroup();
+
+ this.controlPanelGroups.getLast().setPriority(ribbonGallery,
+ newPriority);
+ }
+
+ public void addRibbonComponent(JRibbonComponent comp) {
+ this.addRibbonComponent(comp, 1);
+ }
+
+ public void addRibbonComponent(JRibbonComponent comp, int rowSpan) {
+ if (this.controlPanelGroups.size() == 0)
+ this.startGroup();
+
+ this.controlPanelGroups.getLast().addRibbonComponent(comp, rowSpan);
+ super.add(comp);
+ }
+
+ public List<ControlPanelGroup> getControlPanelGroups() {
+ return Collections.unmodifiableList(this.controlPanelGroups);
+ }
+
+ public int getControlPanelGroupCount() {
+ if (this.controlPanelGroups == null)
+ return 1;
+ return this.controlPanelGroups.size();
+ }
+
+ public String getControlPanelGroupTitle(int controlPanelGroupIndex) {
+ if (this.controlPanelGroups == null)
+ return null;
+ return this.controlPanelGroups.get(controlPanelGroupIndex).groupTitle;
+ }
+
+ public int startGroup() {
+ return this.startGroup(null);
+ }
+
+ public int startGroup(String groupTitle) {
+ ControlPanelGroup controlPanelGroup = new ControlPanelGroup(groupTitle);
+ this.controlPanelGroups.addLast(controlPanelGroup);
+ this.fireChanged();
+ return this.controlPanelGroups.size() - 1;
+ }
+
+ public void setGroupTitle(int groupIndex, String groupTitle) {
+ this.controlPanelGroups.get(groupIndex).setGroupTitle(groupTitle);
+ this.fireChanged();
+ }
+
+ /**
+ * Returns the ribbon gallery based on its name.
+ *
+ * @param galleryName
+ * Ribbon gallery name.
+ * @return Ribbon gallery.
+ */
+ public JRibbonGallery getRibbonGallery(String galleryName) {
+ return this.galleryNameMap.get(galleryName);
+ }
+
+ public void addChangeListener(ChangeListener l) {
+ this.listenerList.add(ChangeListener.class, l);
+ }
+
+ public void removeChangeListener(ChangeListener l) {
+ this.listenerList.remove(ChangeListener.class, l);
+ }
+
+ protected void fireChanged() {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ ChangeEvent ce = new ChangeEvent(this);
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ChangeListener.class) {
+ ((ChangeListener) listeners[i + 1]).stateChanged(ce);
+ }
+ }
+ }
+
+ public List<JRibbonComponent> getRibbonComponents(int groupIndex) {
+ return Collections.unmodifiableList(this.controlPanelGroups.get(
+ groupIndex).getRibbonComps());
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JFlowBandControlPanel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JFlowBandControlPanel.java
new file mode 100644
index 0000000..fd27104
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JFlowBandControlPanel.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.Component;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.ribbon.JRibbonBand;
+import org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority;
+
+/**
+ * Control panel of a single {@link JRibbonBand}. This class is for internal use
+ * only and should not be directly used by the applications.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JFlowBandControlPanel extends AbstractBandControlPanel implements
+ UIResource {
+ /**
+ * List of all components of <code>this</code> control panel.
+ */
+ private List<JComponent> comps;
+
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "FlowBandControlPanelUI";
+
+ /**
+ * Creates a control panel for specified ribbon band.
+ *
+ */
+ public JFlowBandControlPanel() {
+ super();
+
+ this.comps = new LinkedList<JComponent>();
+ }
+
+ /**
+ * Sets the new UI delegate.
+ *
+ * @param ui
+ * New UI delegate.
+ */
+ public void setUI(BandControlPanelUI ui) {
+ super.setUI(ui);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((BandControlPanelUI) UIManager.getUI(this));
+ } else {
+ setUI(new BasicFlowBandControlPanelUI());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#getUI()
+ */
+ @Override
+ public BandControlPanelUI getUI() {
+ return (BandControlPanelUI) ui;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /**
+ * Adds new panel to <code>this</code> control panel.
+ *
+ * @param comp
+ * Panel to add
+ */
+ public void addFlowComponent(JComponent comp) {
+ this.comps.add(comp);
+ super.add(comp);
+ }
+
+ /**
+ * Returns regular panels of <code>this</code> control panel.
+ *
+ * @return Regular panels of <code>this</code> control panel.
+ */
+ public List<JComponent> getFlowComponents() {
+ return this.comps;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JRibbonGallery.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JRibbonGallery.java
new file mode 100644
index 0000000..becd5e6
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JRibbonGallery.java
@@ -0,0 +1,576 @@
+/*
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.*;
+
+import javax.swing.*;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.ribbon.JRibbonBand;
+import org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority;
+import org.pushingpixels.flamingo.api.ribbon.JRibbonBand.RibbonGalleryPopupCallback;
+
+/**
+ * In-ribbon gallery. This class is for internal use only and should not be
+ * directly used by the applications.
+ *
+ * @author Kirill Grouchnikov
+ * @see JRibbonBand#addRibbonGallery(String, List, Map, int, int,
+ * RibbonElementPriority)
+ */
+public class JRibbonGallery extends JComponent {
+ /**
+ * The buttons of <code>this</code> gallery.
+ */
+ protected List<JCommandToggleButton> buttons;
+
+ /**
+ * Button group for ensuring that only one button is selected.
+ */
+ protected CommandToggleButtonGroup buttonSelectionGroup;
+
+ /**
+ * The current display priority of <code>this</code> in-ribbon gallery.
+ */
+ protected RibbonElementPriority displayPriority;
+
+ /**
+ * Preferred widths for each possible display state (set in the user code
+ * according to design preferences).
+ */
+ protected Map<RibbonElementPriority, Integer> preferredVisibleIconCount;
+
+ /**
+ * Gallery button groups.
+ */
+ protected List<StringValuePair<List<JCommandToggleButton>>> buttonGroups;
+
+ /**
+ * Preferred maximum number of button columns for the popup panel.
+ */
+ protected int preferredPopupMaxButtonColumns;
+
+ /**
+ * Preferred maximum number of visible button rows for the popup panel.
+ */
+ protected int preferredPopupMaxVisibleButtonRows;
+
+ /**
+ * Indication whether the ribbon gallery is showing the popup panel.
+ */
+ protected boolean isShowingPopupPanel;
+
+ protected RibbonGalleryPopupCallback popupCallback;
+
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "RibbonGalleryUI";
+
+ private String expandKeyTip;
+
+ private CommandButtonDisplayState buttonDisplayState;
+
+ /**
+ * Creates new in-ribbon gallery.
+ */
+ public JRibbonGallery() {
+ this.buttons = new ArrayList<JCommandToggleButton>();
+ this.buttonSelectionGroup = new CommandToggleButtonGroup();
+ this.buttonSelectionGroup
+ .addPropertyChangeListener(new PropertyChangeListener() {
+ @Override
+ public void propertyChange(final PropertyChangeEvent evt) {
+ if (CommandToggleButtonGroup.SELECTED_PROPERTY
+ .equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ firePropertyChange("selectedButton", evt
+ .getOldValue(), evt.getNewValue());
+ }
+ });
+ }
+ }
+ });
+
+ this.preferredVisibleIconCount = new HashMap<RibbonElementPriority, Integer>();
+ // Initialize with some values. Application should provide real
+ // widths using setPreferredWidth.
+ for (RibbonElementPriority state : RibbonElementPriority.values())
+ this.preferredVisibleIconCount.put(state, 100);
+
+ this.isShowingPopupPanel = false;
+ this.buttonDisplayState = JRibbonBand.BIG_FIXED_LANDSCAPE;
+
+ this.updateUI();
+ }
+
+ /**
+ * Sets the new UI delegate.
+ *
+ * @param ui
+ * New UI delegate.
+ */
+ public void setUI(RibbonGalleryUI ui) {
+ super.setUI(ui);
+ }
+
+ /**
+ * Resets the UI property to a value from the current look and feel.
+ *
+ * @see JComponent#updateUI
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((RibbonGalleryUI) UIManager.getUI(this));
+ } else {
+ setUI(new BasicRibbonGalleryUI());
+ }
+ //
+ // if (this.popupPanel != null)
+ // SwingUtilities.updateComponentTreeUI(this.popupPanel);
+ }
+
+ /**
+ * Returns the UI object which implements the L&F for this component.
+ *
+ * @return a <code>RibbonGalleryUI</code> object
+ * @see #setUI(RibbonGalleryUI)
+ */
+ public RibbonGalleryUI getUI() {
+ return (RibbonGalleryUI) ui;
+ }
+
+ /**
+ * Returns the name of the UI class that implements the L&F for this
+ * component.
+ *
+ * @return the string "RibbonGalleryUI"
+ * @see JComponent#getUIClassID
+ * @see UIDefaults#getUI(javax.swing.JComponent)
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /**
+ * Adds new gallery button to <code>this</code> in-ribbon gallery.
+ *
+ * @param buttonGroup
+ * Button group.
+ * @param button
+ * Gallery button to add.
+ */
+ private void addGalleryButton(
+ StringValuePair<List<JCommandToggleButton>> buttonGroup,
+ JCommandToggleButton button) {
+ String buttonGroupName = buttonGroup.getKey();
+ // find the index to add
+ int indexToAdd = 0;
+ for (int i = 0; i < this.buttonGroups.size(); i++) {
+ StringValuePair<List<JCommandToggleButton>> buttonGroupPair = this.buttonGroups
+ .get(i);
+ String currGroupName = buttonGroupPair.getKey();
+ indexToAdd += buttonGroupPair.getValue().size();
+ if ((currGroupName == null) && (buttonGroupName == null)) {
+ break;
+ }
+ if (currGroupName.compareTo(buttonGroupName) == 0) {
+ break;
+ }
+ }
+ // System.out.println("Added " + button.getText() + " at " +
+ // indexToAdd);
+ this.buttons.add(indexToAdd, button);
+ this.buttonSelectionGroup.add(button);
+ buttonGroup.getValue().add(button);
+ button.setDisplayState(this.buttonDisplayState);
+
+ super.add(button);
+ }
+
+ /**
+ * Removes an existing gallery button from <code>this</code> in-ribbon
+ * gallery.
+ *
+ * @param button
+ * Gallery button to remove.
+ */
+ private void removeGalleryButton(JCommandToggleButton button) {
+ this.buttons.remove(button);
+ this.buttonSelectionGroup.remove(button);
+
+ super.remove(button);
+ }
+
+ /**
+ * Set preferred width of <code>this</code> in-ribbon gallery for the
+ * specified display state.
+ *
+ * @param state
+ * Display state.
+ * @param visibleButtonCount
+ * Preferred width for the specified state.
+ */
+ public void setPreferredVisibleButtonCount(RibbonElementPriority state,
+ int visibleButtonCount) {
+ this.preferredVisibleIconCount.put(state, visibleButtonCount);
+ }
+
+ /**
+ * Returns the preferred width of <code>this</code> in-ribbon gallery for
+ * the specified display state.
+ *
+ * @param state
+ * Display state.
+ * @param availableHeight
+ * Available height in pixels.
+ * @return The preferred width of <code>this</code> in-ribbon gallery for
+ * the specified display state.
+ */
+ public int getPreferredWidth(RibbonElementPriority state,
+ int availableHeight) {
+ int preferredVisibleButtonCount = this.preferredVisibleIconCount
+ .get(state);
+
+ BasicRibbonGalleryUI ui = (BasicRibbonGalleryUI) this.getUI();
+ return ui.getPreferredWidth(preferredVisibleButtonCount,
+ availableHeight);
+ }
+
+ /**
+ * Sets new display priority for <code>this</code> in-ribbon gallery.
+ *
+ * @param displayPriority
+ * New display priority for <code>this</code> in-ribbon gallery.
+ */
+ public void setDisplayPriority(RibbonElementPriority displayPriority) {
+ this.displayPriority = displayPriority;
+ }
+
+ /**
+ * Returns the current display priority for <code>this</code> in-ribbon
+ * gallery.
+ *
+ * @return The current display priority for <code>this</code> in-ribbon
+ * gallery.
+ */
+ public RibbonElementPriority getDisplayPriority() {
+ return this.displayPriority;
+ }
+
+ /**
+ * Returns the number of button groups in <code>this</code> in-ribbon
+ * gallery.
+ *
+ * @return The number of button groups in <code>this</code> in-ribbon
+ * gallery.
+ */
+ public int getButtonGroupCount() {
+ return this.buttonGroups.size();
+ }
+
+ /**
+ * Returns the list of buttons in the specifed button group.
+ *
+ * @param buttonGroupName
+ * Button group name.
+ * @return The list of buttons in the specifed button group.
+ */
+ public List<JCommandToggleButton> getButtonGroup(String buttonGroupName) {
+ for (StringValuePair<List<JCommandToggleButton>> group : this.buttonGroups) {
+ if (group.getKey().compareTo(buttonGroupName) == 0)
+ return group.getValue();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the number of gallery buttons in <code>this</code> in-ribbon
+ * gallery.
+ *
+ * @return The number of gallery buttons in <code>this</code> in-ribbon
+ * gallery.
+ */
+ public int getButtonCount() {
+ return this.buttons.size();
+ }
+
+ /**
+ * Returns the gallery button at specified index.
+ *
+ * @param index
+ * Gallery button index.
+ * @return Gallery button at specified index.
+ */
+ public JCommandToggleButton getButtonAt(int index) {
+ return this.buttons.get(index);
+ }
+
+ /**
+ * Returns the currently selected gallery button.
+ *
+ * @return The currently selected gallery button.
+ */
+ public JCommandToggleButton getSelectedButton() {
+ return this.buttonSelectionGroup.getSelected();
+ }
+
+ /**
+ * Sets new value for the currently selected gallery button.
+ *
+ * @param selectedButton
+ * New value for the currently selected gallery button.
+ */
+ public void setSelectedButton(JCommandToggleButton selectedButton) {
+ this.buttonSelectionGroup.setSelected(selectedButton, true);
+ }
+
+ /**
+ * Returns the associated popup gallery.
+ *
+ * @return The associated popup gallery.
+ */
+ public JCommandButtonPanel getPopupButtonPanel() {
+ JCommandButtonPanel buttonPanel = new JCommandButtonPanel(
+ this.buttonDisplayState);
+ buttonPanel.setMaxButtonColumns(this.preferredPopupMaxButtonColumns);
+ buttonPanel.setToShowGroupLabels(true);
+ for (StringValuePair<List<JCommandToggleButton>> buttonGroupEntry : this.buttonGroups) {
+ String groupName = buttonGroupEntry.getKey();
+ if (groupName == null) {
+ buttonPanel.setToShowGroupLabels(false);
+ }
+ buttonPanel.addButtonGroup(groupName);
+ for (JCommandToggleButton button : buttonGroupEntry.getValue()) {
+ // set the button to visible (the gallery hides the buttons
+ // that don't fit the front row).
+ button.setVisible(true);
+ buttonPanel.addButtonToLastGroup(button);
+ }
+ }
+ // just to make sure that the button panel will not try to add
+ // the buttons to its own button group
+ buttonPanel.setSingleSelectionMode(true);
+ return buttonPanel;
+ }
+
+ /**
+ * Sets indication whether the popup panel is showing.
+ *
+ * @param isShowingPopupPanel
+ * Indication whether the popup panel is showing.
+ */
+ public void setShowingPopupPanel(boolean isShowingPopupPanel) {
+ this.isShowingPopupPanel = isShowingPopupPanel;
+
+ if (!isShowingPopupPanel) {
+ // populate the ribbon gallery back
+ for (StringValuePair<List<JCommandToggleButton>> buttonGroupEntry : this.buttonGroups) {
+ for (JCommandToggleButton button : buttonGroupEntry.getValue()) {
+ button.setDisplayState(this.buttonDisplayState);
+ this.add(button);
+ }
+ }
+ // and layout
+ this.doLayout();
+ }
+ }
+
+ /**
+ * Returns indication whether the popup panel is showing.
+ *
+ * @return <code>true</code> if the popup panel is showing,
+ * <code>false</code> otherwise.
+ */
+ public boolean isShowingPopupPanel() {
+ return this.isShowingPopupPanel;
+ }
+
+ /**
+ * Sets the button groups for this ribbon gallery.
+ *
+ * @param buttons
+ * Button groups.
+ */
+ public void setGroupMapping(
+ List<StringValuePair<List<JCommandToggleButton>>> buttons) {
+ this.buttonGroups = new ArrayList<StringValuePair<List<JCommandToggleButton>>>();
+ boolean hasGroupWithNullTitle = false;
+ for (StringValuePair<List<JCommandToggleButton>> buttonGroupPair : buttons) {
+ if (buttonGroupPair.getKey() == null) {
+ if (hasGroupWithNullTitle) {
+ throw new IllegalArgumentException(
+ "Can't have more than one ribbon gallery group with null name");
+ }
+ hasGroupWithNullTitle = true;
+ }
+
+ // create the list of buttons for this group
+ List<JCommandToggleButton> buttonGroupCopy = new ArrayList<JCommandToggleButton>();
+ // add it to the groups list
+ StringValuePair<List<JCommandToggleButton>> buttonGroupInfo = new StringValuePair<List<JCommandToggleButton>>(
+ buttonGroupPair.getKey(), buttonGroupCopy);
+ this.buttonGroups.add(buttonGroupInfo);
+ // add all the buttons to the control
+ for (JCommandToggleButton button : buttonGroupPair.getValue()) {
+ this.addGalleryButton(buttonGroupInfo, button);
+ }
+ }
+ }
+
+ /**
+ * Adds toggle command buttons to the specified button group in this ribbon
+ * gallery.
+ *
+ * @param buttonGroupName
+ * Button group name.
+ * @param buttons
+ * Toggle command buttons to add to the specified button group.
+ */
+ public void addRibbonGalleryButtons(String buttonGroupName,
+ JCommandToggleButton... buttons) {
+ for (StringValuePair<List<JCommandToggleButton>> buttonGroup : this.buttonGroups) {
+ if (buttonGroup.getKey().compareTo(buttonGroupName) == 0) {
+ for (JCommandToggleButton button : buttons) {
+ // buttonGroup.getValue().add(button);
+ this.addGalleryButton(buttonGroup, button);
+ }
+ return;
+ }
+ }
+ this.revalidate();
+ this.doLayout();
+ }
+
+ /**
+ * Removes the specified toggle command buttons from this ribbon gallery.
+ *
+ * @param buttons
+ * Toggle command buttons to remove from this gallery.
+ */
+ public void removeRibbonGalleryButtons(JCommandToggleButton... buttons) {
+ for (StringValuePair<List<JCommandToggleButton>> buttonGroup : this.buttonGroups) {
+ for (Iterator<JCommandToggleButton> it = buttonGroup.getValue()
+ .iterator(); it.hasNext();) {
+ JCommandToggleButton currButtonInGroup = it.next();
+ for (JCommandToggleButton toRemove : buttons) {
+ if (toRemove == currButtonInGroup) {
+ it.remove();
+ this.removeGalleryButton(toRemove);
+ }
+ }
+ }
+ }
+ this.revalidate();
+ this.doLayout();
+ }
+
+ /**
+ * Sets the preferred dimension of the popup panel.
+ *
+ * @param preferredPopupMaxButtonColumns
+ * Preferred maximum number of button columns for the popup
+ * panel.
+ * @param preferredPopupMaxVisibleButtonRows
+ * Preferred maximum number of visible button rows for the popup
+ * panel.
+ */
+ public void setPreferredPopupPanelDimension(
+ int preferredPopupMaxButtonColumns,
+ int preferredPopupMaxVisibleButtonRows) {
+ this.preferredPopupMaxButtonColumns = preferredPopupMaxButtonColumns;
+ this.preferredPopupMaxVisibleButtonRows = preferredPopupMaxVisibleButtonRows;
+ }
+
+ public void setPopupCallback(RibbonGalleryPopupCallback popupCallback) {
+ this.popupCallback = popupCallback;
+ }
+
+ public RibbonGalleryPopupCallback getPopupCallback() {
+ return popupCallback;
+ }
+
+ public int getPreferredPopupMaxButtonColumns() {
+ return preferredPopupMaxButtonColumns;
+ }
+
+ public int getPreferredPopupMaxVisibleButtonRows() {
+ return preferredPopupMaxVisibleButtonRows;
+ }
+
+ public void setExpandKeyTip(String expandKeyTip) {
+ String old = this.expandKeyTip;
+ this.expandKeyTip = expandKeyTip;
+ this.firePropertyChange("expandKeyTip", old, this.expandKeyTip);
+ }
+
+ public String getExpandKeyTip() {
+ return expandKeyTip;
+ }
+
+ public CommandButtonDisplayState getButtonDisplayState() {
+ return this.buttonDisplayState;
+ }
+
+ public void setButtonDisplayState(
+ CommandButtonDisplayState buttonDisplayState) {
+ if (this.getButtonCount() > 0) {
+ throw new IllegalStateException(
+ "Cannot change button display state on ribbon gallery with existing buttons");
+ }
+ boolean isSupported = (buttonDisplayState == JRibbonBand.BIG_FIXED)
+ || (buttonDisplayState == CommandButtonDisplayState.SMALL)
+ || (buttonDisplayState == JRibbonBand.BIG_FIXED_LANDSCAPE);
+ if (!isSupported) {
+ throw new IllegalArgumentException("Display state "
+ + buttonDisplayState.getDisplayName()
+ + " is not supported in ribbon galleries");
+ }
+ if (!buttonDisplayState.equals(this.buttonDisplayState)) {
+ CommandButtonDisplayState old = this.buttonDisplayState;
+ this.buttonDisplayState = buttonDisplayState;
+
+ for (JCommandToggleButton button : this.buttons)
+ button.setDisplayState(buttonDisplayState);
+
+ this.firePropertyChange("buttonDisplayState", old,
+ this.buttonDisplayState);
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JRibbonRootPane.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JRibbonRootPane.java
new file mode 100644
index 0000000..b2a5a7a
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JRibbonRootPane.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.Event;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import javax.swing.*;
+import javax.swing.plaf.RootPaneUI;
+
+import org.pushingpixels.flamingo.api.ribbon.JRibbon;
+import org.pushingpixels.flamingo.api.ribbon.JRibbonFrame;
+
+/**
+ * Root pane for the {@link JRibbonFrame}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JRibbonRootPane extends JRootPane {
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "RibbonRootPaneUI";
+
+ public static final int RIBBON_SPECIAL_LAYER = JLayeredPane.DEFAULT_LAYER + 50;
+
+ public JRibbonRootPane() {
+ InputMap inputMap = this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
+ ActionMap actionMap = this.getActionMap();
+
+ actionMap.put("toggleMinimized", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JRibbonFrame ribbonFrame = (JRibbonFrame) SwingUtilities
+ .getWindowAncestor(JRibbonRootPane.this);
+ JRibbon ribbon = ribbonFrame.getRibbon();
+ ribbon.setMinimized(!ribbon.isMinimized());
+ }
+ });
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F1, Event.CTRL_MASK),
+ "toggleMinimized");
+ }
+
+ @Override
+ public void updateUI() {
+ setUI((RootPaneUI) UIManager.getUI(this));
+ }
+
+ @Override
+ public String getUIClassID() {
+ if (UIManager.get(uiClassID) != null)
+ return uiClassID;
+ return "RootPaneUI";
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JRibbonTaskToggleButton.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JRibbonTaskToggleButton.java
new file mode 100644
index 0000000..7b1a3e5
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/JRibbonTaskToggleButton.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.Color;
+
+import javax.swing.UIManager;
+
+import org.pushingpixels.flamingo.api.common.JCommandToggleButton;
+import org.pushingpixels.flamingo.api.ribbon.RibbonTask;
+
+/**
+ * Toggle button for ribbon tasks. This class is for internal use only and
+ * should not be directly used by the applications.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JRibbonTaskToggleButton extends JCommandToggleButton {
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "RibbonTaskToggleButtonUI";
+
+ /**
+ * Color of the matching contextual task group. Can be <code>null</code> if
+ * the associated task is not contextual.
+ */
+ private Color contextualGroupHueColor;
+
+ private String keyTip;
+
+ private RibbonTask ribbonTask;
+
+ /**
+ * Creates a new toggle button.
+ *
+ * @param ribbonTask
+ */
+ public JRibbonTaskToggleButton(RibbonTask ribbonTask) {
+ super(ribbonTask.getTitle());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JToggleButton#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI(UIManager.getUI(this));
+ } else {
+ setUI(new BasicRibbonTaskToggleButtonUI());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JToggleButton#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /**
+ * Returns the hue color of the matching contextual task group if the
+ * associated task is contextual.
+ *
+ * @return The hue color of the matching contextual task group if the
+ * associated task is contextual, <code>null</code> otherwise.
+ */
+ public Color getContextualGroupHueColor() {
+ return this.contextualGroupHueColor;
+ }
+
+ public RibbonTask getRibbonTask() {
+ return this.ribbonTask;
+ }
+
+ /**
+ * Sets the hue color of the matching contextual task group on this button.
+ *
+ * @param contextualGroupHueColor
+ * The hue color of the matching contextual task group.
+ */
+ public void setContextualGroupHueColor(Color contextualGroupHueColor) {
+ Color old = this.contextualGroupHueColor;
+ this.contextualGroupHueColor = contextualGroupHueColor;
+
+ this.firePropertyChange("contextualGroupHueColor", old,
+ this.contextualGroupHueColor);
+ }
+
+ public void setKeyTip(String keyTip) {
+ this.keyTip = keyTip;
+ }
+
+ public String getKeyTip() {
+ return keyTip;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/RibbonBandUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/RibbonBandUI.java
new file mode 100644
index 0000000..68a7be1
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/RibbonBandUI.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.ribbon.JRibbonBand;
+
+/**
+ * UI for ribbon band ({@link JRibbonBand}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class RibbonBandUI extends ComponentUI {
+ public abstract int getPreferredCollapsedWidth();
+
+ public abstract int getBandTitleHeight();
+
+ public abstract void trackMouseCrossing(boolean isMouseIn);
+
+ public abstract float getRolloverAmount();
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/RibbonComponentUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/RibbonComponentUI.java
new file mode 100644
index 0000000..8cff088
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/RibbonComponentUI.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.Dimension;
+import java.awt.Point;
+
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.ribbon.JRibbonComponent;
+import org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority;
+
+/**
+ * UI for extended components ({@link JRibbonComponent}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class RibbonComponentUI extends ComponentUI {
+ public abstract Point getKeyTipAnchorCenterPoint();
+
+ public abstract Dimension getPreferredSize(RibbonElementPriority priority);
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/RibbonGalleryUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/RibbonGalleryUI.java
new file mode 100644
index 0000000..27d9dac
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/RibbonGalleryUI.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import javax.swing.plaf.ComponentUI;
+
+
+/**
+ * UI for in-ribbon gallery ({@link JRibbonGallery}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class RibbonGalleryUI extends ComponentUI {
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/RibbonUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/RibbonUI.java
new file mode 100644
index 0000000..b9cbbb4
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/RibbonUI.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon;
+
+import java.awt.Rectangle;
+import java.awt.event.MouseWheelEvent;
+
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.ribbon.JRibbon;
+import org.pushingpixels.flamingo.api.ribbon.RibbonContextualTaskGroup;
+
+/**
+ * UI for ribbon ({@link JRibbon}).
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class RibbonUI extends ComponentUI {
+
+ /** The application icon. This is displayed in the application menu button. */
+ public ResizableIcon applicationIcon;
+
+ /**
+ * Returns the bounds of the specified contextual task group.
+ *
+ * @param group
+ * Contextual task group.
+ * @return The bounds of the specified contextual task group.
+ */
+ public abstract Rectangle getContextualTaskGroupBounds(
+ RibbonContextualTaskGroup group);
+
+ public abstract boolean isShowingScrollsForTaskToggleButtons();
+
+ public abstract boolean isShowingScrollsForBands();
+
+ public abstract void handleMouseWheelEvent(MouseWheelEvent e);
+
+ /**
+ * Returns the application icon. This is displayed on the application menu
+ * button.
+ *
+ * @return the application icon
+ */
+ public synchronized ResizableIcon getApplicationIcon() {
+ return applicationIcon;
+ }
+
+ /**
+ * Sets the application icon. This is displayed on the application menu
+ * button.
+ * <p>
+ * There is no check performed to see if <code>applicationIcon</code> is
+ * <code>null</code>.
+ *
+ * @param applicationIcon
+ * the application icon to set
+ */
+ public synchronized void setApplicationIcon(ResizableIcon applicationIcon) {
+ this.applicationIcon = applicationIcon;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/BasicRibbonApplicationMenuButtonUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/BasicRibbonApplicationMenuButtonUI.java
new file mode 100644
index 0000000..2845d89
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/BasicRibbonApplicationMenuButtonUI.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon.appmenu;
+
+import java.awt.*;
+import java.awt.geom.Ellipse2D;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelCallback;
+import org.pushingpixels.flamingo.api.ribbon.*;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonUI;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+/**
+ * Basic UI for ribbon application menu button
+ * {@link JRibbonApplicationMenuButton}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicRibbonApplicationMenuButtonUI extends BasicCommandButtonUI {
+
+ /** The ribbon this menu button is created for */
+ private JRibbon ribbon;
+ /** The associated application menu button. */
+ protected JRibbonApplicationMenuButton applicationMenuButton;
+
+ /**
+ * Constructs a <code>BasicRibbonApplicationMenuButtonUI</code> specifying
+ * the <code>JRibbon</code> the button UI is created for.
+ *
+ * @param ribbon
+ * the ribbon
+ */
+ public BasicRibbonApplicationMenuButtonUI(JRibbon ribbon) {
+ setRibbon(ribbon);
+ }
+
+ /**
+ * Creates a UI delegate for a {@link JRibbonApplicationMenuButton}. The
+ * <code>JRibbon</code> the menu button belongs to is required in order to
+ * create this UI.
+ * <p>
+ * The <code>component</code> may be a supported object instance which is a
+ * <code>JRibbon</code> or has a reference to the application
+ * <code>JRibbon</code>.
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ * @param component
+ * a <code>JRibbon</code> to be used as criteria for creating the
+ * UI for a new <code>BasicRibbonApplicationMenuButtonUI</code>
+ * @exception IllegalArgumentException
+ * if <code>component</code> is not a <code>JRibbon</code>
+ */
+ public static ComponentUI createUI(JComponent component) {
+ if (component instanceof JRibbon) {
+ return new BasicRibbonApplicationMenuButtonUI((JRibbon) component);
+ } else if (component instanceof JRibbonApplicationMenuButton) {
+ return new BasicRibbonApplicationMenuButtonUI(
+ ((JRibbonApplicationMenuButton) component).getRibbon());
+ }
+ throw new IllegalArgumentException(
+ "creating a BasicRibbonApplicationMenuButtonUI requires a JRibbon");
+ }
+
+ /**
+ * Returns the ribbon reference this button UI is created for.
+ *
+ * @return the ribbon reference
+ */
+ public JRibbon getRibbon() {
+ return ribbon;
+ }
+
+ /**
+ * Sets the ribbon this button UI is created for.
+ *
+ * @param ribbon
+ * the ribbon
+ */
+ protected void setRibbon(JRibbon ribbon) {
+ this.ribbon = ribbon;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicButtonUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.applicationMenuButton = (JRibbonApplicationMenuButton) c;
+ super.installUI(c);
+ }
+
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+
+ Border border = this.commandButton.getBorder();
+ if (border == null || border instanceof UIResource) {
+ Border toInstall = UIManager
+ .getBorder("RibbonApplicationMenuButton.border");
+ if (toInstall == null)
+ toInstall = new BorderUIResource.EmptyBorderUIResource(4, 4, 4,
+ 4);
+ this.commandButton.setBorder(toInstall);
+ }
+
+ this.commandButton.setOpaque(false);
+ }
+
+ @Override
+ protected void configureRenderer() {
+ this.buttonRendererPane = new CellRendererPane();
+ this.commandButton.add(buttonRendererPane);
+ this.rendererButton = new JButton("");
+ }
+
+ @Override
+ protected void unconfigureRenderer() {
+ this.commandButton.remove(this.buttonRendererPane);
+ this.buttonRendererPane = null;
+ this.rendererButton = null;
+ }
+
+ @Override
+ protected void installComponents() {
+ super.installComponents();
+
+ // we can't set the application menu popup panel if we have no reference
+ // to the application ribbon
+ if (ribbon != null) {
+ final JRibbonApplicationMenuButton appMenuButton = (JRibbonApplicationMenuButton) this.commandButton;
+ appMenuButton.setPopupCallback(new PopupPanelCallback() {
+ @Override
+ public JPopupPanel getPopupPanel(
+ final JCommandButton commandButton) {
+ // JRibbonFrame ribbonFrame = (JRibbonFrame) SwingUtilities
+ // .getWindowAncestor(commandButton);
+ // final JRibbon ribbon = ribbonFrame.getRibbon();
+ RibbonApplicationMenu ribbonMenu = ribbon
+ .getApplicationMenu();
+ final JRibbonApplicationMenuPopupPanel menuPopupPanel = new JRibbonApplicationMenuPopupPanel(
+ appMenuButton, ribbonMenu);
+ menuPopupPanel.applyComponentOrientation(appMenuButton
+ .getComponentOrientation());
+ menuPopupPanel
+ .setCustomizer(new JPopupPanel.PopupPanelCustomizer() {
+ @Override
+ public Rectangle getScreenBounds() {
+ boolean ltr = commandButton
+ .getComponentOrientation()
+ .isLeftToRight();
+
+ int pw = menuPopupPanel.getPreferredSize().width;
+ int x = ltr ? ribbon.getLocationOnScreen().x
+ : ribbon.getLocationOnScreen().x
+ + ribbon.getWidth() - pw;
+ int y = commandButton.getLocationOnScreen().y
+ + commandButton.getSize().height
+ / 2 + 2;
+
+ // make sure that the menu popup stays
+ // in bounds
+ Rectangle scrBounds = commandButton
+ .getGraphicsConfiguration()
+ .getBounds();
+ if ((x + pw) > (scrBounds.x + scrBounds.width)) {
+ x = scrBounds.x + scrBounds.width - pw;
+ }
+ int ph = menuPopupPanel.getPreferredSize().height;
+ if ((y + ph) > (scrBounds.y + scrBounds.height)) {
+ y = scrBounds.y + scrBounds.height - ph;
+ }
+
+ return new Rectangle(
+ x,
+ y,
+ menuPopupPanel.getPreferredSize().width,
+ menuPopupPanel.getPreferredSize().height);
+ }
+ });
+
+ return menuPopupPanel;
+ }
+ });
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#paint(java.awt.Graphics
+ * , javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ // g2d.setColor(Color.red);
+ // g2d.fillRect(0, 0, c.getWidth(), c.getHeight());
+ Insets ins = c.getInsets();
+ // System.out.println(c.getWidth() + ":" + c.getHeight());
+ this.paintButtonBackground(g2d,
+ new Rectangle(ins.left, ins.top, c.getWidth() - ins.left
+ - ins.right, c.getHeight() - ins.top - ins.bottom));
+
+ this.layoutInfo = this.layoutManager.getLayoutInfo(this.commandButton,
+ g);
+ commandButton.putClientProperty("icon.bounds", layoutInfo.iconRect);
+
+ this.paintButtonIcon(g2d, layoutInfo.iconRect);
+
+ g2d.dispose();
+ }
+
+ /**
+ * Paints the button background.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param toFill
+ * Rectangle to fill.
+ */
+ @Override
+ protected void paintButtonBackground(Graphics graphics, Rectangle toFill) {
+ // graphics.setColor(Color.red);
+ // graphics.fillRect(toFill.x, toFill.y, toFill.width,
+ // toFill.height);
+ // if (true)
+ // return;
+ //
+ // System.out.println(toFill);
+ this.buttonRendererPane.setBounds(toFill.x, toFill.y, toFill.width,
+ toFill.height);
+ ButtonModel model = this.rendererButton.getModel();
+ model.setEnabled(true);
+ model.setSelected(this.applicationMenuButton.getPopupModel()
+ .isSelected());
+ model.setRollover(this.applicationMenuButton.getPopupModel()
+ .isRollover());
+ model.setPressed(this.applicationMenuButton.getPopupModel().isPressed()
+ || this.applicationMenuButton.getPopupModel().isPopupShowing());
+ model.setArmed(this.applicationMenuButton.getActionModel().isArmed());
+
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.translate(toFill.x, toFill.y);
+
+ Shape clip = g2d.getClip();
+ g2d.clip(new Ellipse2D.Double(0, 0, toFill.width, toFill.height));
+ this.rendererButton.setBorderPainted(false);
+ this.buttonRendererPane.paintComponent(g2d, this.rendererButton,
+ this.applicationMenuButton, -toFill.width / 2,
+ -toFill.height / 2, 2 * toFill.width, 2 * toFill.height, true);
+ g2d.setColor(FlamingoUtilities.getBorderColor().darker());
+ g2d.setClip(clip);
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.draw(new Ellipse2D.Double(0, 0, toFill.width, toFill.height));
+ g2d.dispose();
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/BasicRibbonApplicationMenuPopupPanelUI.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/BasicRibbonApplicationMenuPopupPanelUI.java
new file mode 100644
index 0000000..b6599cd
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/BasicRibbonApplicationMenuPopupPanelUI.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon.appmenu;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonPopupOrientationKind;
+import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
+import org.pushingpixels.flamingo.api.ribbon.*;
+import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntryPrimary.PrimaryRolloverCallback;
+import org.pushingpixels.flamingo.internal.ui.common.popup.BasicPopupPanelUI;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+/**
+ * Basic UI for ribbon application menu button
+ * {@link JRibbonApplicationMenuButton}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasicRibbonApplicationMenuPopupPanelUI extends BasicPopupPanelUI {
+ protected JPanel panelLevel1;
+
+ protected JPanel panelLevel2;
+
+ protected JPanel footerPanel;
+
+ protected static final CommandButtonDisplayState MENU_TILE_LEVEL_1 = new CommandButtonDisplayState(
+ "Ribbon application menu tile level 1", 32) {
+ @Override
+ public CommandButtonLayoutManager createLayoutManager(
+ AbstractCommandButton commandButton) {
+ return new CommandButtonLayoutManagerMenuTileLevel1();
+ }
+ };
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicRibbonApplicationMenuPopupPanelUI();
+ }
+
+ /**
+ * The associated application menu button.
+ */
+ protected JRibbonApplicationMenuPopupPanel applicationMenuPopupPanel;
+
+ protected JPanel mainPanel;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicButtonUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ this.applicationMenuPopupPanel = (JRibbonApplicationMenuPopupPanel) c;
+ this.popupPanel = (JPopupPanel) c;
+
+ this.applicationMenuPopupPanel.setLayout(new BorderLayout());
+
+ installDefaults();
+ installComponents();
+ installListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ uninstallListeners();
+ uninstallComponents();
+ uninstallDefaults();
+
+ this.applicationMenuPopupPanel = null;
+ }
+
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ }
+
+ @Override
+ protected void installComponents() {
+ super.installComponents();
+
+ this.mainPanel = createMainPanel();
+
+ this.panelLevel1 = new JPanel();
+ this.panelLevel1.setLayout(new LayoutManager() {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ int height = 0;
+ int width = 0;
+ for (int i = 0; i < parent.getComponentCount(); i++) {
+ Dimension pref = parent.getComponent(i).getPreferredSize();
+ height += pref.height;
+ width = Math.max(width, pref.width);
+ }
+
+ Insets ins = parent.getInsets();
+ return new Dimension(width + ins.left + ins.right, height
+ + ins.top + ins.bottom);
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return preferredLayoutSize(parent);
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ Insets ins = parent.getInsets();
+
+ int topY = ins.top;
+ for (int i = 0; i < parent.getComponentCount(); i++) {
+ Component comp = parent.getComponent(i);
+ Dimension pref = comp.getPreferredSize();
+ comp.setBounds(ins.left, topY, parent.getWidth() - ins.left
+ - ins.right, pref.height);
+ topY += pref.height;
+ }
+ }
+ });
+
+ final RibbonApplicationMenu ribbonAppMenu = this.applicationMenuPopupPanel
+ .getRibbonAppMenu();
+
+ if (ribbonAppMenu != null) {
+ List<List<RibbonApplicationMenuEntryPrimary>> primaryEntries = ribbonAppMenu
+ .getPrimaryEntries();
+ int primaryGroupCount = primaryEntries.size();
+ for (int i = 0; i < primaryGroupCount; i++) {
+ for (final RibbonApplicationMenuEntryPrimary menuEntry : primaryEntries
+ .get(i)) {
+ final JCommandMenuButton commandButton = new JCommandMenuButton(
+ menuEntry.getText(), menuEntry.getIcon());
+ commandButton
+ .setCommandButtonKind(menuEntry.getEntryKind());
+ commandButton.addActionListener(menuEntry
+ .getMainActionListener());
+ commandButton.setActionKeyTip(menuEntry.getActionKeyTip());
+ commandButton.setPopupKeyTip(menuEntry.getPopupKeyTip());
+ commandButton.setActionRichTooltip(menuEntry.getActionRichTooltip());
+ commandButton.setPopupRichTooltip(menuEntry.getPopupRichTooltip());
+ if (menuEntry.getDisabledIcon() != null) {
+ commandButton.setDisabledIcon(menuEntry
+ .getDisabledIcon());
+ }
+ if (menuEntry.getSecondaryGroupCount() == 0) {
+ // if there are no secondary menu items, register the
+ // application rollover callback to populate the
+ // second level panel
+ commandButton
+ .addRolloverActionListener(new RolloverActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // System.out.println("Rollover action");
+ PrimaryRolloverCallback callback = menuEntry
+ .getRolloverCallback();
+ if (callback != null) {
+ callback
+ .menuEntryActivated(panelLevel2);
+ } else {
+ // default callback?
+ PrimaryRolloverCallback defaultCallback = ribbonAppMenu
+ .getDefaultCallback();
+ if (defaultCallback != null) {
+ defaultCallback
+ .menuEntryActivated(panelLevel2);
+ } else {
+ panelLevel2.removeAll();
+ panelLevel2.revalidate();
+ panelLevel2.repaint();
+ }
+ }
+ panelLevel2
+ .applyComponentOrientation(applicationMenuPopupPanel
+ .getComponentOrientation());
+ }
+ });
+ } else {
+ // register a core callback to populate the second level
+ // panel with secondary menu items
+ final PrimaryRolloverCallback coreCallback = new PrimaryRolloverCallback() {
+ @Override
+ public void menuEntryActivated(JPanel targetPanel) {
+ targetPanel.removeAll();
+ targetPanel.setLayout(new BorderLayout());
+ JRibbonApplicationMenuPopupPanelSecondary secondary = new JRibbonApplicationMenuPopupPanelSecondary(
+ menuEntry) {
+ @Override
+ public void removeNotify() {
+ super.removeNotify();
+ commandButton.getPopupModel()
+ .setPopupShowing(false);
+ }
+ };
+ secondary
+ .applyComponentOrientation(applicationMenuPopupPanel
+ .getComponentOrientation());
+ targetPanel.add(secondary, BorderLayout.CENTER);
+ }
+ };
+ commandButton
+ .addRolloverActionListener(new RolloverActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ coreCallback
+ .menuEntryActivated(panelLevel2);
+ // emulate showing the popup so the
+ // button remains "selected"
+ commandButton.getPopupModel()
+ .setPopupShowing(true);
+ }
+ });
+ }
+ commandButton.setDisplayState(MENU_TILE_LEVEL_1);
+ commandButton
+ .setHorizontalAlignment(SwingUtilities.LEADING);
+ commandButton
+ .setPopupOrientationKind(CommandButtonPopupOrientationKind.SIDEWARD);
+ commandButton.setEnabled(menuEntry.isEnabled());
+ this.panelLevel1.add(commandButton);
+ }
+ if (i < (primaryGroupCount - 1)) {
+ this.panelLevel1.add(new JPopupMenu.Separator());
+ }
+ }
+ }
+
+ mainPanel.add(this.panelLevel1, BorderLayout.LINE_START);
+
+ this.panelLevel2 = new JPanel();
+ this.panelLevel2.setBorder(new Border() {
+ @Override
+ public Insets getBorderInsets(Component c) {
+ boolean ltr = c.getComponentOrientation().isLeftToRight();
+ return new Insets(0, ltr ? 1 : 0, 0, ltr ? 0 : 1);
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return true;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ g.setColor(FlamingoUtilities.getColor(Color.gray,
+ "Label.disabledForeground"));
+ boolean ltr = c.getComponentOrientation().isLeftToRight();
+ int xToPaint = ltr ? x : x + width - 1;
+ g.drawLine(xToPaint, y, xToPaint, y + height);
+ }
+ });
+
+ // check to see if we hide second level menus
+ Object o = null;
+ if (this.applicationMenuPopupPanel.getAppMenuButton() != null && this.applicationMenuPopupPanel.getAppMenuButton().getRibbon() != null) {
+ o = this.applicationMenuPopupPanel.getAppMenuButton().getRibbon().getClientProperty("ribbon.hideSecondary");
+ }
+ if (o instanceof Boolean && (Boolean)o) {
+ this.panelLevel2.setPreferredSize(new Dimension(0, 10));
+ } else {
+ this.panelLevel2.setPreferredSize(new Dimension(30 * FlamingoUtilities
+ .getFont(this.panelLevel1, "Ribbon.font", "Button.font",
+ "Panel.font").getSize() - 30, 10));
+ }
+
+ mainPanel.add(this.panelLevel2, BorderLayout.CENTER);
+
+ if (ribbonAppMenu != null) {
+ if (ribbonAppMenu.getDefaultCallback() != null) {
+ ribbonAppMenu.getDefaultCallback().menuEntryActivated(
+ this.panelLevel2);
+ }
+ }
+
+ this.applicationMenuPopupPanel.add(mainPanel, BorderLayout.CENTER);
+
+ this.footerPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING)) {
+ @Override
+ protected void paintComponent(Graphics g) {
+ FlamingoUtilities.renderSurface(g, footerPanel, new Rectangle(
+ 0, 0, footerPanel.getWidth(), footerPanel.getHeight()),
+ false, false, false);
+ }
+ };
+ if (ribbonAppMenu != null) {
+ for (RibbonApplicationMenuEntryFooter footerEntry : ribbonAppMenu
+ .getFooterEntries()) {
+ JCommandButton commandFooterButton = new JCommandButton(
+ footerEntry.getText(), footerEntry.getIcon());
+ if (footerEntry.getDisabledIcon() != null) {
+ commandFooterButton.setDisabledIcon(footerEntry
+ .getDisabledIcon());
+ }
+ commandFooterButton
+ .setCommandButtonKind(CommandButtonKind.ACTION_ONLY);
+ commandFooterButton.addActionListener(footerEntry
+ .getMainActionListener());
+ commandFooterButton
+ .setDisplayState(CommandButtonDisplayState.MEDIUM);
+ commandFooterButton.setFlat(false);
+ commandFooterButton.setEnabled(footerEntry.isEnabled());
+ this.footerPanel.add(commandFooterButton);
+ }
+ }
+
+ this.applicationMenuPopupPanel
+ .add(this.footerPanel, BorderLayout.SOUTH);
+
+ this.applicationMenuPopupPanel.setBorder(new Border() {
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return new Insets(20, 2, 2, 2);
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return true;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ g.setColor(FlamingoUtilities.getColor(Color.gray,
+ "Label.disabledForeground"));
+ g.drawRect(x, y, width - 1, height - 1);
+ g.setColor(FlamingoUtilities.getColor(Color.gray,
+ "Label.disabledForeground").brighter().brighter());
+ g.drawRect(x + 1, y + 1, width - 3, height - 3);
+ FlamingoUtilities.renderSurface(g, applicationMenuPopupPanel,
+ new Rectangle(x + 2, y + 2, width - 4, 24), false,
+ false, false);
+
+ // draw the application menu button
+ JRibbonApplicationMenuButton button = applicationMenuPopupPanel
+ .getAppMenuButton();
+ JRibbonApplicationMenuButton rendererButton = new JRibbonApplicationMenuButton(
+ applicationMenuPopupPanel.getAppMenuButton()
+ .getRibbon());
+ rendererButton.setPopupKeyTip(button.getPopupKeyTip());
+ rendererButton.setIcon(button.getIcon());
+ rendererButton.getPopupModel().setRollover(false);
+ rendererButton.getPopupModel().setPressed(true);
+ rendererButton.getPopupModel().setArmed(true);
+ rendererButton.getPopupModel().setPopupShowing(true);
+
+ CellRendererPane buttonRendererPane = new CellRendererPane();
+ Point buttonLoc = button.getLocationOnScreen();
+ Point panelLoc = c.getLocationOnScreen();
+
+ buttonRendererPane.setBounds(panelLoc.x - buttonLoc.x,
+ panelLoc.y - buttonLoc.y, button.getWidth(), button
+ .getHeight());
+ buttonRendererPane.paintComponent(g, rendererButton,
+ (Container) c, -panelLoc.x + buttonLoc.x, -panelLoc.y
+ + buttonLoc.y, button.getWidth(), button
+ .getHeight(), true);
+ }
+ });
+ }
+
+ protected JPanel createMainPanel() {
+ JPanel result = new JPanel(new BorderLayout());
+ result.setBorder(new Border() {
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return new Insets(2, 2, 2, 2);
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return true;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ g.setColor(FlamingoUtilities.getColor(Color.gray,
+ "Label.disabledForeground").brighter().brighter());
+ g.drawRect(x, y, width - 1, height - 1);
+ g.setColor(FlamingoUtilities.getColor(Color.gray,
+ "Label.disabledForeground"));
+ g.drawRect(x + 1, y + 1, width - 3, height - 3);
+ }
+ });
+ return result;
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ }
+
+ @Override
+ protected void uninstallDefaults() {
+ super.uninstallDefaults();
+ }
+
+ @Override
+ protected void uninstallComponents() {
+ super.uninstallComponents();
+ }
+
+ @Override
+ protected void uninstallListeners() {
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#paint(java.awt.Graphics
+ * , javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ g2d.dispose();
+ }
+
+ public JPanel getPanelLevel1() {
+ return panelLevel1;
+ }
+
+ public JPanel getPanelLevel2() {
+ return panelLevel2;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/CommandButtonLayoutManagerMenuTileLevel1.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/CommandButtonLayoutManagerMenuTileLevel1.java
new file mode 100644
index 0000000..8b0fb3b
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/CommandButtonLayoutManagerMenuTileLevel1.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon.appmenu;
+
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.util.ArrayList;
+
+import javax.swing.JSeparator;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+public class CommandButtonLayoutManagerMenuTileLevel1 implements
+ CommandButtonLayoutManager {
+
+ @Override
+ public int getPreferredIconSize() {
+ return 32;
+ }
+
+ @Override
+ public Dimension getPreferredSize(AbstractCommandButton commandButton) {
+ Insets borderInsets = commandButton.getInsets();
+ int bx = borderInsets.left + borderInsets.right;
+ int by = borderInsets.top + borderInsets.bottom;
+ FontMetrics fm = commandButton.getFontMetrics(commandButton.getFont());
+ JSeparator jsep = new JSeparator(JSeparator.VERTICAL);
+
+ int titleWidth = fm.stringWidth(commandButton.getText());
+ int layoutHGap = 2 * FlamingoUtilities.getHLayoutGap(commandButton);
+ int layoutVGap = 2 * FlamingoUtilities.getVLayoutGap(commandButton);
+ int widthMed = this.getPreferredIconSize()
+ + 2
+ * layoutHGap
+ + jsep.getPreferredSize().width
+ + titleWidth
+ + (FlamingoUtilities.hasPopupAction(commandButton) ? 1
+ + fm.getHeight() / 2 + 4 * layoutHGap
+ + jsep.getPreferredSize().width : 0);
+ return new Dimension(bx + widthMed, by
+ + Math.max(this.getPreferredIconSize(), 2
+ * (fm.getAscent() + fm.getDescent()) + layoutVGap));
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ }
+
+ @Override
+ public Point getKeyTipAnchorCenterPoint(AbstractCommandButton commandButton) {
+ Insets ins = commandButton.getInsets();
+ int height = commandButton.getHeight();
+ ResizableIcon buttonIcon = commandButton.getIcon();
+ // bottom-right corner of the icon area
+ return new Point(ins.left + buttonIcon.getIconWidth(), height - ins.top
+ - ins.bottom);
+ }
+
+ @Override
+ public CommandButtonLayoutInfo getLayoutInfo(
+ AbstractCommandButton commandButton, Graphics g) {
+ CommandButtonLayoutInfo result = new CommandButtonLayoutInfo();
+
+ result.actionClickArea = new Rectangle(0, 0, 0, 0);
+ result.popupClickArea = new Rectangle(0, 0, 0, 0);
+
+ Insets ins = commandButton.getInsets();
+
+ result.iconRect = new Rectangle();
+ result.popupActionRect = new Rectangle();
+
+ int width = commandButton.getWidth();
+ int height = commandButton.getHeight();
+
+ FontMetrics fm = g.getFontMetrics();
+ int labelHeight = fm.getAscent() + fm.getDescent();
+
+ JCommandButton.CommandButtonKind buttonKind = (commandButton instanceof JCommandButton) ? ((JCommandButton) commandButton)
+ .getCommandButtonKind()
+ : JCommandButton.CommandButtonKind.ACTION_ONLY;
+
+ if (buttonKind == JCommandButton.CommandButtonKind.ACTION_ONLY) {
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width;
+ result.actionClickArea.height = height;
+ result.isTextInActionArea = true;
+ }
+ if (buttonKind == JCommandButton.CommandButtonKind.POPUP_ONLY) {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+ result.isTextInActionArea = false;
+ }
+
+ JSeparator jsep = new JSeparator(JSeparator.VERTICAL);
+ int layoutHGap = 2 * FlamingoUtilities.getHLayoutGap(commandButton);
+
+ boolean ltr = commandButton.getComponentOrientation().isLeftToRight();
+
+ ResizableIcon buttonIcon = commandButton.getIcon();
+
+ if (ltr) {
+ int x = ins.left;
+ // small icon, 1-line text, 1-line extra text and action arrow
+ if (buttonIcon != null) {
+ result.iconRect.x = x;
+ result.iconRect.y = (height - buttonIcon.getIconHeight()) / 2;
+ result.iconRect.width = buttonIcon.getIconWidth();
+ result.iconRect.height = buttonIcon.getIconHeight();
+
+ x += buttonIcon.getIconWidth();
+ }
+ if (buttonKind == JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_POPUP) {
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = x + layoutHGap;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = x + layoutHGap;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width - x - layoutHGap;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = x + layoutHGap;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = false;
+ }
+ x += 2 * layoutHGap + jsep.getPreferredSize().width;
+
+ TextLayoutInfo lineLayoutInfo = new TextLayoutInfo();
+ lineLayoutInfo.text = commandButton.getText();
+ lineLayoutInfo.textRect = new Rectangle();
+
+ lineLayoutInfo.textRect.x = x;
+ lineLayoutInfo.textRect.y = (height - labelHeight) / 2;
+ lineLayoutInfo.textRect.width = fm.stringWidth(commandButton
+ .getText());
+ lineLayoutInfo.textRect.height = labelHeight;
+
+ result.textLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.textLayoutInfoList.add(lineLayoutInfo);
+
+ x += fm.getStringBounds(commandButton.getText(), g).getWidth();
+ if (buttonKind == JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION) {
+ // popup click areas are right aligned
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width - ins.right - labelHeight;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = width - ins.right - labelHeight;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = labelHeight + ins.right;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = width - ins.right - labelHeight;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = true;
+ }
+
+ if (FlamingoUtilities.hasPopupAction(commandButton)) {
+ result.popupActionRect.x = width - ins.right - labelHeight * 3
+ / 4;
+ result.popupActionRect.y = (height - labelHeight) / 2 - 1;
+ result.popupActionRect.width = 1 + labelHeight / 2;
+ result.popupActionRect.height = labelHeight + 2;
+ }
+ } else {
+ int x = commandButton.getWidth() - ins.right;
+ // small icon, 1-line text, 1-line extra text and action arrow
+ if (buttonIcon != null) {
+ result.iconRect.x = x - buttonIcon.getIconWidth();
+ result.iconRect.y = (height - buttonIcon.getIconHeight()) / 2;
+ result.iconRect.width = buttonIcon.getIconWidth();
+ result.iconRect.height = buttonIcon.getIconHeight();
+
+ x -= buttonIcon.getIconWidth();
+ }
+ if (buttonKind == JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_POPUP) {
+ result.actionClickArea.x = x + layoutHGap;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width - x - layoutHGap;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = x + layoutHGap;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = x + layoutHGap;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = false;
+ }
+ x -= (2 * layoutHGap + jsep.getPreferredSize().width);
+
+ TextLayoutInfo lineLayoutInfo = new TextLayoutInfo();
+ lineLayoutInfo.text = commandButton.getText();
+ lineLayoutInfo.textRect = new Rectangle();
+
+ lineLayoutInfo.textRect.width = fm.stringWidth(commandButton
+ .getText());
+ lineLayoutInfo.textRect.x = x - lineLayoutInfo.textRect.width;
+ lineLayoutInfo.textRect.y = (height - labelHeight) / 2;
+ lineLayoutInfo.textRect.height = labelHeight;
+
+ result.textLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.textLayoutInfoList.add(lineLayoutInfo);
+
+ if (buttonKind == JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION) {
+ // popup click areas are left aligned
+ result.actionClickArea.x = labelHeight + ins.left;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width - ins.right - labelHeight;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = ins.left + labelHeight;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = labelHeight + ins.left;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = true;
+ }
+
+ if (FlamingoUtilities.hasPopupAction(commandButton)) {
+ result.popupActionRect.x = ins.left + labelHeight / 4;
+ result.popupActionRect.y = (height - labelHeight) / 2 - 1;
+ result.popupActionRect.width = 1 + labelHeight / 2;
+ result.popupActionRect.height = labelHeight + 2;
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/CommandButtonLayoutManagerMenuTileLevel2.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/CommandButtonLayoutManagerMenuTileLevel2.java
new file mode 100755
index 0000000..bd9ae05
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/CommandButtonLayoutManagerMenuTileLevel2.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon.appmenu;
+
+import java.awt.*;
+import java.awt.font.LineBreakMeasurer;
+import java.awt.font.TextAttribute;
+import java.beans.PropertyChangeEvent;
+import java.text.AttributedString;
+import java.util.ArrayList;
+
+import javax.swing.JSeparator;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+
+public class CommandButtonLayoutManagerMenuTileLevel2 implements
+ CommandButtonLayoutManager {
+
+ @Override
+ public int getPreferredIconSize() {
+ return 32;
+ }
+
+ @Override
+ public Dimension getPreferredSize(AbstractCommandButton commandButton) {
+ FontMetrics fm = commandButton.getFontMetrics(commandButton.getFont());
+ JSeparator jsep = new JSeparator(JSeparator.VERTICAL);
+
+ int titleWidth = fm.stringWidth(commandButton.getText());
+ int layoutHGap = 2 * FlamingoUtilities.getHLayoutGap(commandButton);
+ int layoutVGap = 2 * FlamingoUtilities.getVLayoutGap(commandButton);
+
+ Insets borderInsets = commandButton.getInsets();
+ int bx = borderInsets.left + borderInsets.right;
+ int by = borderInsets.top + borderInsets.bottom + layoutVGap*2;
+
+ int widthMed = this.getPreferredIconSize()
+ + 2
+ * layoutHGap
+ + jsep.getPreferredSize().width
+ + titleWidth
+ + (FlamingoUtilities.hasPopupAction(commandButton) ? 1
+ + fm.getHeight() / 2 + 4 * layoutHGap
+ + jsep.getPreferredSize().width : 0);
+
+ // height - three lines of text and two gaps between them.
+ // The gap between the lines is half the main gap.
+ int fontHeight = fm.getAscent() + fm.getDescent();
+ int textHeight = fontHeight + layoutVGap;
+ String extraText = commandButton.getExtraText();
+ if ((extraText != null) && (extraText.length() > 0)) {
+ textHeight += 2 * fontHeight;
+ }
+ return new Dimension(bx + widthMed, by
+ + Math.max(this.getPreferredIconSize(), textHeight));
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ }
+
+ @Override
+ public Point getKeyTipAnchorCenterPoint(AbstractCommandButton commandButton) {
+ Insets ins = commandButton.getInsets();
+ int height = commandButton.getHeight();
+ ResizableIcon buttonIcon = commandButton.getIcon();
+ // bottom-right corner of the icon area
+ return new Point(ins.left + buttonIcon.getIconWidth(),
+ (height + buttonIcon.getIconHeight()) / 2);
+ }
+
+ @Override
+ public CommandButtonLayoutInfo getLayoutInfo(
+ AbstractCommandButton commandButton, Graphics g) {
+ CommandButtonLayoutInfo result = new CommandButtonLayoutInfo();
+
+ result.actionClickArea = new Rectangle(0, 0, 0, 0);
+ result.popupClickArea = new Rectangle(0, 0, 0, 0);
+
+ Insets ins = commandButton.getInsets();
+
+ result.iconRect = new Rectangle();
+ result.popupActionRect = new Rectangle();
+
+ int width = commandButton.getWidth();
+ int height = commandButton.getHeight();
+
+ FontMetrics fm = g.getFontMetrics();
+ int labelHeight = fm.getAscent() + fm.getDescent();
+
+ JCommandButton.CommandButtonKind buttonKind = (commandButton instanceof JCommandButton) ? ((JCommandButton) commandButton)
+ .getCommandButtonKind()
+ : JCommandButton.CommandButtonKind.ACTION_ONLY;
+
+ if (buttonKind == JCommandButton.CommandButtonKind.ACTION_ONLY) {
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width;
+ result.actionClickArea.height = height;
+ result.isTextInActionArea = true;
+ }
+ if (buttonKind == JCommandButton.CommandButtonKind.POPUP_ONLY) {
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width;
+ result.popupClickArea.height = height;
+ result.isTextInActionArea = false;
+ }
+
+ JSeparator jsep = new JSeparator(JSeparator.VERTICAL);
+ int layoutHGap = 2 * FlamingoUtilities.getHLayoutGap(commandButton);
+ int layoutVGap = 2 * FlamingoUtilities.getVLayoutGap(commandButton);
+
+ ResizableIcon buttonIcon = commandButton.getIcon();
+ boolean ltr = commandButton.getComponentOrientation().isLeftToRight();
+
+ if (ltr) {
+ int x = ins.left;
+ // medium icon, 1-line text, 1-line extra text and action arrow
+ if (buttonIcon != null) {
+ result.iconRect.x = x;
+ result.iconRect.y = ins.top + layoutVGap;
+ result.iconRect.width = buttonIcon.getIconWidth();
+ result.iconRect.height = buttonIcon.getIconHeight();
+
+ x += buttonIcon.getIconWidth();
+ }
+ if (buttonKind == JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_POPUP) {
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = x + layoutHGap;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = x + layoutHGap;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = width - x - layoutHGap;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = x + layoutHGap;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = false;
+ }
+ x += 2 * layoutHGap + jsep.getPreferredSize().width;
+
+ TextLayoutInfo lineLayoutInfo = new TextLayoutInfo();
+ lineLayoutInfo.text = commandButton.getText();
+ lineLayoutInfo.textRect = new Rectangle();
+
+ lineLayoutInfo.textRect.x = x;
+ lineLayoutInfo.textRect.y = ins.top + layoutVGap / 2;
+ lineLayoutInfo.textRect.width = fm.stringWidth(commandButton
+ .getText());
+ lineLayoutInfo.textRect.height = labelHeight;
+
+ result.textLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.textLayoutInfoList.add(lineLayoutInfo);
+
+ String extraText = commandButton.getExtraText();
+ if ((extraText == null) || (extraText.length() == 0)) {
+ lineLayoutInfo.textRect.y = (height - labelHeight) / 2;
+ } else {
+ AttributedString attributedDescription = new AttributedString(
+ commandButton.getExtraText());
+ attributedDescription.addAttribute(TextAttribute.FONT,
+ g.getFont());
+ LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer(
+ attributedDescription.getIterator(),
+ ((Graphics2D) g).getFontRenderContext());
+ // The max width of the extra text line - need to leave
+ // space for the popup arrow icon
+ int maxFirstExtraLineWidth = width - x - ins.right - layoutHGap
+ - labelHeight;
+ int breakIndex = lineBreakMeasurer
+ .nextOffset(maxFirstExtraLineWidth);
+
+ TextLayoutInfo extraLineLayoutInfo1 = new TextLayoutInfo();
+ extraLineLayoutInfo1.text = commandButton.getExtraText()
+ .substring(0, breakIndex);
+ extraLineLayoutInfo1.textRect = new Rectangle();
+
+ extraLineLayoutInfo1.textRect.x = x;
+ extraLineLayoutInfo1.textRect.y = ins.top + layoutVGap
+ + labelHeight;
+ extraLineLayoutInfo1.textRect.width = fm
+ .stringWidth(extraLineLayoutInfo1.text);
+ extraLineLayoutInfo1.textRect.height = labelHeight;
+
+ TextLayoutInfo extraLineLayoutInfo2 = new TextLayoutInfo();
+ extraLineLayoutInfo2.text = commandButton.getExtraText()
+ .substring(breakIndex);
+ extraLineLayoutInfo2.textRect = new Rectangle();
+
+ extraLineLayoutInfo2.textRect.x = x;
+ extraLineLayoutInfo2.textRect.y = ins.top + layoutVGap + 2
+ * labelHeight;
+ extraLineLayoutInfo2.textRect.width = fm
+ .stringWidth(extraLineLayoutInfo2.text);
+ extraLineLayoutInfo2.textRect.height = labelHeight;
+
+ result.extraTextLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.extraTextLayoutInfoList.add(extraLineLayoutInfo1);
+ result.extraTextLayoutInfoList.add(extraLineLayoutInfo2);
+ }
+
+ x += fm.getStringBounds(commandButton.getText(), g).getWidth();
+ if (buttonKind == JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION) {
+ // popup click areas are right aligned
+ result.actionClickArea.x = 0;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width - ins.right - labelHeight;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = width - ins.right - labelHeight;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = labelHeight + ins.right;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = width - ins.right - labelHeight;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ result.separatorArea.height = height;
+ result.isTextInActionArea = true;
+ }
+
+ if (FlamingoUtilities.hasPopupAction(commandButton)) {
+ result.popupActionRect.x = width - ins.right - labelHeight * 3
+ / 4;
+ result.popupActionRect.y = (height - labelHeight) / 2 - 1;
+ result.popupActionRect.width = 1 + labelHeight / 2;
+ result.popupActionRect.height = labelHeight + 2;
+ }
+ } else {
+ int x = commandButton.getWidth() - ins.right;
+ // medium icon, 1-line text, 1-line extra text and action arrow
+ if (buttonIcon != null) {
+ result.iconRect.x = x - buttonIcon.getIconWidth();
+ result.iconRect.y = ins.top + layoutVGap;
+ result.iconRect.width = buttonIcon.getIconWidth();
+ result.iconRect.height = buttonIcon.getIconHeight();
+
+ x -= buttonIcon.getIconWidth();
+ }
+ if (buttonKind == JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_POPUP) {
+ result.actionClickArea.x = x + layoutHGap;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width - x - layoutHGap;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = x + layoutHGap;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = x + layoutHGap;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = false;
+ }
+ x -= (2 * layoutHGap + jsep.getPreferredSize().width);
+
+ TextLayoutInfo lineLayoutInfo = new TextLayoutInfo();
+ lineLayoutInfo.text = commandButton.getText();
+ lineLayoutInfo.textRect = new Rectangle();
+
+ lineLayoutInfo.textRect.width = fm.stringWidth(commandButton
+ .getText());
+ lineLayoutInfo.textRect.x = x - lineLayoutInfo.textRect.width;
+ lineLayoutInfo.textRect.y = ins.top + layoutVGap / 2;
+ lineLayoutInfo.textRect.height = labelHeight;
+
+ result.textLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.textLayoutInfoList.add(lineLayoutInfo);
+
+ String extraText = commandButton.getExtraText();
+ if ((extraText == null) || (extraText.length() == 0)) {
+ lineLayoutInfo.textRect.y = (height - labelHeight) / 2;
+ } else {
+ AttributedString attributedDescription = new AttributedString(
+ commandButton.getExtraText());
+ attributedDescription.addAttribute(TextAttribute.FONT,
+ g.getFont());
+ LineBreakMeasurer lineBreakMeasurer = new LineBreakMeasurer(
+ attributedDescription.getIterator(),
+ ((Graphics2D) g).getFontRenderContext());
+ // The max width of the extra text line - need to leave
+ // space for the popup arrow icon
+ int maxFirstExtraLineWidth = x - ins.left - layoutHGap
+ - labelHeight;
+ int breakIndex = lineBreakMeasurer
+ .nextOffset(maxFirstExtraLineWidth);
+
+ TextLayoutInfo extraLineLayoutInfo1 = new TextLayoutInfo();
+ extraLineLayoutInfo1.text = commandButton.getExtraText()
+ .substring(0, breakIndex);
+ extraLineLayoutInfo1.textRect = new Rectangle();
+
+ extraLineLayoutInfo1.textRect.width = fm
+ .stringWidth(extraLineLayoutInfo1.text);
+ extraLineLayoutInfo1.textRect.x = x
+ - extraLineLayoutInfo1.textRect.width;
+ extraLineLayoutInfo1.textRect.y = ins.top + layoutVGap
+ + labelHeight;
+ extraLineLayoutInfo1.textRect.height = labelHeight;
+
+ TextLayoutInfo extraLineLayoutInfo2 = new TextLayoutInfo();
+ extraLineLayoutInfo2.text = commandButton.getExtraText()
+ .substring(breakIndex);
+ extraLineLayoutInfo2.textRect = new Rectangle();
+
+ extraLineLayoutInfo2.textRect.width = fm
+ .stringWidth(extraLineLayoutInfo2.text);
+ extraLineLayoutInfo2.textRect.x = x
+ - extraLineLayoutInfo2.textRect.width;
+ extraLineLayoutInfo2.textRect.y = ins.top + layoutVGap + 2
+ * labelHeight;
+ extraLineLayoutInfo2.textRect.height = labelHeight;
+
+ result.extraTextLayoutInfoList = new ArrayList<TextLayoutInfo>();
+ result.extraTextLayoutInfoList.add(extraLineLayoutInfo1);
+ result.extraTextLayoutInfoList.add(extraLineLayoutInfo2);
+ }
+
+ if (buttonKind == JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION) {
+ // popup click areas are left aligned
+ result.actionClickArea.x = labelHeight + ins.left;
+ result.actionClickArea.y = 0;
+ result.actionClickArea.width = width - ins.right - labelHeight;
+ result.actionClickArea.height = height;
+
+ result.popupClickArea.x = 0;
+ result.popupClickArea.y = 0;
+ result.popupClickArea.width = ins.left + labelHeight;
+ result.popupClickArea.height = height;
+
+ result.separatorOrientation = CommandButtonSeparatorOrientation.VERTICAL;
+ result.separatorArea = new Rectangle();
+ result.separatorArea.x = labelHeight + ins.left;
+ result.separatorArea.y = 0;
+ result.separatorArea.width = new JSeparator(JSeparator.VERTICAL)
+ .getPreferredSize().width;
+ result.separatorArea.height = height;
+
+ result.isTextInActionArea = true;
+ }
+
+ if (FlamingoUtilities.hasPopupAction(commandButton)) {
+ result.popupActionRect.x = ins.left + labelHeight / 4;
+ result.popupActionRect.y = (height - labelHeight) / 2 - 1;
+ result.popupActionRect.width = 1 + labelHeight / 2;
+ result.popupActionRect.height = labelHeight + 2;
+ }
+ }
+ return result;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/JRibbonApplicationMenuButton.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/JRibbonApplicationMenuButton.java
new file mode 100644
index 0000000..9832dc0
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/JRibbonApplicationMenuButton.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon.appmenu;
+
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+
+import javax.swing.UIManager;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.icon.EmptyResizableIcon;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.ribbon.JRibbon;
+import org.pushingpixels.flamingo.api.ribbon.JRibbonFrame;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonUI;
+
+/**
+ * The main application menu button for {@link JRibbon} component placed in a
+ * {@link JRibbonFrame}. This class is for internal use only and is intended for
+ * look-and-feel layer customization.
+ *
+ * @author Kirill Grouchnikov
+ */
+ at SuppressWarnings("serial")
+public class JRibbonApplicationMenuButton extends JCommandButton {
+
+ /** The application ribbon this menu button is for */
+ private JRibbon ribbon;
+ /** The UI class ID string. */
+ public static final String uiClassID = "RibbonApplicationMenuButtonUI";
+ static final int APP_BUTTON_SIZE = Integer.getInteger(
+ "peacock.appButtonSize", 24);
+
+ private final static CommandButtonDisplayState APP_MENU_BUTTON_STATE = new CommandButtonDisplayState(
+ "Ribbon Application Menu Button", APP_BUTTON_SIZE) {
+ @Override
+ public org.pushingpixels.flamingo.api.common.CommandButtonLayoutManager createLayoutManager(
+ org.pushingpixels.flamingo.api.common.AbstractCommandButton commandButton) {
+ return new CommandButtonLayoutManager() {
+ @Override
+ public int getPreferredIconSize() {
+ return APP_BUTTON_SIZE;
+ }
+
+ @Override
+ public CommandButtonLayoutInfo getLayoutInfo(
+ AbstractCommandButton commandButton, Graphics g) {
+ CommandButtonLayoutInfo result = new CommandButtonLayoutInfo();
+ result.actionClickArea = new Rectangle(0, 0, 0, 0);
+ result.popupClickArea = new Rectangle(0, 0, commandButton
+ .getWidth(), commandButton.getHeight());
+ result.popupActionRect = new Rectangle(0, 0, 0, 0);
+ ResizableIcon icon = commandButton.getIcon();
+ if (icon != null) {
+ result.iconRect = new Rectangle((commandButton
+ .getWidth() - icon.getIconWidth()) / 2,
+ (commandButton.getHeight() - icon
+ .getIconHeight()) / 2, icon
+ .getIconWidth(), icon.getIconHeight());
+ }
+ result.isTextInActionArea = false;
+ return result;
+ }
+
+ @Override
+ public Dimension getPreferredSize(
+ AbstractCommandButton commandButton) {
+ return new Dimension(40, 40);
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ }
+
+ @Override
+ public Point getKeyTipAnchorCenterPoint(
+ AbstractCommandButton commandButton) {
+ // dead center
+ return new Point(commandButton.getWidth() / 2,
+ commandButton.getHeight() / 2);
+ }
+ };
+ }
+ };
+
+ /**
+ * Constructs a <code>JRibbonApplicationMenuButton</code> specifying the
+ * ribbon component it belongs to. If the <code>ribbon</code>'s application
+ * icon is <code>null</code> an {@link EmptyResizableIcon} is used for this
+ * button.
+ * <p>
+ * A <code>JRibbonApplicationMenuButton</code> is a
+ * {@link org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind#POPUP_ONLY} button and uses a custom button
+ * display state (see {@link #APP_MENU_BUTTON_STATE}).
+ *
+ * @param ribbon
+ * the ribbon component
+ */
+ public JRibbonApplicationMenuButton(JRibbon ribbon) {
+ super("", (ribbon != null && ribbon.getApplicationIcon() != null) ? ribbon
+ .getApplicationIcon() : new EmptyResizableIcon(16));
+ this.setCommandButtonKind(CommandButtonKind.POPUP_ONLY);
+ this.setDisplayState(APP_MENU_BUTTON_STATE);
+ setRibbon(ribbon);
+ // update the UI now that the ribbon has been set
+ updateUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JButton#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((BasicCommandButtonUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicRibbonApplicationMenuButtonUI.createUI(this));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JButton#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ /**
+ * Returns a reference to the application ribbon this application menu
+ * button is for.
+ *
+ * @return the application ribbon
+ */
+ public synchronized JRibbon getRibbon() {
+ return this.ribbon;
+ }
+
+ /**
+ * Sets the ribbon this application menu button is created for.
+ *
+ * @param ribbon
+ * the application ribbon
+ */
+ public synchronized void setRibbon(JRibbon ribbon) {
+ this.ribbon = ribbon;
+ }
+
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/JRibbonApplicationMenuPopupPanel.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/JRibbonApplicationMenuPopupPanel.java
new file mode 100644
index 0000000..e552147
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/JRibbonApplicationMenuPopupPanel.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon.appmenu;
+
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+
+import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
+import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenu;
+import org.pushingpixels.flamingo.internal.ui.common.popup.BasicPopupPanelUI;
+
+public class JRibbonApplicationMenuPopupPanel extends JPopupPanel {
+ /**
+ * The UI class ID string.
+ */
+ public static final String uiClassID = "RibbonApplicationMenuPopupPanelUI";
+
+ protected JRibbonApplicationMenuButton appMenuButton;
+
+ protected RibbonApplicationMenu ribbonAppMenu;
+
+ public JRibbonApplicationMenuPopupPanel(
+ final JRibbonApplicationMenuButton button,
+ RibbonApplicationMenu ribbonAppMenu) {
+ this.appMenuButton = button;
+ this.ribbonAppMenu = ribbonAppMenu;
+
+ this.updateUI();
+ }
+
+ public JPanel getPanelLevel1() {
+ return ((BasicRibbonApplicationMenuPopupPanelUI) getUI())
+ .getPanelLevel1();
+ }
+
+ public JPanel getPanelLevel2() {
+ return ((BasicRibbonApplicationMenuPopupPanelUI) getUI())
+ .getPanelLevel2();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JButton#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ if (UIManager.get(getUIClassID()) != null) {
+ setUI((BasicPopupPanelUI) UIManager.getUI(this));
+ } else {
+ setUI(BasicRibbonApplicationMenuPopupPanelUI.createUI(this));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JButton#getUIClassID()
+ */
+ @Override
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ public JRibbonApplicationMenuButton getAppMenuButton() {
+ return appMenuButton;
+ }
+
+ public RibbonApplicationMenu getRibbonAppMenu() {
+ return ribbonAppMenu;
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/JRibbonApplicationMenuPopupPanelSecondary.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/JRibbonApplicationMenuPopupPanelSecondary.java
new file mode 100644
index 0000000..c6120bd
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/ui/ribbon/appmenu/JRibbonApplicationMenuPopupPanelSecondary.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.ui.ribbon.appmenu;
+
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonPopupOrientationKind;
+import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntryPrimary;
+import org.pushingpixels.flamingo.api.ribbon.RibbonApplicationMenuEntrySecondary;
+
+public class JRibbonApplicationMenuPopupPanelSecondary extends
+ JCommandButtonPanel {
+ protected static final CommandButtonDisplayState MENU_TILE_LEVEL_2 = new CommandButtonDisplayState(
+ "Ribbon application menu tile level 2", 32) {
+ @Override
+ public CommandButtonLayoutManager createLayoutManager(
+ AbstractCommandButton commandButton) {
+ return new CommandButtonLayoutManagerMenuTileLevel2();
+ }
+ };
+
+ public JRibbonApplicationMenuPopupPanelSecondary(
+ RibbonApplicationMenuEntryPrimary primaryMenuEntry) {
+ super(MENU_TILE_LEVEL_2);
+ this.setMaxButtonColumns(1);
+
+ int groupCount = primaryMenuEntry.getSecondaryGroupCount();
+ for (int i = 0; i < groupCount; i++) {
+ String groupDesc = primaryMenuEntry.getSecondaryGroupTitleAt(i);
+ this.addButtonGroup(groupDesc);
+
+ for (final RibbonApplicationMenuEntrySecondary menuEntry : primaryMenuEntry
+ .getSecondaryGroupEntries(i)) {
+ JCommandMenuButton commandButton = new JCommandMenuButton(
+ menuEntry.getText(), menuEntry.getIcon());
+ commandButton.setExtraText(menuEntry.getDescriptionText());
+ commandButton.setCommandButtonKind(menuEntry.getEntryKind());
+ commandButton.addActionListener(menuEntry
+ .getMainActionListener());
+ commandButton.setDisplayState(MENU_TILE_LEVEL_2);
+ commandButton.setHorizontalAlignment(SwingUtilities.LEADING);
+ commandButton
+ .setPopupOrientationKind(CommandButtonPopupOrientationKind.SIDEWARD);
+ commandButton.setEnabled(menuEntry.isEnabled());
+ commandButton.setPopupCallback(menuEntry.getPopupCallback());
+ commandButton.setActionKeyTip(menuEntry.getActionKeyTip());
+ commandButton.setPopupKeyTip(menuEntry.getPopupKeyTip());
+ commandButton.setActionRichTooltip(menuEntry.getActionRichTooltip());
+ commandButton.setPopupRichTooltip(menuEntry.getPopupRichTooltip());
+ if (menuEntry.getDisabledIcon() != null) {
+ commandButton.setDisabledIcon(menuEntry.getDisabledIcon());
+ }
+ this.addButtonToLastGroup(commandButton);
+ }
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/AbstractFilter.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/AbstractFilter.java
new file mode 100644
index 0000000..58d18e8
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/AbstractFilter.java
@@ -0,0 +1,189 @@
+/*
+ * $Id: AbstractFilter.java 657 2010-05-29 02:06:24Z kirillcool $
+ *
+ * Dual-licensed under LGPL (Sun and Romain Guy) and BSD (Romain Guy).
+ *
+ * Copyright 2005 Sun Microsystems, Inc., 4150 Network Circle,
+ * Santa Clara, California 95054, U.S.A. All rights reserved.
+ *
+ * Copyright (c) 2006 Romain Guy <romain.guy at mac.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.flamingo.internal.utils;
+
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.*;
+
+/**
+ * <p>
+ * Provides an abstract implementation of the <code>BufferedImageOp</code>
+ * interface. This class can be used to created new image filters based on
+ * <code>BufferedImageOp</code>.
+ * </p>
+ *
+ * @author Romain Guy <romain.guy at mac.com>
+ */
+public abstract class AbstractFilter implements BufferedImageOp {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.image.BufferedImageOp#getBounds2D(java.awt.image.BufferedImage)
+ */
+ @Override
+ public Rectangle2D getBounds2D(BufferedImage src) {
+ return new Rectangle(0, 0, src.getWidth(), src.getHeight());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image
+ * .BufferedImage, java.awt.image.ColorModel)
+ */
+ @Override
+ public BufferedImage createCompatibleDestImage(BufferedImage src,
+ ColorModel destCM) {
+ if (destCM == null) {
+ destCM = src.getColorModel();
+ }
+
+ return new BufferedImage(destCM, destCM.createCompatibleWritableRaster(
+ src.getWidth(), src.getHeight()),
+ destCM.isAlphaPremultiplied(), null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.image.BufferedImageOp#getPoint2D(java.awt.geom.Point2D,
+ * java.awt.geom.Point2D)
+ */
+ @Override
+ public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
+ return (Point2D) srcPt.clone();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.image.BufferedImageOp#getRenderingHints()
+ */
+ @Override
+ public RenderingHints getRenderingHints() {
+ return null;
+ }
+
+ /**
+ * Returns an array of integer pixels in the default RGB color model
+ * (TYPE_INT_ARGB) and default sRGB color space, from a portion of the image
+ * data.
+ *
+ * @param img
+ * Image.
+ * @param x
+ * The starting X coordinate
+ * @param y
+ * The starting Y coordinate
+ * @param w
+ * Width of region.
+ * @param h
+ * Height of region.
+ * @param pixels
+ * If not <code>null</code>, the pixels are written here.
+ * @return Array or RGB pixels.
+ */
+ protected int[] getPixels(BufferedImage img, int x, int y, int w, int h,
+ int[] pixels) {
+ if (w == 0 || h == 0) {
+ return new int[0];
+ }
+
+ if (pixels == null) {
+ pixels = new int[w * h];
+ } else if (pixels.length < w * h) {
+ throw new IllegalArgumentException(
+ "pixels array must have a length" + " >= w*h");
+ }
+
+ int imageType = img.getType();
+ if (imageType == BufferedImage.TYPE_INT_ARGB
+ || imageType == BufferedImage.TYPE_INT_RGB) {
+ Raster raster = img.getRaster();
+ return (int[]) raster.getDataElements(x, y, w, h, pixels);
+ }
+
+ // Unmanages the image
+ return img.getRGB(x, y, w, h, pixels, 0, w);
+ }
+
+ /**
+ * <p>
+ * Writes a rectangular area of pixels in the destination
+ * <code>BufferedImage</code>. Calling this method on an image of type
+ * different from <code>BufferedImage.TYPE_INT_ARGB</code> and
+ * <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.
+ * </p>
+ *
+ * @param img
+ * the destination image
+ * @param x
+ * the x location at which to start storing pixels
+ * @param y
+ * the y location at which to start storing pixels
+ * @param w
+ * the width of the rectangle of pixels to store
+ * @param h
+ * the height of the rectangle of pixels to store
+ * @param pixels
+ * an array of pixels, stored as integers
+ * @throws IllegalArgumentException
+ * is <code>pixels</code> is non-null and of length < w*h
+ */
+ protected void setPixels(BufferedImage img, int x, int y, int w, int h,
+ int[] pixels) {
+ if (pixels == null || w == 0 || h == 0) {
+ return;
+ } else if (pixels.length < w * h) {
+ throw new IllegalArgumentException(
+ "pixels array must have a length" + " >= w*h");
+ }
+
+ int imageType = img.getType();
+ if (imageType == BufferedImage.TYPE_INT_ARGB
+ || imageType == BufferedImage.TYPE_INT_RGB) {
+ WritableRaster raster = img.getRaster();
+ raster.setDataElements(x, y, w, h, pixels);
+ } else {
+ // Unmanages the image
+ img.setRGB(x, y, w, h, pixels, 0, w);
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/ArrowResizableIcon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/ArrowResizableIcon.java
new file mode 100644
index 0000000..b98cb24
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/ArrowResizableIcon.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.utils;
+
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+
+/**
+ * Helper implementation of {@link ResizableIcon} that draws an arrow.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ArrowResizableIcon implements ResizableIcon {
+ /**
+ * Initial dimension.
+ */
+ private Dimension initialDim;
+
+ /**
+ * The current icon width.
+ */
+ protected int width;
+
+ /**
+ * The current icon height.
+ */
+ protected int height;
+
+ /**
+ * Arrow direction. One of {@link SwingConstants#SOUTH},
+ * {@link SwingConstants#NORTH}, {@link SwingConstants#EAST} or
+ * {@link SwingConstants#WEST}.
+ */
+ protected int direction;
+
+ /**
+ * Creates a new arrow resizable icon.
+ *
+ * @param initialDim
+ * Initial icon dimension.
+ * @param direction
+ * Arrow direction. Must be one of {@link SwingConstants#SOUTH},
+ * {@link SwingConstants#NORTH}, {@link SwingConstants#EAST} or
+ * {@link SwingConstants#WEST}.
+ */
+ public ArrowResizableIcon(Dimension initialDim, int direction) {
+ this.initialDim = initialDim;
+ this.width = initialDim.width;
+ this.height = initialDim.height;
+ this.direction = direction;
+ }
+
+ /**
+ * Creates a new arrow resizable icon.
+ *
+ * @param initialDim
+ * Initial icon dimension.
+ * @param direction
+ * Arrow direction. Must be one of {@link SwingConstants#SOUTH},
+ * {@link SwingConstants#NORTH}, {@link SwingConstants#EAST} or
+ * {@link SwingConstants#WEST}.
+ */
+ public ArrowResizableIcon(int initialDim, int direction) {
+ this(new Dimension(initialDim, initialDim), direction);
+ }
+
+ public void revertToOriginalDimension() {
+ this.width = initialDim.width;
+ this.height = initialDim.height;
+ }
+
+ @Override
+ public void setDimension(Dimension newDimension) {
+ this.width = newDimension.width;
+ this.height = newDimension.height;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return this.height;
+ }
+
+ @Override
+ public int getIconWidth() {
+ return this.width;
+ }
+
+ protected boolean toPaintEnabled(Component c) {
+ return c.isEnabled();
+ }
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ float strokeWidth = this.width / 7.0f;
+ if (strokeWidth < 1.0f)
+ strokeWidth = 1.0f;
+ Stroke stroke = new BasicStroke(strokeWidth, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER);
+
+ graphics.setStroke(stroke);
+
+ GeneralPath gp = new GeneralPath();
+ switch (direction) {
+ case SwingUtilities.SOUTH:
+ gp.moveTo(0, 2);
+ gp.lineTo((float) 0.5 * (width - 1), height - 2);
+ gp.lineTo(width - 1, 2);
+ break;
+ case SwingUtilities.NORTH:
+ gp.moveTo(0, height - 2);
+ gp.lineTo((float) 0.5 * (width - 1), 2);
+ gp.lineTo(width - 1, height - 2);
+ break;
+ case SwingUtilities.EAST:
+ gp.moveTo(2, 0);
+ gp.lineTo(width - 2, (float) 0.5 * (height - 1));
+ gp.lineTo(2, height - 1);
+ break;
+ case SwingUtilities.WEST:
+ gp.moveTo(width - 2, 0);
+ gp.lineTo(2, (float) 0.5 * (height - 1));
+ gp.lineTo(width - 2, height - 1);
+ break;
+ }
+
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics.translate(x, y + 1);
+ Color dropColor = this.toPaintEnabled(c) ? new Color(255, 255, 255, 196)
+ : new Color(255, 255, 255, 32);
+ graphics.setColor(dropColor);
+ graphics.draw(gp);
+
+ graphics.translate(0, -1);
+ Color arrowColor = this.toPaintEnabled(c) ? Color.black : Color.gray;
+ graphics.setColor(arrowColor);
+ if (this.width < 9) {
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+ graphics.draw(gp);
+ }
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics.draw(gp);
+ graphics.dispose();
+ }
+
+ public static class CommandButtonPopupIcon extends ArrowResizableIcon {
+
+ public CommandButtonPopupIcon(int initialDim, int direction) {
+ super(initialDim, direction);
+ }
+
+ @Override
+ protected boolean toPaintEnabled(Component c) {
+ JCommandButton jcb = (JCommandButton) c;
+ return jcb.isEnabled() && jcb.getPopupModel().isEnabled();
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/ButtonSizingUtils.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/ButtonSizingUtils.java
new file mode 100644
index 0000000..ff8a9fe
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/ButtonSizingUtils.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.utils;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+
+public class ButtonSizingUtils {
+ private static ButtonSizingUtils instance;
+
+ private Insets outsets;
+
+ private Insets toggleOutsets;
+
+ public static synchronized ButtonSizingUtils getInstance() {
+ if (instance == null)
+ instance = new ButtonSizingUtils();
+ return instance;
+ }
+
+ private ButtonSizingUtils() {
+ this.outsets = this.syncOutsets(new JButton(""));
+ this.toggleOutsets = this.syncOutsets(new JToggleButton(""));
+ UIManager.addPropertyChangeListener(new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("lookAndFeel".equals(evt.getPropertyName())) {
+ outsets = syncOutsets(new JButton(""));
+ toggleOutsets = syncOutsets(new JToggleButton(""));
+ }
+ }
+ });
+ }
+
+ private Insets syncOutsets(AbstractButton renderer) {
+ JPanel panel = new JPanel(null);
+ renderer.putClientProperty("JButton.buttonStyle", "square");
+ renderer.setFocusable(false);
+ renderer.setOpaque(false);
+ panel.add(renderer);
+ renderer.setBounds(0, 0, 100, 50);
+
+ GraphicsEnvironment e = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice d = e.getDefaultScreenDevice();
+ GraphicsConfiguration c = d.getDefaultConfiguration();
+ BufferedImage compatibleImage = c.createCompatibleImage(100, 50,
+ Transparency.TRANSLUCENT);
+ renderer.paint(compatibleImage.getGraphics());
+
+ // analyze top
+ int top = 0;
+ for (int i = 0; i < 25; i++) {
+ int rgba = compatibleImage.getRGB(50, i);
+ int alpha = (rgba >>> 24) & 0xFF;
+ if (alpha == 255) {
+ top = i;
+ break;
+ }
+ }
+ // analyze bottom
+ int bottom = 0;
+ for (int i = 49; i > 25; i--) {
+ int rgba = compatibleImage.getRGB(50, i);
+ int alpha = (rgba >>> 24) & 0xFF;
+ if (alpha == 255) {
+ bottom = 49 - i;
+ break;
+ }
+ }
+ // analyze left
+ int left = 0;
+ for (int i = 0; i < 50; i++) {
+ int rgba = compatibleImage.getRGB(i, 25);
+ int alpha = (rgba >>> 24) & 0xFF;
+ if (alpha == 255) {
+ left = i;
+ break;
+ }
+ }
+ // analyze right
+ int right = 0;
+ for (int i = 99; i > 50; i--) {
+ int rgba = compatibleImage.getRGB(i, 25);
+ int alpha = (rgba >>> 24) & 0xFF;
+ if (alpha == 255) {
+ right = 99 - i;
+ break;
+ }
+ }
+
+ return new Insets(top, left, bottom, right);
+ }
+
+ public Insets getOutsets() {
+ return new Insets(outsets.top, outsets.left, outsets.bottom,
+ outsets.right);
+ }
+
+ public Insets getToggleOutsets() {
+ return new Insets(toggleOutsets.top, toggleOutsets.left,
+ toggleOutsets.bottom, toggleOutsets.right);
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/ColorShiftFilter.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/ColorShiftFilter.java
new file mode 100644
index 0000000..c184071
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/ColorShiftFilter.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.utils;
+
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+
+/**
+ * Image filter that shifts the colors of the original image.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ColorShiftFilter extends AbstractFilter {
+ /**
+ * Red component of the shift color.
+ */
+ int rShift;
+
+ /**
+ * Green component of the shift color.
+ */
+ int gShift;
+
+ /**
+ * Blue component of the shift color.
+ */
+ int bShift;
+
+ /**
+ * Shift amount in 0.0-1.0 range.
+ */
+ double hueShiftAmount;
+
+ /**
+ * Creates a new color shift filter.
+ *
+ * @param shiftColor
+ * Shift color.
+ * @param shiftAmount
+ * Shift amount in 0.0-1.0 range.
+ */
+ public ColorShiftFilter(Color shiftColor, double shiftAmount) {
+ this.rShift = shiftColor.getRed();
+ this.gShift = shiftColor.getGreen();
+ this.bShift = shiftColor.getBlue();
+ this.hueShiftAmount = shiftAmount;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.image.BufferedImageOp#filter(java.awt.image.BufferedImage,
+ * java.awt.image.BufferedImage)
+ */
+ @Override
+ public BufferedImage filter(BufferedImage src, BufferedImage dst) {
+ if (dst == null) {
+ dst = createCompatibleDestImage(src, null);
+ }
+
+ int width = src.getWidth();
+ int height = src.getHeight();
+
+ int[] pixels = new int[width * height];
+ getPixels(src, 0, 0, width, height, pixels);
+ shiftColor(pixels);
+ setPixels(dst, 0, 0, width, height, pixels);
+
+ return dst;
+ }
+
+ /**
+ * Color-shifts all the pixels in the specified pixel array.
+ *
+ * @param pixels
+ * Pixel array for color-shifting.
+ */
+ private void shiftColor(int[] pixels) {
+ for (int i = 0; i < pixels.length; i++) {
+ int argb = pixels[i];
+ int r = (argb >>> 16) & 0xFF;
+ int g = (argb >>> 8) & 0xFF;
+ int b = (argb >>> 0) & 0xFF;
+
+ int nr = (int) (this.hueShiftAmount * this.rShift + (1.0 - this.hueShiftAmount)
+ * r);
+ int ng = (int) (this.hueShiftAmount * this.gShift + (1.0 - this.hueShiftAmount)
+ * g);
+ int nb = (int) (this.hueShiftAmount * this.bShift + (1.0 - this.hueShiftAmount)
+ * b);
+
+ pixels[i] = (argb & 0xFF000000) | nr << 16 | ng << 8 | nb;
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/DoubleArrowResizableIcon.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/DoubleArrowResizableIcon.java
new file mode 100644
index 0000000..94abbe1
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/DoubleArrowResizableIcon.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.utils;
+
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+
+/**
+ * Helper implementation of {@link ResizableIcon} that draws a double arrow.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DoubleArrowResizableIcon implements ResizableIcon {
+ /**
+ * Initial dimension.
+ */
+ private Dimension initialDim;
+
+ /**
+ * The width of the rendered image.
+ */
+ protected int width;
+
+ /**
+ * The height of the rendered image.
+ */
+ protected int height;
+
+ /**
+ * Arrow direction. One of {@link SwingConstants#SOUTH},
+ * {@link SwingConstants#NORTH}, {@link SwingConstants#EAST} or
+ * {@link SwingConstants#WEST}.
+ */
+ protected int direction;
+
+ /**
+ * Creates a new double arrow resizable icon.
+ *
+ * @param initialDim
+ * Initial icon dimension.
+ * @param direction
+ * Arrow direction. Currently only {@link SwingConstants#SOUTH}
+ * is supported.
+ */
+ public DoubleArrowResizableIcon(Dimension initialDim, int direction) {
+ this.initialDim = initialDim;
+ this.width = initialDim.width;
+ this.height = initialDim.height;
+ this.direction = direction;
+ }
+
+ /**
+ * Creates a new double arrow resizable icon.
+ *
+ * @param initialDim
+ * Initial icon dimension.
+ * @param direction
+ * Arrow direction. Currently only {@link SwingConstants#SOUTH}
+ * is supported.
+ */
+ public DoubleArrowResizableIcon(int initialDim, int direction) {
+ this(new Dimension(initialDim, initialDim), direction);
+ }
+
+ public void revertToOriginalDimension() {
+ this.width = initialDim.width;
+ this.height = initialDim.height;
+ }
+
+ @Override
+ public void setDimension(Dimension newDimension) {
+ this.width = newDimension.width;
+ this.height = newDimension.height;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return this.height;
+ }
+
+ @Override
+ public int getIconWidth() {
+ return this.width;
+ }
+
+ protected boolean toPaintEnabled(Component c) {
+ return c.isEnabled();
+ }
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ Color arrowColor = this.toPaintEnabled(c) ? Color.black : Color.gray;
+ graphics.setColor(arrowColor);
+ Stroke stroke = new BasicStroke(this.width / 8.0f,
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+
+ graphics.setStroke(stroke);
+ graphics.translate(x, y);
+ GeneralPath gp = new GeneralPath();
+ int arrowHeight = height / 2;
+ int arrowWidth = width / 2;
+ int deltaH = (height + 2) / 3;
+ int deltaW = (width + 2) / 3;
+ switch (direction) {
+ case SwingUtilities.NORTH:
+ gp.moveTo(0, height - 1);
+ gp.lineTo((float) 0.5 * (width - 1), height - 1 - arrowHeight);
+ gp.lineTo(width - 1, height - 1);
+
+ gp.moveTo(0, height - 1 - deltaH);
+ gp.lineTo((float) 0.5 * (width - 1), height - 1 - arrowHeight
+ - deltaH);
+ gp.lineTo(width - 1, height - 1 - deltaH);
+
+ break;
+
+ case SwingUtilities.SOUTH:
+ gp.moveTo(0, 0);
+ gp.lineTo((float) 0.5 * (width - 1), arrowHeight);
+ gp.lineTo(width - 1, 0);
+
+ gp.moveTo(0, deltaH);
+ gp.lineTo((float) 0.5 * (width - 1), arrowHeight + deltaH);
+ gp.lineTo(width - 1, deltaH);
+
+ break;
+
+ case SwingUtilities.EAST:
+ gp.moveTo(0, 0);
+ gp.lineTo(arrowWidth, (float) 0.5 * (height - 1));
+ gp.lineTo(0, height - 1);
+
+ gp.moveTo(deltaW, 0);
+ gp.lineTo(arrowWidth + deltaW, (float) 0.5 * (height - 1));
+ gp.lineTo(deltaW, height - 1);
+
+ break;
+
+ case SwingUtilities.WEST:
+ gp.moveTo(width - 1, 0);
+ gp.lineTo(width - 1 - arrowWidth, (float) 0.5 * (height - 1));
+ gp.lineTo(width - 1, height - 1);
+
+ gp.moveTo(width - 1 - deltaW, 0);
+ gp.lineTo(width - 1 - arrowWidth - deltaW, (float) 0.5
+ * (height - 1));
+ gp.lineTo(width - 1 - deltaW, height - 1);
+
+ break;
+
+ }
+ graphics.draw(gp);
+ graphics.dispose();
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/FlamingoUtilities.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/FlamingoUtilities.java
new file mode 100644
index 0000000..f99e59c
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/FlamingoUtilities.java
@@ -0,0 +1,621 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.utils;
+
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+import java.awt.image.BufferedImage;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager;
+import org.pushingpixels.flamingo.api.ribbon.*;
+import org.pushingpixels.flamingo.api.ribbon.resize.IconRibbonBandResizePolicy;
+import org.pushingpixels.flamingo.api.ribbon.resize.RibbonBandResizePolicy;
+import org.pushingpixels.flamingo.internal.ui.ribbon.AbstractBandControlPanel;
+import org.pushingpixels.flamingo.internal.ui.ribbon.JRibbonTaskToggleButton;
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuButton;
+
+/**
+ * Helper utilities for Flamingo project. This class is for internal use only.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class FlamingoUtilities {
+ /**
+ * Gets the component font.
+ *
+ * @param comp
+ * Component.
+ * @param keys
+ * {@link UIManager} keys.
+ * @return If the component is not <code>null</code>, its font is returned.
+ * Otherwise the first entry in {@link UIManager} which is a
+ * {@link Font} is returned.
+ */
+ public static FontUIResource getFont(Component comp, String... keys) {
+ if (comp != null) {
+ Font compFont = comp.getFont();
+ if ((compFont != null) && !(compFont instanceof UIResource)) {
+ return new FontUIResource(compFont);
+ }
+ }
+ for (String key : keys) {
+ Font font = UIManager.getFont(key);
+ if (font != null) {
+ if (font instanceof UIResource)
+ return (FontUIResource) font;
+ else
+ return new FontUIResource(font);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the color based on the specified {@link UIManager} keys.
+ *
+ * @param defaultColor
+ * Default color to return if none of the {@link UIManager} keys
+ * are present.
+ * @param keys
+ * {@link UIManager} keys.
+ * @return The first entry in {@link UIManager} which is a color. If none,
+ * then the default color is returned.
+ */
+ public static Color getColor(Color defaultColor, String... keys) {
+ for (String key : keys) {
+ Color color = UIManager.getColor(key);
+ if (color != null)
+ return color;
+ }
+ return new ColorUIResource(defaultColor);
+ }
+
+ /**
+ * Returns a ribbon band expand icon.
+ *
+ * @return Ribbon band expand icon.
+ */
+ public static ResizableIcon getRibbonBandExpandIcon(
+ AbstractRibbonBand ribbonBand) {
+ boolean ltr = ribbonBand.getComponentOrientation().isLeftToRight();
+ return new ArrowResizableIcon(9, ltr ? SwingConstants.EAST
+ : SwingConstants.WEST);
+ }
+
+ /**
+ * Returns a popup action icon for the specific command button.
+ */
+ public static ResizableIcon getCommandButtonPopupActionIcon(
+ JCommandButton commandButton) {
+ JCommandButton.CommandButtonPopupOrientationKind popupOrientationKind = commandButton
+ .getPopupOrientationKind();
+ switch (popupOrientationKind) {
+ case DOWNWARD:
+ return new ArrowResizableIcon.CommandButtonPopupIcon(9,
+ SwingConstants.SOUTH);
+ case SIDEWARD:
+ return new ArrowResizableIcon.CommandButtonPopupIcon(
+ 9,
+ commandButton.getComponentOrientation().isLeftToRight() ? SwingConstants.EAST
+ : SwingConstants.WEST);
+ }
+ return null;
+ }
+
+ /**
+ * Creates a thumbnail of the specified width.
+ *
+ * @param image
+ * The original image.
+ * @param requestedThumbWidth
+ * The width of the resulting thumbnail.
+ * @return Thumbnail of the specified width.
+ * @author Romain Guy
+ */
+ public static BufferedImage createThumbnail(BufferedImage image,
+ int requestedThumbWidth) {
+ float ratio = (float) image.getWidth() / (float) image.getHeight();
+ int width = image.getWidth();
+ BufferedImage thumb = image;
+
+ do {
+ width /= 2;
+ if (width < requestedThumbWidth) {
+ width = requestedThumbWidth;
+ }
+
+ BufferedImage temp = new BufferedImage(width,
+ (int) (width / ratio), BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2 = temp.createGraphics();
+ g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
+ g2.dispose();
+
+ thumb = temp;
+ } while (width != requestedThumbWidth);
+
+ return thumb;
+ }
+
+ /**
+ * Returns the outline of the ribbon border.
+ *
+ * @param startX
+ * The starting X of the ribbon area.
+ * @param endX
+ * The ending X of the ribbon area.
+ * @param startSelectedX
+ * The starting X of the toggle tab button of the selected task.
+ * @param endSelectedX
+ * The ending X of the toggle tab button of the selected task.
+ * @param topY
+ * The top Y of the ribbon area.
+ * @param bandTopY
+ * The top Y of the ribbon band area.
+ * @param bottomY
+ * The bottom Y of the ribbon area.
+ * @param radius
+ * Corner radius.
+ * @return The outline of the ribbon border.
+ */
+ public static GeneralPath getRibbonBorderOutline(int startX, int endX,
+ int startSelectedX, int endSelectedX, int topY, int bandTopY,
+ int bottomY, float radius) {
+ int height = bottomY - topY;
+ GeneralPath result = new GeneralPath();
+ float radius3 = (float) (radius / (1.5 * Math.pow(height, 0.5)));
+
+ // start in the top left corner at the end of the curve
+ result.moveTo(startX + radius, bandTopY);
+
+ // move to the bottom start of the selected tab and curve up
+ result.lineTo(startSelectedX - radius, bandTopY);
+ // result.quadTo(startSelectedX - radius3, bandTopY - radius3,
+ // startSelectedX, bandTopY - radius);
+
+ // move to the top start of the selected tab and curve right
+ // result.lineTo(startSelectedX, topY + radius);
+ // result.quadTo(startSelectedX + radius3, topY + radius3,
+ // startSelectedX
+ // + radius, topY);
+
+ // move to the top end of the selected tab and curve down
+ // result.lineTo(endSelectedX - radius - 1, topY);
+ // result.quadTo(endSelectedX + radius3 - 1, topY + radius3,
+ // endSelectedX - 1, topY + radius);
+
+ // move to the bottom end of the selected tab and curve right
+ // result.lineTo(endSelectedX - 1, bandTopY - radius);
+ // result.quadTo(endSelectedX + radius3 - 1, bandTopY - radius3,
+ // endSelectedX + radius - 1, bandTopY);
+ result.moveTo(endSelectedX + radius - 1, bandTopY);
+
+ // move to the top right corner and curve down
+ result.lineTo(endX - radius - 1, bandTopY);
+ result.quadTo(endX - radius3 - 1, bandTopY + radius3, endX - 1,
+ bandTopY + radius);
+
+ // move to the bottom right corner and curve left
+ result.lineTo(endX - 1, bottomY - radius - 1);
+ result.quadTo(endX - radius3 - 1, bottomY - 1 - radius3, endX - radius
+ - 1, bottomY - 1);
+
+ // move to the bottom left corner and curve up
+ result.lineTo(startX + radius, bottomY - 1);
+ result.quadTo(startX + radius3, bottomY - 1 - radius3, startX, bottomY
+ - radius - 1);
+
+ // move to the top left corner and curve right
+ result.lineTo(startX, bandTopY + radius);
+ result.quadTo(startX + radius3, bandTopY + radius3, startX + radius,
+ bandTopY);
+
+ return result;
+ }
+
+ /**
+ * Returns the clip area of a task toggle button in ribbon component.
+ *
+ * @param width
+ * Toggle tab button width.
+ * @param height
+ * Toggle tab button height.
+ * @param radius
+ * Toggle tab button corner radius.
+ * @return Clip area of a toggle tab button in ribbon component.
+ */
+ public static GeneralPath getRibbonTaskToggleButtonOutline(int width,
+ int height, float radius) {
+ GeneralPath result = new GeneralPath();
+ float radius3 = (float) (radius / (1.5 * Math.pow(height, 0.5)));
+
+ // start at the bottom left
+ result.moveTo(0, height);
+
+ // move to the top start and curve right
+ result.lineTo(0, radius);
+ result.quadTo(radius3, radius3, radius, 0);
+
+ // move to the top end and curve down
+ result.lineTo(width - radius - 1, 0);
+ result.quadTo(width + radius3 - 1, radius3, width - 1, radius);
+
+ // move to the bottom right end
+ result.lineTo(width - 1, height);
+
+ // move to the bottom left end
+ result.lineTo(0, height);
+
+ return result;
+ }
+
+ /**
+ * Returns the outline of in-ribbon gallery.
+ *
+ * @param startX
+ * Start X of the in-ribbon gallery.
+ * @param endX
+ * End X of the in-ribbon gallery.
+ * @param topY
+ * Top Y of the in-ribbon gallery.
+ * @param bottomY
+ * Bottom Y of the in-ribbon gallery.
+ * @param radius
+ * Corner radius.
+ * @return The outline of in-ribbon gallery.
+ */
+ public static GeneralPath getRibbonGalleryOutline(int startX, int endX,
+ int topY, int bottomY, float radius) {
+
+ int height = bottomY - topY;
+ GeneralPath result = new GeneralPath();
+ float radius3 = (float) (radius / (1.5 * Math.pow(height, 0.5)));
+
+ // start in the top left corner at the end of the curve
+ result.moveTo(startX + radius, topY);
+
+ // move to the top right corner and curve down
+ result.lineTo(endX - radius - 1, topY);
+ result.quadTo(endX - radius3 - 1, topY + radius3, endX - 1, topY
+ + radius);
+
+ // move to the bottom right corner and curve left
+ result.lineTo(endX - 1, bottomY - radius - 1);
+ result.quadTo(endX - radius3 - 1, bottomY - 1 - radius3, endX - radius
+ - 1, bottomY - 1);
+
+ // move to the bottom left corner and curve up
+ result.lineTo(startX + radius, bottomY - 1);
+ result.quadTo(startX + radius3, bottomY - 1 - radius3, startX, bottomY
+ - radius - 1);
+
+ // move to the top left corner and curve right
+ result.lineTo(startX, topY + radius);
+ result.quadTo(startX + radius3, topY + radius3, startX + radius, topY);
+
+ return result;
+ }
+
+ /**
+ * Clips string based on specified font metrics and available width (in
+ * pixels). Returns the clipped string, which contains the beginning and the
+ * end of the input string separated by ellipses (...) in case the string is
+ * too long to fit into the specified width, and the origianl string
+ * otherwise.
+ *
+ * @param metrics
+ * Font metrics.
+ * @param availableWidth
+ * Available width in pixels.
+ * @param fullText
+ * String to clip.
+ * @return The clipped string, which contains the beginning and the end of
+ * the input string separated by ellipses (...) in case the string
+ * is too long to fit into the specified width, and the origianl
+ * string otherwise.
+ */
+ public static String clipString(FontMetrics metrics, int availableWidth,
+ String fullText) {
+
+ if (metrics.stringWidth(fullText) <= availableWidth)
+ return fullText;
+
+ String ellipses = "...";
+ int ellipsesWidth = metrics.stringWidth(ellipses);
+ if (ellipsesWidth > availableWidth)
+ return "";
+
+ String starter = "";
+
+ int w = fullText.length();
+ String prevText = "";
+ for (int i = 0; i < w; i++) {
+ String newStarter = starter + fullText.charAt(i);
+ String newText = newStarter + ellipses;
+ if (metrics.stringWidth(newText) <= availableWidth) {
+ starter = newStarter;
+ prevText = newText;
+ continue;
+ }
+ return prevText;
+ }
+ return fullText;
+ }
+
+ /**
+ * Retrieves transparent image of specified dimension.
+ *
+ * @param width
+ * Image width.
+ * @param height
+ * Image height.
+ * @return Transparent image of specified dimension.
+ */
+ public static BufferedImage getBlankImage(int width, int height) {
+ GraphicsEnvironment e = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice d = e.getDefaultScreenDevice();
+ GraphicsConfiguration c = d.getDefaultConfiguration();
+ BufferedImage compatibleImage = c.createCompatibleImage(width, height,
+ Transparency.TRANSLUCENT);
+ return compatibleImage;
+ }
+
+ /**
+ * Returns the alpha version of the specified color.
+ *
+ * @param color
+ * Original color.
+ * @param alpha
+ * Alpha channel value.
+ * @return Alpha version of the specified color.
+ */
+ public static Color getAlphaColor(Color color, int alpha) {
+ return new Color(color.getRed(), color.getGreen(), color.getBlue(),
+ alpha);
+ }
+
+ public static int getHLayoutGap(AbstractCommandButton commandButton) {
+ Font font = commandButton.getFont();
+ if (font == null)
+ font = UIManager.getFont("Button.font");
+ return (int) Math.ceil(commandButton.getHGapScaleFactor()
+ * (font.getSize() - 4) / 4);
+ }
+
+ public static int getVLayoutGap(AbstractCommandButton commandButton) {
+ Font font = commandButton.getFont();
+ if (font == null)
+ font = UIManager.getFont("Button.font");
+ return (int) Math.ceil(commandButton.getVGapScaleFactor()
+ * (font.getSize() - 4) / 4);
+ }
+
+ public static boolean hasPopupAction(AbstractCommandButton commandButton) {
+ if (commandButton instanceof JCommandButton) {
+ JCommandButton jcb = (JCommandButton) commandButton;
+ return jcb.getCommandButtonKind().hasPopup();
+ }
+ return false;
+ }
+
+ /**
+ * Handles updating the application menu button icon image. If
+ * <code>ribbonFrame</code> does not have an application menu button nothing
+ * is performed.
+ *
+ * @param ribbonFrame
+ * the ribbon frame containing the application icon
+ */
+ public static void updateRibbonFrameIconImages(JRibbonFrame ribbonFrame) {
+ JRibbonApplicationMenuButton appMenuButton = getApplicationMenuButton(ribbonFrame);
+ if (appMenuButton == null) {
+ return;
+ }
+
+ ResizableIcon appIcon = ribbonFrame.getApplicationIcon();
+ if (appIcon != null) {
+ appMenuButton.setIcon(appIcon);
+ }
+ }
+
+ /**
+ * Recursively searches the <code>comp</code> child components for a
+ * <code>JRibbonApplicationMenuButton</code> and returns it. If nothing is
+ * found <code>null</code> is returned.
+ *
+ * @param comp
+ * the component to recursively search
+ * @return the <code>JRibbonApplicationMenuButton</code>, or
+ * <code>null</code> if not found
+ */
+ public static JRibbonApplicationMenuButton getApplicationMenuButton(
+ Component comp) {
+ if (comp instanceof JRibbonApplicationMenuButton)
+ return (JRibbonApplicationMenuButton) comp;
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++) {
+ JRibbonApplicationMenuButton result = getApplicationMenuButton(cont
+ .getComponent(i));
+ if ((result != null) && result.isVisible())
+ return result;
+ }
+ }
+ return null;
+ }
+
+ public static void renderSurface(Graphics g, Container c, Rectangle rect,
+ boolean toSimulateRollover, boolean hasTopBorder,
+ boolean hasBottomBorder) {
+ CellRendererPane buttonRendererPane = new CellRendererPane();
+ JButton rendererButton = new JButton("");
+ rendererButton.getModel().setRollover(toSimulateRollover);
+
+ buttonRendererPane.setBounds(rect.x, rect.y, rect.width, rect.height);
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.clipRect(rect.x, rect.y, rect.width, rect.height);
+ buttonRendererPane.paintComponent(g2d, rendererButton, c, rect.x
+ - rect.width / 2, rect.y - rect.height / 2, 2 * rect.width,
+ 2 * rect.height, true);
+
+ g2d.setColor(FlamingoUtilities.getBorderColor());
+ if (hasTopBorder) {
+ g2d.drawLine(rect.x, rect.y, rect.x + rect.width - 1, rect.y);
+ }
+ if (hasBottomBorder) {
+ g2d.drawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width
+ - 1, rect.y + rect.height - 1);
+ }
+ g2d.dispose();
+ }
+
+ /**
+ * Returns lighter version of the specified color.
+ *
+ * @param color
+ * Color.
+ * @param diff
+ * Difference factor (values closer to 1.0 will produce results
+ * closer to white color).
+ * @return Lighter version of the specified color.
+ */
+ public static Color getLighterColor(Color color, double diff) {
+ int r = color.getRed() + (int) (diff * (255 - color.getRed()));
+ int g = color.getGreen() + (int) (diff * (255 - color.getGreen()));
+ int b = color.getBlue() + (int) (diff * (255 - color.getBlue()));
+ return new Color(r, g, b);
+ }
+
+ public static Color getBorderColor() {
+ return FlamingoUtilities.getColor(Color.gray,
+ "TextField.inactiveForeground", "Button.disabledText",
+ "ComboBox.disabledForeground");
+ }
+
+ public static boolean isShowingMinimizedRibbonInPopup(JRibbon ribbon) {
+ List<PopupPanelManager.PopupInfo> popups = PopupPanelManager
+ .defaultManager().getShownPath();
+ if (popups.size() == 0)
+ return false;
+
+ for (PopupPanelManager.PopupInfo popup : popups) {
+ JComponent originator = popup.getPopupOriginator();
+ if (originator instanceof JRibbonTaskToggleButton) {
+ return (ribbon == SwingUtilities.getAncestorOfClass(
+ JRibbon.class, originator));
+ }
+ }
+ return false;
+ }
+
+ public static boolean isShowingMinimizedRibbonInPopup(
+ JRibbonTaskToggleButton taskToggleButton) {
+ List<PopupPanelManager.PopupInfo> popups = PopupPanelManager
+ .defaultManager().getShownPath();
+ if (popups.size() == 0)
+ return false;
+
+ for (PopupPanelManager.PopupInfo popup : popups) {
+ JComponent originator = popup.getPopupOriginator();
+ if (originator == taskToggleButton)
+ return true;
+ }
+ return false;
+ }
+
+ public static void checkResizePoliciesConsistency(
+ AbstractRibbonBand ribbonBand) {
+ Insets ins = ribbonBand.getInsets();
+ AbstractBandControlPanel controlPanel = ribbonBand.getControlPanel();
+ if (controlPanel == null)
+ return;
+ int height = controlPanel.getPreferredSize().height
+ + ribbonBand.getUI().getBandTitleHeight() + ins.top
+ + ins.bottom;
+ List<RibbonBandResizePolicy> resizePolicies = ribbonBand
+ .getResizePolicies();
+ checkResizePoliciesConsistencyBase(ribbonBand);
+ for (int i = 0; i < (resizePolicies.size() - 1); i++) {
+ RibbonBandResizePolicy policy1 = resizePolicies.get(i);
+ RibbonBandResizePolicy policy2 = resizePolicies.get(i + 1);
+ int width1 = policy1.getPreferredWidth(height, 4);
+ int width2 = policy2.getPreferredWidth(height, 4);
+ if (width1 < width2) {
+ // create the trace message
+ StringBuilder builder = new StringBuilder();
+ builder.append("Inconsistent preferred widths\n");
+ builder.append("Ribbon band '");
+ builder.append(ribbonBand.getTitle());
+ builder.append("' has the following resize policies\n");
+ for (RibbonBandResizePolicy policy : resizePolicies) {
+ int width = policy.getPreferredWidth(height, 4);
+ builder.append("\t");
+ builder.append(policy.getClass().getName());
+ builder.append(" with preferred width ");
+ builder.append(width).append("\n");
+ }
+ builder.append(policy1.getClass().getName());
+ builder.append(" with pref width ");
+ builder.append(width1);
+ builder.append(" is followed by resize policy ");
+ builder.append(policy2.getClass().getName());
+ builder.append(" with larger pref width\n");
+
+ throw new IllegalStateException(builder.toString());
+ }
+ }
+ }
+
+ public static void checkResizePoliciesConsistencyBase(
+ AbstractRibbonBand ribbonBand) {
+ List<RibbonBandResizePolicy> resizePolicies = ribbonBand
+ .getResizePolicies();
+ if (resizePolicies.size() == 0) {
+ throw new IllegalStateException("Resize policy list is empty");
+ }
+ for (int i = 0; i < resizePolicies.size(); i++) {
+ RibbonBandResizePolicy policy = resizePolicies.get(i);
+ boolean isIcon = policy instanceof IconRibbonBandResizePolicy;
+ if (isIcon && (i < (resizePolicies.size() - 1))) {
+ throw new IllegalStateException(
+ "Icon resize policy must be the last in the list");
+ }
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/KeyTipManager.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/KeyTipManager.java
new file mode 100644
index 0000000..667b00c
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/KeyTipManager.java
@@ -0,0 +1,670 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.utils;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.lang.annotation.*;
+import java.util.*;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import javax.swing.*;
+import javax.swing.FocusManager;
+import javax.swing.event.EventListenerList;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager;
+import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager.PopupInfo;
+import org.pushingpixels.flamingo.api.ribbon.*;
+import org.pushingpixels.flamingo.internal.ui.ribbon.*;
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuButton;
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuPopupPanel;
+
+public class KeyTipManager {
+ boolean isShowingKeyTips;
+
+ List<KeyTipChain> keyTipChains;
+
+ protected EventListenerList listenerList;
+
+ protected BlockingQueue<Character> processingQueue;
+ protected ProcessingThread processingThread;
+
+ private JRibbonFrame rootOwner;
+
+ private Component focusOwner;
+
+ private static final KeyTipManager instance = new KeyTipManager();
+
+ public interface KeyTipLinkTraversal {
+ public KeyTipChain getNextChain();
+ }
+
+ public static interface KeyTipListener extends EventListener {
+ public void keyTipsShown(KeyTipEvent event);
+
+ public void keyTipsHidden(KeyTipEvent event);
+ }
+
+ public static class KeyTipEvent extends AWTEvent {
+ public KeyTipEvent(Object source, int id) {
+ super(source, id);
+ }
+ }
+
+ /**
+ * Annotation to mark a command button that shows UI content with associated
+ * keytips on clicking its action area. Can be used to associate keytips
+ * with menu command buttons in the popup menu shown when the ribbon gallery
+ * is expanded.
+ *
+ * @author Kirill Grouchnikov
+ */
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ public static @interface HasNextKeyTipChain {
+ }
+
+ public class KeyTipLink {
+ public String keyTipString;
+
+ public JComponent comp;
+
+ public Point prefAnchorPoint;
+
+ public ActionListener onActivated;
+
+ public KeyTipLinkTraversal traversal;
+
+ public boolean enabled;
+ }
+
+ public class KeyTipChain {
+ private List<KeyTipLink> links;
+
+ public int keyTipLookupIndex;
+
+ public JComponent chainParentComponent;
+
+ private KeyTipLinkTraversal parent;
+
+ public KeyTipChain(JComponent chainParentComponent) {
+ this.chainParentComponent = chainParentComponent;
+ this.links = new ArrayList<KeyTipLink>();
+ this.keyTipLookupIndex = 0;
+ }
+
+ public void addLink(KeyTipLink link) {
+ this.links.add(link);
+ }
+ }
+
+ public static KeyTipManager defaultManager() {
+ return instance;
+ }
+
+ private KeyTipManager() {
+ this.isShowingKeyTips = false;
+ this.keyTipChains = new ArrayList<KeyTipChain>();
+ this.listenerList = new EventListenerList();
+ this.processingQueue = new LinkedBlockingQueue<Character>();
+ this.processingThread = new ProcessingThread();
+ this.processingThread.start();
+ }
+
+ public boolean isShowingKeyTips() {
+ return !this.keyTipChains.isEmpty();
+ }
+
+ public void hideAllKeyTips() {
+ if (this.keyTipChains.isEmpty())
+ return;
+ this.keyTipChains.clear();
+ this.fireKeyTipsHidden(rootOwner);
+ repaintWindows();
+
+ // try restoring the focus owner if still relevant
+ this.tryRestoringFocusOwner();
+ }
+
+ private void tryRestoringFocusOwner() {
+ if (focusOwner != null) {
+ if (focusOwner.isDisplayable() && focusOwner.isShowing()) {
+ focusOwner.requestFocus();
+ }
+ }
+ }
+
+ public void showRootKeyTipChain(JRibbonFrame ribbonFrame) {
+ if (!this.keyTipChains.isEmpty()) {
+ throw new IllegalStateException(
+ "Can't call this method when key tip chains are present");
+ }
+
+ // store the current focus owner
+ focusOwner = FocusManager.getCurrentManager().getFocusOwner();
+ // and transfer the focus to the ribbon frame itself. If the focus
+ // is cleared, no key events will be dispatched to our window.
+ ribbonFrame.requestFocus();
+
+ rootOwner = ribbonFrame;
+ final JRibbon ribbon = ribbonFrame.getRibbon();
+ // root chain - application menu button,
+ // taskbar panel components and task toggle buttons
+ KeyTipChain root = new KeyTipChain(ribbon);
+
+ // application menu button
+ final JRibbonApplicationMenuButton appMenuButton = FlamingoUtilities
+ .getApplicationMenuButton(ribbonFrame);
+ if ((appMenuButton != null)
+ && (ribbon.getApplicationMenuKeyTip() != null)) {
+ final KeyTipLink appMenuButtonLink = new KeyTipLink();
+ appMenuButtonLink.comp = appMenuButton;
+ appMenuButtonLink.keyTipString = ribbon.getApplicationMenuKeyTip();
+ appMenuButtonLink.prefAnchorPoint = appMenuButton.getUI()
+ .getKeyTipAnchorCenterPoint();
+ appMenuButtonLink.onActivated = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ appMenuButton.doPopupClick();
+ }
+ };
+ appMenuButtonLink.enabled = true;
+ appMenuButtonLink.traversal = new KeyTipLinkTraversal() {
+ @Override
+ public KeyTipChain getNextChain() {
+ // System.out.println("Get next chain");
+ // collect key tips of all controls in the relevant popup
+ // panel
+ List<PopupInfo> popups = PopupPanelManager.defaultManager()
+ .getShownPath();
+ if (popups.size() > 0) {
+ PopupInfo last = popups.get(popups.size() - 1);
+ if (last.getPopupOriginator() == appMenuButton) {
+ JPopupPanel popupPanel = last.getPopupPanel();
+ KeyTipChain chain = new KeyTipChain(popupPanel);
+ chain.parent = appMenuButtonLink.traversal;
+ populateChain(last.getPopupPanel(), chain);
+ // popupPanel.putClientProperty(KEYTIP_MANAGER,
+ // KeyTipManager.this);
+ return chain;
+ }
+ }
+ return null;
+ }
+ };
+ root.addLink(appMenuButtonLink);
+ }
+
+ // taskbar panel components
+ for (Component taskbarComp : ribbon.getTaskbarComponents()) {
+ if (taskbarComp instanceof AbstractCommandButton) {
+ AbstractCommandButton cb = (AbstractCommandButton) taskbarComp;
+ KeyTipLink actionLink = getCommandButtonActionLink(cb);
+ if (actionLink != null) {
+ root.addLink(actionLink);
+ }
+ if (taskbarComp instanceof JCommandButton) {
+ JCommandButton jcb = (JCommandButton) taskbarComp;
+ KeyTipLink popupLink = getCommandButtonPopupLink(jcb);
+ if (popupLink != null) {
+ root.addLink(popupLink);
+ }
+ }
+ }
+ }
+
+ // task toggle buttons
+ RibbonUI ui = ribbon.getUI();
+ if (ui instanceof BasicRibbonUI) {
+ for (Map.Entry<RibbonTask, JRibbonTaskToggleButton> ttbEntry : ((BasicRibbonUI) ui)
+ .getTaskToggleButtons().entrySet()) {
+ final RibbonTask task = ttbEntry.getKey();
+ final JRibbonTaskToggleButton taskToggleButton = ttbEntry
+ .getValue();
+ String keyTip = task.getKeyTip();
+ if (keyTip != null) {
+ final KeyTipLink taskToggleButtonLink = new KeyTipLink();
+ taskToggleButtonLink.comp = taskToggleButton;
+ taskToggleButtonLink.keyTipString = keyTip;
+ taskToggleButtonLink.prefAnchorPoint = new Point(
+ taskToggleButton.getWidth() / 2, taskToggleButton
+ .getHeight());
+ taskToggleButtonLink.onActivated = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ taskToggleButton.doActionClick();
+ }
+ };
+ taskToggleButtonLink.enabled = true;
+ taskToggleButtonLink.traversal = new KeyTipLinkTraversal() {
+ @Override
+ public KeyTipChain getNextChain() {
+ KeyTipChain taskChain = new KeyTipChain(
+ taskToggleButton);
+ // collect key tips of all controls from all task
+ // bands
+ for (AbstractRibbonBand band : task.getBands())
+ populateChain(band, taskChain);
+ taskChain.parent = taskToggleButtonLink.traversal;
+ return taskChain;
+ }
+ };
+ root.addLink(taskToggleButtonLink);
+ }
+ }
+ }
+ this.keyTipChains.add(root);
+ this.fireKeyTipsShown(ribbonFrame);
+ ribbonFrame.repaint();
+ }
+
+ public Collection<KeyTipLink> getCurrentlyShownKeyTips() {
+ if (this.keyTipChains.isEmpty())
+ return Collections.emptyList();
+ return Collections.unmodifiableCollection(this.keyTipChains
+ .get(this.keyTipChains.size() - 1).links);
+ }
+
+ public KeyTipChain getCurrentlyShownKeyTipChain() {
+ if (this.keyTipChains.isEmpty())
+ return null;
+ return this.keyTipChains.get(this.keyTipChains.size() - 1);
+ }
+
+ public void showPreviousChain() {
+ if (this.keyTipChains.isEmpty())
+ return;
+ this.keyTipChains.remove(this.keyTipChains.size() - 1);
+ // was last?
+ if (!this.isShowingKeyTips()) {
+ // try restoring focus owner
+ this.tryRestoringFocusOwner();
+ }
+ repaintWindows();
+ }
+
+ private void addCommandButtonLinks(Component c, KeyTipChain chain) {
+ AbstractCommandButton cb = (AbstractCommandButton) c;
+ KeyTipLink actionLink = getCommandButtonActionLink(cb);
+ if (actionLink != null) {
+ chain.addLink(actionLink);
+ }
+ if (c instanceof JCommandButton) {
+ JCommandButton jcb = (JCommandButton) c;
+ KeyTipLink popupLink = getCommandButtonPopupLink(jcb);
+ if (popupLink != null) {
+ chain.addLink(popupLink);
+ }
+ }
+ }
+
+ private void populateChain(final Component c, final KeyTipChain chain) {
+ if (c instanceof AbstractCommandButton) {
+ Rectangle compBounds = c.getBounds();
+ if (c.isVisible() && c.isShowing()) {
+ if ((compBounds.height > 0) && (compBounds.width > 0))
+ addCommandButtonLinks(c, chain);
+ else
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ Rectangle compBounds = c.getBounds();
+ if ((compBounds.height > 0)
+ && (compBounds.width > 0))
+ addCommandButtonLinks(c, chain);
+ }
+ });
+ }
+ }
+
+ if (c instanceof JRibbonComponent) {
+ JRibbonComponent rc = (JRibbonComponent) c;
+ KeyTipLink link = getRibbonComponentLink(rc);
+ if (link != null) {
+ chain.addLink(link);
+ }
+ }
+
+ if (c instanceof Container) {
+ Container cont = (Container) c;
+ for (int i = 0; i < cont.getComponentCount(); i++) {
+ populateChain(cont.getComponent(i), chain);
+ }
+ }
+ }
+
+ private KeyTipLink getCommandButtonActionLink(final AbstractCommandButton cb) {
+ if (cb.getActionKeyTip() != null) {
+ final KeyTipLink link = new KeyTipLink();
+ link.comp = cb;
+ link.keyTipString = cb.getActionKeyTip();
+ link.prefAnchorPoint = cb.getUI().getKeyTipAnchorCenterPoint();
+ link.onActivated = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ cb.doActionClick();
+ }
+ };
+ link.enabled = cb.getActionModel().isEnabled();
+ if (cb.getClass().isAnnotationPresent(
+ KeyTipManager.HasNextKeyTipChain.class)) {
+ link.traversal = new KeyTipLinkTraversal() {
+ @Override
+ public KeyTipChain getNextChain() {
+ // collect key tips of all controls in the relevant
+ // popup panel
+ List<PopupInfo> popups = PopupPanelManager
+ .defaultManager().getShownPath();
+ if (popups.size() > 0) {
+ PopupInfo last = popups.get(popups.size() - 1);
+ JPopupPanel popupPanel = last.getPopupPanel();
+ KeyTipChain chain = new KeyTipChain(popupPanel);
+ populateChain(last.getPopupPanel(), chain);
+ chain.parent = link.traversal;
+ return chain;
+ }
+ return null;
+ }
+ };
+ } else {
+ link.traversal = null;
+ }
+ return link;
+ }
+ return null;
+ }
+
+ private KeyTipLink getRibbonComponentLink(final JRibbonComponent rc) {
+ if (rc.getKeyTip() != null) {
+ KeyTipLink link = new KeyTipLink();
+ link.comp = rc;
+ link.keyTipString = rc.getKeyTip();
+ link.prefAnchorPoint = rc.getUI().getKeyTipAnchorCenterPoint();
+ link.onActivated = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JComponent mainComponent = rc.getMainComponent();
+ if (mainComponent instanceof AbstractButton) {
+ ((AbstractButton) mainComponent).doClick();
+ } else {
+ if (mainComponent instanceof JComboBox) {
+ ((JComboBox) mainComponent).showPopup();
+ } else {
+ if (mainComponent instanceof JSpinner) {
+ JComponent editor = ((JSpinner) mainComponent)
+ .getEditor();
+ editor.requestFocusInWindow();
+ } else {
+ mainComponent.requestFocusInWindow();
+ }
+ }
+ }
+ }
+ };
+ link.enabled = rc.getMainComponent().isEnabled();
+ link.traversal = null;
+ return link;
+ }
+ return null;
+ }
+
+ private KeyTipLink getCommandButtonPopupLink(final JCommandButton cb) {
+ if (cb.getPopupKeyTip() != null) {
+ final KeyTipLink link = new KeyTipLink();
+ link.comp = cb;
+ link.keyTipString = cb.getPopupKeyTip();
+ link.prefAnchorPoint = cb.getUI().getKeyTipAnchorCenterPoint();
+ link.onActivated = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (cb instanceof JCommandMenuButton) {
+ ((JCommandMenuButton) cb).doActionRollover();
+ }
+ cb.doPopupClick();
+ }
+ };
+ link.enabled = cb.getPopupModel().isEnabled();
+ link.traversal = new KeyTipLinkTraversal() {
+ @Override
+ public KeyTipChain getNextChain() {
+ // System.out.println("Get next chain");
+ // collect key tips of all controls in the relevant popup
+ // panel
+ List<PopupInfo> popups = PopupPanelManager.defaultManager()
+ .getShownPath();
+ if (popups.size() > 0) {
+ PopupInfo last = popups.get(popups.size() - 1);
+ // if (last.getPopupOriginator() == cb) {
+ JPopupPanel popupPanel = last.getPopupPanel();
+ // special case - application menu
+ if (popupPanel instanceof JRibbonApplicationMenuPopupPanel) {
+ JRibbonApplicationMenuPopupPanel appMenuPopupPanel = (JRibbonApplicationMenuPopupPanel) popupPanel;
+ // check whether there are entries at level 2
+ JPanel level1 = appMenuPopupPanel.getPanelLevel1();
+ JPanel level2 = appMenuPopupPanel.getPanelLevel2();
+ if (level2.getComponentCount() > 0) {
+ KeyTipChain chain = new KeyTipChain(level2);
+ populateChain(level2, chain);
+ chain.parent = link.traversal;
+ return chain;
+ } else {
+ KeyTipChain chain = new KeyTipChain(level1);
+ populateChain(level1, chain);
+ chain.parent = link.traversal;
+ return chain;
+ }
+ } else {
+ KeyTipChain chain = new KeyTipChain(popupPanel);
+ populateChain(last.getPopupPanel(), chain);
+ chain.parent = link.traversal;
+ return chain;
+ }
+ // popupPanel.putClientProperty(KEYTIP_MANAGER,
+ // KeyTipManager.this);
+ // }
+ }
+ return null;
+ }
+ };
+ return link;
+ }
+ return null;
+ }
+
+ public void handleKeyPress(char keyChar) {
+ this.processingQueue.add(keyChar);
+ }
+
+ private class ProcessingThread extends Thread {
+ public ProcessingThread() {
+ super();
+ this.setName("KeyTipManager processing thread");
+ this.setDaemon(true);
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ final char keyChar = processingQueue.take();
+ SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ processNextKeyPress(keyChar);
+ }
+ });
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private void processNextKeyPress(char keyChar) {
+ if (this.keyTipChains.isEmpty())
+ return;
+
+ KeyTipChain currChain = this.keyTipChains
+ .get(this.keyTipChains.size() - 1);
+ // go over the key tip links and see if there is an exact match
+ for (final KeyTipLink link : currChain.links) {
+ String keyTipString = link.keyTipString;
+ if ((Character.toLowerCase(keyTipString
+ .charAt(currChain.keyTipLookupIndex)) == Character
+ .toLowerCase(keyChar))
+ && (keyTipString.length() == (currChain.keyTipLookupIndex + 1))) {
+ // exact match
+ if (link.enabled) {
+ link.onActivated.actionPerformed(new ActionEvent(link.comp,
+ ActionEvent.ACTION_PERFORMED, "keyTipActivated"));
+ if (link.traversal != null) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ final KeyTipChain next = link.traversal
+ .getNextChain();
+ if (next != null) {
+ KeyTipChain prev = (keyTipChains.isEmpty() ? null
+ : keyTipChains.get(keyTipChains
+ .size() - 1));
+ keyTipChains.add(next);
+ repaintWindows();
+ if (prev != null) {
+ // force repaint of all menu buttons
+ for (KeyTipLink link : prev.links) {
+ if (link.comp instanceof JCommandMenuButton)
+ link.comp.repaint();
+ }
+ }
+ }
+ }
+ });
+ } else {
+ // match found and activated, and no further
+ // traversal - dismiss all key tip chains
+ hideAllKeyTips();
+ }
+ }
+ return;
+ }
+ }
+
+ // go over the key tip links and look for key tips that have
+ // the specified character as the prefix
+ if (currChain.keyTipLookupIndex == 0) {
+ KeyTipChain secondary = new KeyTipChain(
+ currChain.chainParentComponent);
+ secondary.keyTipLookupIndex = 1;
+ for (KeyTipLink link : currChain.links) {
+ String keyTipString = link.keyTipString;
+ if ((Character.toLowerCase(keyTipString
+ .charAt(currChain.keyTipLookupIndex)) == Character
+ .toLowerCase(keyChar))
+ && (keyTipString.length() == 2)) {
+ KeyTipLink secondaryLink = new KeyTipLink();
+ secondaryLink.comp = link.comp;
+ secondaryLink.enabled = link.enabled;
+ secondaryLink.keyTipString = link.keyTipString;
+ secondaryLink.onActivated = link.onActivated;
+ secondaryLink.prefAnchorPoint = link.prefAnchorPoint;
+ secondaryLink.traversal = link.traversal;
+ secondary.addLink(secondaryLink);
+ }
+ }
+ if (secondary.links.size() > 0) {
+ this.keyTipChains.add(secondary);
+ }
+ repaintWindows();
+ return;
+ }
+ }
+
+ private void repaintWindows() {
+ for (Window window : Window.getWindows()) {
+ window.repaint();
+ }
+ List<PopupInfo> popups = PopupPanelManager.defaultManager()
+ .getShownPath();
+ for (PopupPanelManager.PopupInfo popup : popups) {
+ JPopupPanel popupPanel = popup.getPopupPanel();
+ popupPanel.paintImmediately(new Rectangle(0, 0, popupPanel
+ .getWidth(), popupPanel.getHeight()));
+ }
+ }
+
+ public void addKeyTipListener(KeyTipListener keyTipListener) {
+ this.listenerList.add(KeyTipListener.class, keyTipListener);
+ }
+
+ public void removeKeyTipListener(KeyTipListener keyTipListener) {
+ this.listenerList.remove(KeyTipListener.class, keyTipListener);
+ }
+
+ protected void fireKeyTipsShown(JRibbonFrame ribbonFrame) {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ KeyTipEvent e = new KeyTipEvent(ribbonFrame, 0);
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == KeyTipListener.class) {
+ ((KeyTipListener) listeners[i + 1]).keyTipsShown(e);
+ }
+ }
+ }
+
+ protected void fireKeyTipsHidden(JRibbonFrame ribbonFrame) {
+ // Guaranteed to return a non-null array
+ Object[] listeners = listenerList.getListenerList();
+ KeyTipEvent e = new KeyTipEvent(ribbonFrame, 0);
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == KeyTipListener.class) {
+ ((KeyTipListener) listeners[i + 1]).keyTipsHidden(e);
+ }
+ }
+ }
+
+ public void refreshCurrentChain() {
+ KeyTipChain curr = this.keyTipChains.get(this.keyTipChains.size() - 1);
+ if (curr.parent == null)
+ return;
+ KeyTipChain refreshed = curr.parent.getNextChain();
+ this.keyTipChains.remove(this.keyTipChains.size() - 1);
+ this.keyTipChains.add(refreshed);
+ repaintWindows();
+ }
+}
\ No newline at end of file
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/KeyTipRenderingUtilities.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/KeyTipRenderingUtilities.java
new file mode 100644
index 0000000..d0de966
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/KeyTipRenderingUtilities.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.utils;
+
+import java.awt.*;
+import java.awt.font.LineMetrics;
+import java.awt.geom.RoundRectangle2D;
+import java.util.Collection;
+
+import javax.swing.*;
+
+import org.pushingpixels.flamingo.api.common.CommandButtonLayoutManager;
+import org.pushingpixels.flamingo.api.common.JCommandMenuButton;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonPopupOrientationKind;
+
+public class KeyTipRenderingUtilities {
+ private static int INSETS = 3;
+
+ public static Dimension getPrefSize(FontMetrics fm, String keyTip) {
+ int prefWidth = fm.stringWidth(keyTip) + 2 * INSETS + 1;
+ int prefHeight = fm.getHeight() + INSETS - 1;
+ return new Dimension(prefWidth, prefHeight);
+ }
+
+ public static void renderKeyTip(Graphics g, Container c, Rectangle rect,
+ String keyTip, boolean toPaintEnabled) {
+ CellRendererPane buttonRendererPane = new CellRendererPane();
+ JButton rendererButton = new JButton("");
+ rendererButton.setEnabled(toPaintEnabled);
+
+ buttonRendererPane.setBounds(rect.x, rect.y, rect.width, rect.height);
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ g2d.setComposite(AlphaComposite.SrcOver.derive(toPaintEnabled ? 1.0f
+ : 0.5f));
+
+ Shape clip = g2d.getClip();
+ RoundRectangle2D.Double roundRect = new RoundRectangle2D.Double(rect.x,
+ rect.y, rect.width - 1, rect.height - 1, 6, 6);
+ g2d.clip(roundRect);
+ buttonRendererPane.paintComponent(g2d, rendererButton, c, rect.x
+ - rect.width / 2, rect.y - rect.height / 2, 2 * rect.width,
+ 2 * rect.height, true);
+ g2d.setClip(clip);
+
+ g2d.setColor(FlamingoUtilities.getBorderColor());
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.draw(roundRect);
+
+ g2d.setColor(FlamingoUtilities.getColor(Color.black,
+ "Button.foreground"));
+ Font font = UIManager.getFont("Button.font");
+ font = font.deriveFont(font.getSize() + 1.0f);
+ g2d.setFont(font);
+ int strWidth = g2d.getFontMetrics().stringWidth(keyTip);
+ //int strHeight = g2d.getFontMetrics().getHeight();
+
+ g2d.translate(rect.x, rect.y);
+ LineMetrics lineMetrics = g2d.getFontMetrics().getLineMetrics(keyTip, g2d);
+ int strHeight = (int)lineMetrics.getHeight();
+ g2d.drawString(keyTip, (rect.width - strWidth + 1) / 2,
+ (rect.height + strHeight) / 2
+ - g2d.getFontMetrics().getDescent() + 1);
+
+ g2d.dispose();
+ }
+
+ public static void renderMenuButtonKeyTips(Graphics g,
+ JCommandMenuButton menuButton,
+ CommandButtonLayoutManager layoutManager) {
+ Collection<KeyTipManager.KeyTipLink> currLinks = KeyTipManager
+ .defaultManager().getCurrentlyShownKeyTips();
+ if (currLinks == null)
+ return;
+
+ boolean found = false;
+ for (KeyTipManager.KeyTipLink link : currLinks) {
+ found = (link.comp == menuButton);
+ if (found)
+ break;
+ }
+
+ if (!found)
+ return;
+
+ // System.out.println("Painting key tip for " + menuButton.getText());
+
+ String actionKeyTip = menuButton.getActionKeyTip();
+ String popupKeyTip = menuButton.getPopupKeyTip();
+ CommandButtonLayoutManager.CommandButtonLayoutInfo layoutInfo = layoutManager
+ .getLayoutInfo(menuButton, g);
+ Point prefCenter = menuButton.getUI().getKeyTipAnchorCenterPoint();
+ if ((layoutInfo.iconRect.width > 0) && (actionKeyTip != null)) {
+ Dimension pref = KeyTipRenderingUtilities.getPrefSize(g
+ .getFontMetrics(), actionKeyTip);
+ KeyTipRenderingUtilities.renderKeyTip(g, menuButton, new Rectangle(
+ prefCenter.x - pref.width / 2, Math.min(prefCenter.y
+ - pref.height / 2, layoutInfo.actionClickArea.y
+ + layoutInfo.actionClickArea.height - pref.height),
+ pref.width, pref.height), actionKeyTip, menuButton
+ .getActionModel().isEnabled());
+ }
+ if ((layoutInfo.popupClickArea.width > 0) && (popupKeyTip != null)) {
+ Dimension pref = KeyTipRenderingUtilities.getPrefSize(g
+ .getFontMetrics(), popupKeyTip);
+ if (menuButton.getPopupOrientationKind() == CommandButtonPopupOrientationKind.SIDEWARD) {
+ if (menuButton.getCommandButtonKind() != CommandButtonKind.POPUP_ONLY) {
+ // vertically aligned with the action keytip along
+ // the right edge
+ KeyTipRenderingUtilities.renderKeyTip(g, menuButton,
+ new Rectangle(layoutInfo.popupClickArea.x
+ + layoutInfo.popupClickArea.width
+ - pref.width - 4, Math.min(prefCenter.y
+ - pref.height / 2,
+ layoutInfo.actionClickArea.y
+ + layoutInfo.actionClickArea.height
+ - pref.height), pref.width,
+ pref.height), popupKeyTip, menuButton
+ .getPopupModel().isEnabled());
+ } else {
+ KeyTipRenderingUtilities
+ .renderKeyTip(
+ g,
+ menuButton,
+ new Rectangle(
+ prefCenter.x - pref.width / 2,
+ Math
+ .min(
+ prefCenter.y
+ - pref.height
+ / 2,
+ layoutInfo.popupClickArea.y
+ + layoutInfo.popupClickArea.height
+ - pref.height),
+ pref.width, pref.height),
+ popupKeyTip, menuButton.getPopupModel()
+ .isEnabled());
+ }
+ } else {
+ // horizontally centered along the bottom edge
+ KeyTipRenderingUtilities
+ .renderKeyTip(
+ g,
+ menuButton,
+ new Rectangle(
+ (layoutInfo.popupClickArea.x
+ + layoutInfo.popupClickArea.width - pref.width) / 2,
+ layoutInfo.popupClickArea.y
+ + layoutInfo.popupClickArea.height
+ - pref.height, pref.width,
+ pref.height), popupKeyTip, menuButton
+ .getPopupModel().isEnabled());
+ }
+ }
+ }
+}
diff --git a/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/RenderingUtils.java b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/RenderingUtils.java
new file mode 100644
index 0000000..6f18576
--- /dev/null
+++ b/flamingo/src/main/java/org/pushingpixels/flamingo/internal/utils/RenderingUtils.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2001-2006 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.flamingo.internal.utils;
+
+import java.awt.*;
+import java.awt.print.PrinterGraphics;
+import java.util.*;
+
+/**
+ * Utilities to install desktop rendering hints for correctly rasterizing texts.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RenderingUtils {
+ /**
+ * Desktop property name that points to a collection of rendering hints. See
+ * <a href="http://java.sun.com/javase/6/docs/api/java/awt/doc-files/DesktopProperties.html"
+ * >documentation</a> for more details.
+ */
+ private static final String PROP_DESKTOPHINTS = "awt.font.desktophints";
+
+ /**
+ * Installs desktop hints on the specified graphics context.
+ *
+ * @param g2
+ * Graphics context.
+ * @return Map of old rendering hints.
+ */
+ public static Map installDesktopHints(Graphics2D g2) {
+ Map oldRenderingHints = null;
+ Map desktopHints = desktopHints(g2);
+ if (desktopHints != null && !desktopHints.isEmpty()) {
+ oldRenderingHints = new HashMap(desktopHints.size());
+ RenderingHints.Key key;
+ for (Iterator i = desktopHints.keySet().iterator(); i.hasNext();) {
+ key = (RenderingHints.Key) i.next();
+ oldRenderingHints.put(key, g2.getRenderingHint(key));
+ }
+ g2.addRenderingHints(desktopHints);
+ }
+ return oldRenderingHints;
+ }
+
+ /**
+ * Returns the desktop hints for the specified graphics context.
+ *
+ * @param g2
+ * Graphics context.
+ * @return The desktop hints for the specified graphics context.
+ */
+ private static Map desktopHints(Graphics2D g2) {
+ if (isPrinting(g2)) {
+ return null;
+ }
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ GraphicsDevice device = g2.getDeviceConfiguration().getDevice();
+ Map desktopHints = (Map) toolkit.getDesktopProperty(PROP_DESKTOPHINTS
+ + '.' + device.getIDstring());
+ if (desktopHints == null) {
+ desktopHints = (Map) toolkit.getDesktopProperty(PROP_DESKTOPHINTS);
+ }
+ // It is possible to get a non-empty map but with disabled AA.
+ if (desktopHints != null) {
+ Object aaHint = desktopHints
+ .get(RenderingHints.KEY_TEXT_ANTIALIASING);
+ if ((aaHint == RenderingHints.VALUE_TEXT_ANTIALIAS_OFF)
+ || (aaHint == RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT)) {
+ desktopHints = null;
+ }
+ }
+ return desktopHints;
+ }
+
+ /**
+ * Checks whether the specified graphics context is a print context.
+ *
+ * @param g
+ * Graphics context.
+ * @return <code>true</code> if the specified graphics context is a print
+ * context.
+ */
+ private static boolean isPrinting(Graphics g) {
+ return g instanceof PrintGraphics || g instanceof PrinterGraphics;
+ }
+}
diff --git a/flamingo/src/main/resources/org/pushingpixels/flamingo/api/svg/SvgTranscoderTemplatePlain.templ b/flamingo/src/main/resources/org/pushingpixels/flamingo/api/svg/SvgTranscoderTemplatePlain.templ
new file mode 100644
index 0000000..ae5e77a
--- /dev/null
+++ b/flamingo/src/main/resources/org/pushingpixels/flamingo/api/svg/SvgTranscoderTemplatePlain.templ
@@ -0,0 +1,72 @@
+TOKEN_PACKAGE
+
+import java.awt.*;
+import java.awt.geom.*;
+
+/**
+ * This class has been automatically generated using <a
+ * href="https://flamingo.dev.java.net">Flamingo SVG transcoder</a>.
+ */
+public class TOKEN_CLASSNAME {
+ /**
+ * Paints the transcoded SVG image on the specified graphics context. You
+ * can install a custom transformation on the graphics context to scale the
+ * image.
+ *
+ * @param g
+ * Graphics context.
+ */
+ public static void paint(Graphics2D g) {
+ Shape shape = null;
+ Paint paint = null;
+ Stroke stroke = null;
+
+ float origAlpha = 1.0f;
+ Composite origComposite = ((Graphics2D)g).getComposite();
+ if (origComposite instanceof AlphaComposite) {
+ AlphaComposite origAlphaComposite =
+ (AlphaComposite)origComposite;
+ if (origAlphaComposite.getRule() == AlphaComposite.SRC_OVER) {
+ origAlpha = origAlphaComposite.getAlpha();
+ }
+ }
+
+ TOKEN_PAINTING_CODE
+ }
+
+ /**
+ * Returns the X of the bounding box of the original SVG image.
+ *
+ * @return The X of the bounding box of the original SVG image.
+ */
+ public static int getOrigX() {
+ return TOKEN_ORIG_X;
+ }
+
+ /**
+ * Returns the Y of the bounding box of the original SVG image.
+ *
+ * @return The Y of the bounding box of the original SVG image.
+ */
+ public static int getOrigY() {
+ return TOKEN_ORIG_Y;
+ }
+
+ /**
+ * Returns the width of the bounding box of the original SVG image.
+ *
+ * @return The width of the bounding box of the original SVG image.
+ */
+ public static int getOrigWidth() {
+ return TOKEN_ORIG_WIDTH;
+ }
+
+ /**
+ * Returns the height of the bounding box of the original SVG image.
+ *
+ * @return The height of the bounding box of the original SVG image.
+ */
+ public static int getOrigHeight() {
+ return TOKEN_ORIG_HEIGHT;
+ }
+}
diff --git a/flamingo/src/main/resources/org/pushingpixels/flamingo/api/svg/SvgTranscoderTemplateResizable.templ b/flamingo/src/main/resources/org/pushingpixels/flamingo/api/svg/SvgTranscoderTemplateResizable.templ
new file mode 100644
index 0000000..8c63a57
--- /dev/null
+++ b/flamingo/src/main/resources/org/pushingpixels/flamingo/api/svg/SvgTranscoderTemplateResizable.templ
@@ -0,0 +1,145 @@
+TOKEN_PACKAGE
+
+import java.awt.*;
+import java.awt.geom.*;
+
+/**
+ * This class has been automatically generated using <a
+ * href="https://flamingo.dev.java.net">Flamingo SVG transcoder</a>.
+ */
+public class TOKEN_CLASSNAME implements
+ org.pushingpixels.flamingo.api.common.icon.ResizableIcon {
+ /**
+ * Paints the transcoded SVG image on the specified graphics context. You
+ * can install a custom transformation on the graphics context to scale the
+ * image.
+ *
+ * @param g
+ * Graphics context.
+ */
+ public static void paint(Graphics2D g) {
+ Shape shape = null;
+ Paint paint = null;
+ Stroke stroke = null;
+
+ float origAlpha = 1.0f;
+ Composite origComposite = ((Graphics2D)g).getComposite();
+ if (origComposite instanceof AlphaComposite) {
+ AlphaComposite origAlphaComposite =
+ (AlphaComposite)origComposite;
+ if (origAlphaComposite.getRule() == AlphaComposite.SRC_OVER) {
+ origAlpha = origAlphaComposite.getAlpha();
+ }
+ }
+
+ TOKEN_PAINTING_CODE
+ }
+
+ /**
+ * Returns the X of the bounding box of the original SVG image.
+ *
+ * @return The X of the bounding box of the original SVG image.
+ */
+ public static int getOrigX() {
+ return TOKEN_ORIG_X;
+ }
+
+ /**
+ * Returns the Y of the bounding box of the original SVG image.
+ *
+ * @return The Y of the bounding box of the original SVG image.
+ */
+ public static int getOrigY() {
+ return TOKEN_ORIG_Y;
+ }
+
+ /**
+ * Returns the width of the bounding box of the original SVG image.
+ *
+ * @return The width of the bounding box of the original SVG image.
+ */
+ public static int getOrigWidth() {
+ return TOKEN_ORIG_WIDTH;
+ }
+
+ /**
+ * Returns the height of the bounding box of the original SVG image.
+ *
+ * @return The height of the bounding box of the original SVG image.
+ */
+ public static int getOrigHeight() {
+ return TOKEN_ORIG_HEIGHT;
+ }
+
+ /**
+ * The current width of this resizable icon.
+ */
+ int width;
+
+ /**
+ * The current height of this resizable icon.
+ */
+ int height;
+
+ /**
+ * Creates a new transcoded SVG image.
+ */
+ public TOKEN_CLASSNAME() {
+ this.width = getOrigWidth();
+ this.height = getOrigHeight();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return height;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return width;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.icon.ResizableIcon#setDimension(java.awt.Dimension
+ * )
+ */
+ @Override
+ public void setDimension(Dimension newDimension) {
+ this.width = newDimension.width;
+ this.height = newDimension.height;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.translate(x, y);
+
+ double coef1 = (double) this.width / (double) getOrigWidth();
+ double coef2 = (double) this.height / (double) getOrigHeight();
+ double coef = Math.min(coef1, coef2);
+ g2d.scale(coef, coef);
+ paint(g2d);
+ g2d.dispose();
+ }
+}
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cf12650
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/bin/bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/"
+APP_HOME="`pwd -P`"
+cd "$SAVED"
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+ at if "%DEBUG%" == "" @echo off
+ at rem ##########################################################################
+ at rem
+ at rem Gradle startup script for Windows
+ at rem
+ at rem ##########################################################################
+
+ at rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+ at rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+ at rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+ at rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+ at rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+ at rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+ at rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+ at rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+ at rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/laf-plugin/build.gradle b/laf-plugin/build.gradle
new file mode 100755
index 0000000..5eb8138
--- /dev/null
+++ b/laf-plugin/build.gradle
@@ -0,0 +1,43 @@
+jar {
+ manifest {
+ attributes(
+ "Laf-Plugin-Version": version,
+ "Laf-Plugin-VersionName": versionKey,
+ )
+ }
+}
+
+uploadPublished {
+ repositories {
+ mavenDeployer {
+ configurePOM(pom)
+ }
+ }
+}
+
+install {
+ configurePOM(repositories.mavenInstaller.pom)
+}
+
+private def configurePOM(def pom) {
+ configureBasePom(pom)
+ pom.project {
+ name "laf-plugin"
+ description "A fork of @kirilcool's laf-plugin project"
+ url "http://insubstantial.github.com/insubstantial/laf-plugin/"
+ licenses {
+ license {
+ name 'The BSD License'
+ url 'http://www.opensource.org/licenses/bsd-license.php'
+ distribution 'repo'
+ comments 'Covers LafPlugin'
+ }
+ license {
+ name 'The zlib/libpng License'
+ url 'http://www.opensource.org/licenses/zlib-license'
+ distribution 'repo'
+ comments 'Covers the XMLElement and XMLParseException classes'
+ }
+ }
+ }
+}
diff --git a/laf-plugin/settings.gradle b/laf-plugin/settings.gradle
new file mode 100755
index 0000000..c845799
--- /dev/null
+++ b/laf-plugin/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'laf-plugin'
diff --git a/laf-plugin/src/main/java/org/pushingpixels/lafplugin/ComponentPluginManager.java b/laf-plugin/src/main/java/org/pushingpixels/lafplugin/ComponentPluginManager.java
new file mode 100644
index 0000000..aaf018c
--- /dev/null
+++ b/laf-plugin/src/main/java/org/pushingpixels/lafplugin/ComponentPluginManager.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Plugin Kirill Grouchnikov and contributors. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafplugin;
+
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.swing.UIDefaults;
+
+/**
+ * Plugin manager for look-and-feels.
+ *
+ * @author Kirill Grouchnikov
+ * @author Erik Vickroy
+ * @author Robert Beeger
+ * @author Frederic Lavigne
+ * @author Pattrick Gotthardt
+ */
+public class ComponentPluginManager extends PluginManager {
+ /**
+ * Simple constructor.
+ *
+ * @param xmlName
+ * The name of XML file that contains plugin configuration.
+ */
+ public ComponentPluginManager(String xmlName) {
+ super(xmlName, LafComponentPlugin.TAG_MAIN,
+ LafComponentPlugin.COMPONENT_TAG_PLUGIN_CLASS);
+ }
+
+ /**
+ * Helper function to initialize all available component plugins of
+ * <code>this</code> plugin manager. Calls the
+ * {@link LafComponentPlugin#initialize()} of all available component
+ * plugins.
+ */
+ public void initializeAll() {
+ Set availablePlugins = this.getAvailablePlugins();
+ for (Iterator iterator = availablePlugins.iterator(); iterator
+ .hasNext();) {
+ Object pluginObject = iterator.next();
+ if (pluginObject instanceof LafComponentPlugin)
+ ((LafComponentPlugin) pluginObject).initialize();
+ }
+ }
+
+ /**
+ * Helper function to uninitialize all available component plugins of
+ * <code>this</code> plugin manager. Calls the
+ * {@link LafComponentPlugin#uninitialize()} of all available component
+ * plugins.
+ */
+ public void uninitializeAll() {
+ Set availablePlugins = this.getAvailablePlugins();
+ for (Iterator iterator = availablePlugins.iterator(); iterator
+ .hasNext();) {
+ Object pluginObject = iterator.next();
+ if (pluginObject instanceof LafComponentPlugin)
+ ((LafComponentPlugin) pluginObject).uninitialize();
+ }
+ }
+
+ /**
+ * Helper function to process the (possibly) theme-dependent default
+ * settings of all available component plugins of <code>this</code> plugin
+ * manager. Calls the {@link LafComponentPlugin#getDefaults(Object)} of all
+ * available plugins and puts the respective results in the specified table.
+ *
+ * @param table
+ * The table that will be updated with the (possibly)
+ * theme-dependent default settings of all available component
+ * plugins.
+ * @param themeInfo
+ * LAF-specific information on the current theme.
+ */
+ public void processAllDefaultsEntries(UIDefaults table, Object themeInfo) {
+ Set availablePlugins = this.getAvailablePlugins();
+ for (Iterator iterator = availablePlugins.iterator(); iterator
+ .hasNext();) {
+ Object pluginObject = iterator.next();
+ if (pluginObject instanceof LafComponentPlugin) {
+ Object[] defaults = ((LafComponentPlugin) pluginObject)
+ .getDefaults(themeInfo);
+ if (defaults != null) {
+ table.putDefaults(defaults);
+ }
+ }
+ }
+ }
+}
diff --git a/laf-plugin/src/main/java/org/pushingpixels/lafplugin/LafComponentPlugin.java b/laf-plugin/src/main/java/org/pushingpixels/lafplugin/LafComponentPlugin.java
new file mode 100644
index 0000000..b1d8b3f
--- /dev/null
+++ b/laf-plugin/src/main/java/org/pushingpixels/lafplugin/LafComponentPlugin.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Plugin Kirill Grouchnikov and contributors. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Plugin Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafplugin;
+
+import javax.swing.plaf.metal.MetalTheme;
+
+/**
+ * Basic interface for look-and-feel plugins.
+ *
+ * @author Kirill Grouchnikov
+ * @author Erik Vickroy
+ * @author Robert Beeger
+ * @author Frederic Lavigne
+ * @author Pattrick Gotthardt
+ */
+public interface LafComponentPlugin extends LafPlugin {
+ /**
+ * XML tag for look-and-feel plugins that specify component UI delegates.
+ */
+ public static final String COMPONENT_TAG_PLUGIN_CLASS = "component-plugin-class";
+
+ /**
+ * Initializes <code>this</code> plugin.
+ */
+ public void initialize();
+
+ /**
+ * Unitializes <code>this</code> plugin.
+ */
+ public void uninitialize();
+
+ /**
+ * Retrieves a collection of custom settings based on the specified theme.
+ * The entries in the array should be pairwise, odd being symbolic name of a
+ * setting, and even being the setting value.
+ *
+ * @param themeInfo
+ * Theme information object. Can be {@link MetalTheme}, for
+ * instance or any other LAF-specific object.
+ * @return Collection of custom settings based on the specified theme. The
+ * entries in the array should be pairwise, odd being symbolic name
+ * of a setting, and even being the setting value.
+ */
+ public Object[] getDefaults(Object themeInfo);
+}
diff --git a/laf-plugin/src/main/java/org/pushingpixels/lafplugin/LafPlugin.java b/laf-plugin/src/main/java/org/pushingpixels/lafplugin/LafPlugin.java
new file mode 100644
index 0000000..e8df42b
--- /dev/null
+++ b/laf-plugin/src/main/java/org/pushingpixels/lafplugin/LafPlugin.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Plugin Kirill Grouchnikov and contributors. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafplugin;
+
+
+/**
+ * Basic interface for look-and-feel plugins.
+ *
+ * @author Kirill Grouchnikov
+ * @author Erik Vickroy
+ * @author Robert Beeger
+ * @author Frederic Lavigne
+ * @author Pattrick Gotthardt
+ */
+public interface LafPlugin {
+ /**
+ * Main XML tag. See
+ * {@link PluginManager#PluginManager(String, String, String)}.
+ */
+ public static final String TAG_MAIN = "laf-plugin";
+}
diff --git a/laf-plugin/src/main/java/org/pushingpixels/lafplugin/PluginManager.java b/laf-plugin/src/main/java/org/pushingpixels/lafplugin/PluginManager.java
new file mode 100644
index 0000000..12fd416
--- /dev/null
+++ b/laf-plugin/src/main/java/org/pushingpixels/lafplugin/PluginManager.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Plugin Kirill Grouchnikov and contributors. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafplugin;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.*;
+
+import javax.swing.UIManager;
+
+/**
+ * Plugin manager for look-and-feels.
+ *
+ * @author Kirill Grouchnikov
+ * @author Erik Vickroy
+ * @author Robert Beeger
+ * @author Frederic Lavigne
+ * @author Pattrick Gotthardt
+ */
+public class PluginManager {
+ private String mainTag;
+
+ private String pluginTag;
+
+ private String xmlName;
+
+ private Set plugins;
+
+ /**
+ * Simple constructor.
+ *
+ * @param xmlName
+ * The name of XML file that contains plugin configuration.
+ * @param mainTag
+ * The main tag in the XML configuration file.
+ * @param pluginTag
+ * The tag that corresponds to a single plugin kind. Specifies
+ * the plugin kind that will be located in
+ * {@link #getAvailablePlugins(boolean)}.
+ */
+ public PluginManager(String xmlName, String mainTag, String pluginTag) {
+ this.xmlName = xmlName;
+ this.mainTag = mainTag;
+ this.pluginTag = pluginTag;
+ this.plugins = null;
+ }
+
+ // protected String getPluginClass(URL pluginUrl) {
+ // InputStream is = null;
+ // try {
+ // DocumentBuilder builder = DocumentBuilderFactory.newInstance()
+ // .newDocumentBuilder();
+ // is = pluginUrl.openStream();
+ // Document doc = builder.parse(is);
+ // Node root = doc.getFirstChild();
+ // if (!this.mainTag.equals(root.getNodeName()))
+ // return null;
+ // NodeList children = root.getChildNodes();
+ // for (int i = 0; i < children.getLength(); i++) {
+ // Node child = children.item(i);
+ // if (!this.pluginTag.equals(child.getNodeName()))
+ // continue;
+ // if (child.getChildNodes().getLength() != 1)
+ // return null;
+ // Node text = child.getFirstChild();
+ // if (text.getNodeType() != Node.TEXT_NODE)
+ // return null;
+ // return text.getNodeValue();
+ // }
+ // return null;
+ // } catch (Exception exc) {
+ // return null;
+ // } finally {
+ // if (is != null) {
+ // try {
+ // is.close();
+ // } catch (Exception e) {
+ // }
+ // }
+ // }
+ // }
+ //
+ protected String getPluginClass(URL pluginUrl) {
+ InputStream is = null;
+ InputStreamReader isr = null;
+ try {
+ XMLElement xml = new XMLElement();
+ is = pluginUrl.openStream();
+ isr = new InputStreamReader(is);
+ xml.parseFromReader(isr);
+ if (!this.mainTag.equals(xml.getName()))
+ return null;
+ Enumeration children = xml.enumerateChildren();
+ while (children.hasMoreElements()) {
+ XMLElement child = (XMLElement) children.nextElement();
+ if (!this.pluginTag.equals(child.getName()))
+ continue;
+ if (child.countChildren() != 0)
+ return null;
+ return child.getContent();
+ }
+ return null;
+ } catch (Exception exc) {
+ return null;
+ } finally {
+ if (isr != null) {
+ try {
+ isr.close();
+ } catch (Exception e) {
+ }
+ }
+ if (is != null) {
+ try {
+ is.close();
+ } catch (Exception e) {
+ }
+ }
+ }
+ }
+
+ protected Object getPlugin(URL pluginUrl) throws Exception {
+ String pluginClassName = this.getPluginClass(pluginUrl);
+ if (pluginClassName == null)
+ return null;
+ ClassLoader classLoader = (ClassLoader) UIManager.get("ClassLoader");
+ if (classLoader == null)
+ classLoader = Thread.currentThread().getContextClassLoader();
+ Class pluginClass = Class.forName(pluginClassName, true, classLoader);
+ if (pluginClass == null)
+ return null;
+ Object pluginInstance = pluginClass.newInstance();
+ if (pluginInstance == null)
+ return null;
+ return pluginInstance;
+ }
+
+ /**
+ * Returns a collection of all available plugins.
+ *
+ * @return Collection of all available plugins. The classpath is scanned
+ * only once.
+ * @see #getAvailablePlugins(boolean)
+ */
+ public Set getAvailablePlugins() {
+ return this.getAvailablePlugins(false);
+ }
+
+ /**
+ * Returns a collection of all available plugins. The parameter specifies
+ * whether the classpath should be rescanned or whether to return the
+ * already found plugins (after first-time scan).
+ *
+ * @param toReload
+ * If <code>true</code>, the classpath is scanned for available
+ * plugins every time <code>this</code> function is called. If
+ * <code>false</code>, the classpath scan is performed only once.
+ * The consecutive calls return the cached result.
+ * @return Collection of all available plugins.
+ */
+ public Set getAvailablePlugins(boolean toReload) {
+ if (!toReload && (this.plugins != null))
+ return this.plugins;
+
+ this.plugins = new HashSet();
+
+ // the following is fix by Dag Joar and Christian Schlichtherle
+ // for application running with -Xbootclasspath VM flag. In this case,
+ // the using MyClass.class.getClassLoader() would return null,
+ // but the context class loader will function properly
+ // that classes will be properly loaded regardless of whether the lib is
+ // added to the system class path, the extension class path and
+ // regardless of the class loader architecture set up by some
+ // frameworks.
+ ClassLoader cl = (ClassLoader) UIManager.get("ClassLoader");
+ if (cl == null)
+ cl = Thread.currentThread().getContextClassLoader();
+ try {
+ Enumeration urls = cl.getResources(this.xmlName);
+ while (urls.hasMoreElements()) {
+ URL pluginUrl = (URL) urls.nextElement();
+ Object pluginInstance = this.getPlugin(pluginUrl);
+ if (pluginInstance != null)
+ this.plugins.add(pluginInstance);
+
+ }
+ } catch (Exception exc) {
+ return null;
+ }
+
+ return plugins;
+ }
+}
diff --git a/laf-plugin/src/main/java/org/pushingpixels/lafplugin/XMLElement.java b/laf-plugin/src/main/java/org/pushingpixels/lafplugin/XMLElement.java
new file mode 100644
index 0000000..af4edb1
--- /dev/null
+++ b/laf-plugin/src/main/java/org/pushingpixels/lafplugin/XMLElement.java
@@ -0,0 +1,2875 @@
+/* XMLElement.java
+ *
+ * $Revision: 39 $
+ * $Date: 2009-10-14 19:49:29 -0700 (Wed, 14 Oct 2009) $
+ * $Name$
+ *
+ * This file is part of NanoXML 2 Lite.
+ * Copyright (C) 2000-2002 Marc De Scheemaecker, All Rights Reserved.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the
+ * use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software in
+ * a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ *
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ *
+ * 3. This notice may not be removed or altered from any source distribution.
+ *****************************************************************************/
+
+
+package org.pushingpixels.lafplugin;
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.Writer;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+
+/**
+ * XMLElement is a representation of an XML object. The object is able to parse
+ * XML code.
+ * <P><DL>
+ * <DT><B>Parsing XML Data</B></DT>
+ * <DD>
+ * You can parse XML data using the following code:
+ * <UL><CODE>
+ * XMLElement xml = new XMLElement();<BR>
+ * FileReader reader = new FileReader("filename.xml");<BR>
+ * xml.parseFromReader(reader);
+ * </CODE></UL></DD></DL>
+ * <DL><DT><B>Retrieving Attributes</B></DT>
+ * <DD>
+ * You can enumerate the attributes of an element using the method
+ * {@link #enumerateAttributeNames() enumerateAttributeNames}.
+ * The attribute values can be retrieved using the method
+ * {@link #getStringAttribute(java.lang.String) getStringAttribute}.
+ * The following example shows how to list the attributes of an element:
+ * <UL><CODE>
+ * XMLElement element = ...;<BR>
+ * Enumeration enum = element.getAttributeNames();<BR>
+ * while (enum.hasMoreElements()) {<BR>
+ * String key = (String) enum.nextElement();<BR>
+ * String value = element.getStringAttribute(key);<BR>
+ * System.out.println(key + " = " + value);<BR>
+ * }
+ * </CODE></UL></DD></DL>
+ * <DL><DT><B>Retrieving Child Elements</B></DT>
+ * <DD>
+ * You can enumerate the children of an element using
+ * {@link #enumerateChildren() enumerateChildren}.
+ * The number of child elements can be retrieved using
+ * {@link #countChildren() countChildren}.
+ * </DD></DL>
+ * <DL><DT><B>Elements Containing Character Data</B></DT>
+ * <DD>
+ * If an elements contains character data, like in the following example:
+ * <UL><CODE>
+ * <title>The Title</title>
+ * </CODE></UL>
+ * you can retrieve that data using the method
+ * {@link #getContent() getContent}.
+ * </DD></DL>
+ * <DL><DT><B>Subclassing XMLElement</B></DT>
+ * <DD>
+ * When subclassing XMLElement, you need to override the method
+ * {@link #createAnotherElement() createAnotherElement}
+ * which has to return a new copy of the receiver.
+ * </DD></DL>
+ * <P>
+ *
+ * see nanoxml.XMLParseException
+ *
+ * @author Marc De Scheemaecker
+ * <<A href="mailto:cyberelf at mac.com">cyberelf at mac.com</A>>
+ * @version $Name$, $Revision: 39 $
+ */
+public class XMLElement
+{
+
+ /**
+ * Serialization serial version ID.
+ */
+ static final long serialVersionUID = 6685035139346394777L;
+
+
+ /**
+ * Major version of NanoXML. Classes with the same major and minor
+ * version are binary compatible. Classes with the same major version
+ * are source compatible. If the major version is different, you may
+ * need to modify the client source code.
+ *
+ * see nanoxml.XMLElement#NANOXML_MINOR_VERSION
+ */
+ public static final int NANOXML_MAJOR_VERSION = 2;
+
+
+ /**
+ * Minor version of NanoXML. Classes with the same major and minor
+ * version are binary compatible. Classes with the same major version
+ * are source compatible. If the major version is different, you may
+ * need to modify the client source code.
+ *
+ * see nanoxml.XMLElement#NANOXML_MAJOR_VERSION
+ */
+ public static final int NANOXML_MINOR_VERSION = 2;
+
+
+ /**
+ * The attributes given to the element.
+ *
+ * <dl><dt><b>Invariants:</b></dt><dd>
+ * <ul><li>The field can be empty.
+ * <li>The field is never <code>null</code>.
+ * <li>The keys and the values are strings.
+ * </ul></dd></dl>
+ */
+ private Hashtable attributes;
+
+
+ /**
+ * Child elements of the element.
+ *
+ * <dl><dt><b>Invariants:</b></dt><dd>
+ * <ul><li>The field can be empty.
+ * <li>The field is never <code>null</code>.
+ * <li>The elements are instances of <code>XMLElement</code>
+ * or a subclass of <code>XMLElement</code>.
+ * </ul></dd></dl>
+ */
+ private Vector children;
+
+
+ /**
+ * The name of the element.
+ *
+ * <dl><dt><b>Invariants:</b></dt><dd>
+ * <ul><li>The field is <code>null</code> iff the element is not
+ * initialized by either parse or setName.
+ * <li>If the field is not <code>null</code>, it's not empty.
+ * <li>If the field is not <code>null</code>, it contains a valid
+ * XML identifier.
+ * </ul></dd></dl>
+ */
+ private String name;
+
+
+ /**
+ * The #PCDATA content of the object.
+ *
+ * <dl><dt><b>Invariants:</b></dt><dd>
+ * <ul><li>The field is <code>null</code> iff the element is not a
+ * #PCDATA element.
+ * <li>The field can be any string, including the empty string.
+ * </ul></dd></dl>
+ */
+ private String contents;
+
+
+ /**
+ * Conversion table for &...; entities. The keys are the entity names
+ * without the & and ; delimiters.
+ *
+ * <dl><dt><b>Invariants:</b></dt><dd>
+ * <ul><li>The field is never <code>null</code>.
+ * <li>The field always contains the following associations:
+ * "lt" => "<", "gt" => ">",
+ * "quot" => "\"", "apos" => "'",
+ * "amp" => "&"
+ * <li>The keys are strings
+ * <li>The values are char arrays
+ * </ul></dd></dl>
+ */
+ private Hashtable entities;
+
+
+ /**
+ * The line number where the element starts.
+ *
+ * <dl><dt><b>Invariants:</b></dt><dd>
+ * <ul><li><code>lineNr >= 0</code>
+ * </ul></dd></dl>
+ */
+ private int lineNr;
+
+
+ /**
+ * <code>true</code> if the case of the element and attribute names
+ * are case insensitive.
+ */
+ private boolean ignoreCase;
+
+
+ /**
+ * <code>true</code> if the leading and trailing whitespace of #PCDATA
+ * sections have to be ignored.
+ */
+ private boolean ignoreWhitespace;
+
+
+ /**
+ * Character read too much.
+ * This character provides push-back functionality to the input reader
+ * without having to use a PushbackReader.
+ * If there is no such character, this field is '\0'.
+ */
+ private char charReadTooMuch;
+
+
+ /**
+ * The reader provided by the caller of the parse method.
+ *
+ * <dl><dt><b>Invariants:</b></dt><dd>
+ * <ul><li>The field is not <code>null</code> while the parse method
+ * is running.
+ * </ul></dd></dl>
+ */
+ private Reader reader;
+
+
+ /**
+ * The current line number in the source content.
+ *
+ * <dl><dt><b>Invariants:</b></dt><dd>
+ * <ul><li>parserLineNr > 0 while the parse method is running.
+ * </ul></dd></dl>
+ */
+ private int parserLineNr;
+
+
+ /**
+ * Creates and initializes a new XML element.
+ * Calling the construction is equivalent to:
+ * <ul><code>new XMLElement(new Hashtable(), false, true)
+ * </code></ul>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>countChildren() => 0
+ * <li>enumerateChildren() => empty enumeration
+ * <li>enumeratePropertyNames() => empty enumeration
+ * <li>getChildren() => empty vector
+ * <li>getContent() => ""
+ * <li>getLineNr() => 0
+ * <li>getName() => null
+ * </ul></dd></dl>
+ *
+ * see nanoxml.XMLElement#XMLElement(java.util.Hashtable)
+ * XMLElement(Hashtable)
+ * see nanoxml.XMLElement#XMLElement(boolean)
+ * see nanoxml.XMLElement#XMLElement(java.util.Hashtable,boolean)
+ * XMLElement(Hashtable, boolean)
+ */
+ public XMLElement()
+ {
+ this(new Hashtable(), false, true, true);
+ }
+
+
+ /**
+ * Creates and initializes a new XML element.
+ * Calling the construction is equivalent to:
+ * <ul><code>new XMLElement(entities, false, true)
+ * </code></ul>
+ *
+ * @param entities
+ * The entity conversion table.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>entities != null</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>countChildren() => 0
+ * <li>enumerateChildren() => empty enumeration
+ * <li>enumeratePropertyNames() => empty enumeration
+ * <li>getChildren() => empty vector
+ * <li>getContent() => ""
+ * <li>getLineNr() => 0
+ * <li>getName() => null
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#XMLElement()
+ * see nanoxml.XMLElement#XMLElement(boolean)
+ * see nanoxml.XMLElement#XMLElement(java.util.Hashtable,boolean)
+ * XMLElement(Hashtable, boolean)
+ */
+ public XMLElement(Hashtable entities)
+ {
+ this(entities, false, true, true);
+ }
+
+
+ /**
+ * Creates and initializes a new XML element.
+ * Calling the construction is equivalent to:
+ * <ul><code>new XMLElement(new Hashtable(), skipLeadingWhitespace, true)
+ * </code></ul>
+ *
+ * @param skipLeadingWhitespace
+ * <code>true</code> if leading and trailing whitespace in PCDATA
+ * content has to be removed.
+ *
+ * </dl><dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>countChildren() => 0
+ * <li>enumerateChildren() => empty enumeration
+ * <li>enumeratePropertyNames() => empty enumeration
+ * <li>getChildren() => empty vector
+ * <li>getContent() => ""
+ * <li>getLineNr() => 0
+ * <li>getName() => null
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#XMLElement()
+ * see nanoxml.XMLElement#XMLElement(java.util.Hashtable)
+ * XMLElement(Hashtable)
+ * see nanoxml.XMLElement#XMLElement(java.util.Hashtable,boolean)
+ * XMLElement(Hashtable, boolean)
+ */
+ public XMLElement(boolean skipLeadingWhitespace)
+ {
+ this(new Hashtable(), skipLeadingWhitespace, true, true);
+ }
+
+
+ /**
+ * Creates and initializes a new XML element.
+ * Calling the construction is equivalent to:
+ * <ul><code>new XMLElement(entities, skipLeadingWhitespace, true)
+ * </code></ul>
+ *
+ * @param entities
+ * The entity conversion table.
+ * @param skipLeadingWhitespace
+ * <code>true</code> if leading and trailing whitespace in PCDATA
+ * content has to be removed.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>entities != null</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>countChildren() => 0
+ * <li>enumerateChildren() => empty enumeration
+ * <li>enumeratePropertyNames() => empty enumeration
+ * <li>getChildren() => empty vector
+ * <li>getContent() => ""
+ * <li>getLineNr() => 0
+ * <li>getName() => null
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#XMLElement()
+ * see nanoxml.XMLElement#XMLElement(boolean)
+ * see nanoxml.XMLElement#XMLElement(java.util.Hashtable)
+ * XMLElement(Hashtable)
+ */
+ public XMLElement(Hashtable entities,
+ boolean skipLeadingWhitespace)
+ {
+ this(entities, skipLeadingWhitespace, true, true);
+ }
+
+
+ /**
+ * Creates and initializes a new XML element.
+ *
+ * @param entities
+ * The entity conversion table.
+ * @param skipLeadingWhitespace
+ * <code>true</code> if leading and trailing whitespace in PCDATA
+ * content has to be removed.
+ * @param ignoreCase
+ * <code>true</code> if the case of element and attribute names have
+ * to be ignored.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>entities != null</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>countChildren() => 0
+ * <li>enumerateChildren() => empty enumeration
+ * <li>enumeratePropertyNames() => empty enumeration
+ * <li>getChildren() => empty vector
+ * <li>getContent() => ""
+ * <li>getLineNr() => 0
+ * <li>getName() => null
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#XMLElement()
+ * see nanoxml.XMLElement#XMLElement(boolean)
+ * see nanoxml.XMLElement#XMLElement(java.util.Hashtable)
+ * XMLElement(Hashtable)
+ * see nanoxml.XMLElement#XMLElement(java.util.Hashtable,boolean)
+ * XMLElement(Hashtable, boolean)
+ */
+ public XMLElement(Hashtable entities,
+ boolean skipLeadingWhitespace,
+ boolean ignoreCase)
+ {
+ this(entities, skipLeadingWhitespace, true, ignoreCase);
+ }
+
+
+ /**
+ * Creates and initializes a new XML element.
+ * <P>
+ * This constructor should <I>only</I> be called from
+ * {@link #createAnotherElement() createAnotherElement}
+ * to create child elements.
+ *
+ * @param entities
+ * The entity conversion table.
+ * @param skipLeadingWhitespace
+ * <code>true</code> if leading and trailing whitespace in PCDATA
+ * content has to be removed.
+ * @param fillBasicConversionTable
+ * <code>true</code> if the basic entities need to be added to
+ * the entity list.
+ * @param ignoreCase
+ * <code>true</code> if the case of element and attribute names have
+ * to be ignored.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>entities != null</code>
+ * <li>if <code>fillBasicConversionTable == false</code>
+ * then <code>entities</code> contains at least the following
+ * entries: <code>amp</code>, <code>lt</code>, <code>gt</code>,
+ * <code>apos</code> and <code>quot</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>countChildren() => 0
+ * <li>enumerateChildren() => empty enumeration
+ * <li>enumeratePropertyNames() => empty enumeration
+ * <li>getChildren() => empty vector
+ * <li>getContent() => ""
+ * <li>getLineNr() => 0
+ * <li>getName() => null
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#createAnotherElement()
+ */
+ protected XMLElement(Hashtable entities,
+ boolean skipLeadingWhitespace,
+ boolean fillBasicConversionTable,
+ boolean ignoreCase)
+ {
+ this.ignoreWhitespace = skipLeadingWhitespace;
+ this.ignoreCase = ignoreCase;
+ this.name = null;
+ this.contents = "";
+ this.attributes = new Hashtable();
+ this.children = new Vector();
+ this.entities = entities;
+ this.lineNr = 0;
+ Enumeration enumeration = this.entities.keys();
+ while (enumeration.hasMoreElements()) {
+ Object key = enumeration.nextElement();
+ Object value = this.entities.get(key);
+ if (value instanceof String) {
+ value = ((String) value).toCharArray();
+ this.entities.put(key, value);
+ }
+ }
+ if (fillBasicConversionTable) {
+ this.entities.put("amp", new char[] { '&' });
+ this.entities.put("quot", new char[] { '"' });
+ this.entities.put("apos", new char[] { '\'' });
+ this.entities.put("lt", new char[] { '<' });
+ this.entities.put("gt", new char[] { '>' });
+ }
+ }
+
+
+ /**
+ * Adds a child element.
+ *
+ * @param child
+ * The child element to add.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>child != null</code>
+ * <li><code>child.getName() != null</code>
+ * <li><code>child</code> does not have a parent element
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>countChildren() => old.countChildren() + 1
+ * <li>enumerateChildren() => old.enumerateChildren() + child
+ * <li>getChildren() => old.enumerateChildren() + child
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#countChildren()
+ * see nanoxml.XMLElement#enumerateChildren()
+ * see nanoxml.XMLElement#getChildren()
+ * see nanoxml.XMLElement#removeChild(nanoxml.XMLElement)
+ * removeChild(XMLElement)
+ */
+ public void addChild(XMLElement child)
+ {
+ this.children.addElement(child);
+ }
+
+
+ /**
+ * Adds or modifies an attribute.
+ *
+ * @param name
+ * The name of the attribute.
+ * @param value
+ * The value of the attribute.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * <li><code>value != null</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>enumerateAttributeNames()
+ * => old.enumerateAttributeNames() + name
+ * <li>getAttribute(name) => value
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setDoubleAttribute(java.lang.String, double)
+ * setDoubleAttribute(String, double)
+ * see nanoxml.XMLElement#setIntAttribute(java.lang.String, int)
+ * setIntAttribute(String, int)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getAttribute(java.lang.String)
+ * getAttribute(String)
+ * see nanoxml.XMLElement#getAttribute(java.lang.String, java.lang.Object)
+ * getAttribute(String, Object)
+ * see nanoxml.XMLElement#getAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getAttribute(String, Hashtable, String, boolean)
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String)
+ * getStringAttribute(String)
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String,
+ * java.lang.String)
+ * getStringAttribute(String, String)
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getStringAttribute(String, Hashtable, String, boolean)
+ */
+ public void setAttribute(String name,
+ Object value)
+ {
+ if (this.ignoreCase) {
+ name = name.toUpperCase();
+ }
+ this.attributes.put(name, value.toString());
+ }
+
+
+ /**
+ * Adds or modifies an attribute.
+ *
+ * @param name
+ * The name of the attribute.
+ * @param value
+ * The value of the attribute.
+ *
+ * @deprecated Use {@link #setAttribute(java.lang.String, java.lang.Object)
+ * setAttribute} instead.
+ */
+ public void addProperty(String name,
+ Object value)
+ {
+ this.setAttribute(name, value);
+ }
+
+
+ /**
+ * Adds or modifies an attribute.
+ *
+ * @param name
+ * The name of the attribute.
+ * @param value
+ * The value of the attribute.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>enumerateAttributeNames()
+ * => old.enumerateAttributeNames() + name
+ * <li>getIntAttribute(name) => value
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setDoubleAttribute(java.lang.String, double)
+ * setDoubleAttribute(String, double)
+ * see nanoxml.XMLElement#setAttribute(java.lang.String, java.lang.Object)
+ * setAttribute(String, Object)
+ * see nanoxml.XMLElement#removeAttribute(java.lang.String)
+ * removeAttribute(String)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String)
+ * getIntAttribute(String)
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String, int)
+ * getIntAttribute(String, int)
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getIntAttribute(String, Hashtable, String, boolean)
+ */
+ public void setIntAttribute(String name,
+ int value)
+ {
+ if (this.ignoreCase) {
+ name = name.toUpperCase();
+ }
+ this.attributes.put(name, Integer.toString(value));
+ }
+
+
+ /**
+ * Adds or modifies an attribute.
+ *
+ * @param key
+ * The name of the attribute.
+ * @param value
+ * The value of the attribute.
+ *
+ * @deprecated Use {@link #setIntAttribute(java.lang.String, int)
+ * setIntAttribute} instead.
+ */
+ public void addProperty(String key,
+ int value)
+ {
+ this.setIntAttribute(key, value);
+ }
+
+
+ /**
+ * Adds or modifies an attribute.
+ *
+ * @param name
+ * The name of the attribute.
+ * @param value
+ * The value of the attribute.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>enumerateAttributeNames()
+ * => old.enumerateAttributeNames() + name
+ * <li>getDoubleAttribute(name) => value
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setIntAttribute(java.lang.String, int)
+ * setIntAttribute(String, int)
+ * see nanoxml.XMLElement#setAttribute(java.lang.String, java.lang.Object)
+ * setAttribute(String, Object)
+ * see nanoxml.XMLElement#removeAttribute(java.lang.String)
+ * removeAttribute(String)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String)
+ * getDoubleAttribute(String)
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String, double)
+ * getDoubleAttribute(String, double)
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getDoubleAttribute(String, Hashtable, String, boolean)
+ */
+ public void setDoubleAttribute(String name,
+ double value)
+ {
+ if (this.ignoreCase) {
+ name = name.toUpperCase();
+ }
+ this.attributes.put(name, Double.toString(value));
+ }
+
+
+ /**
+ * Adds or modifies an attribute.
+ *
+ * @param name
+ * The name of the attribute.
+ * @param value
+ * The value of the attribute.
+ *
+ * @deprecated Use {@link #setDoubleAttribute(java.lang.String, double)
+ * setDoubleAttribute} instead.
+ */
+ public void addProperty(String name,
+ double value)
+ {
+ this.setDoubleAttribute(name, value);
+ }
+
+
+ /**
+ * Returns the number of child elements of the element.
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li><code>result >= 0</code>
+ * </ul></dd></dl>
+ *
+ * see nanoxml.XMLElement#addChild(nanoxml.XMLElement)
+ * addChild(XMLElement)
+ * see nanoxml.XMLElement#enumerateChildren()
+ * see nanoxml.XMLElement#getChildren()
+ * see nanoxml.XMLElement#removeChild(nanoxml.XMLElement)
+ * removeChild(XMLElement)
+ */
+ public int countChildren()
+ {
+ return this.children.size();
+ }
+
+
+ /**
+ * Enumerates the attribute names.
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li><code>result != null</code>
+ * </ul></dd></dl>
+ *
+ * see nanoxml.XMLElement#setDoubleAttribute(java.lang.String, double)
+ * setDoubleAttribute(String, double)
+ * see nanoxml.XMLElement#setIntAttribute(java.lang.String, int)
+ * setIntAttribute(String, int)
+ * see nanoxml.XMLElement#setAttribute(java.lang.String, java.lang.Object)
+ * setAttribute(String, Object)
+ * see nanoxml.XMLElement#removeAttribute(java.lang.String)
+ * removeAttribute(String)
+ * see nanoxml.XMLElement#getAttribute(java.lang.String)
+ * getAttribute(String)
+ * see nanoxml.XMLElement#getAttribute(java.lang.String, java.lang.Object)
+ * getAttribute(String, String)
+ * see nanoxml.XMLElement#getAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getAttribute(String, Hashtable, String, boolean)
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String)
+ * getStringAttribute(String)
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String,
+ * java.lang.String)
+ * getStringAttribute(String, String)
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getStringAttribute(String, Hashtable, String, boolean)
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String)
+ * getIntAttribute(String)
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String, int)
+ * getIntAttribute(String, int)
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getIntAttribute(String, Hashtable, String, boolean)
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String)
+ * getDoubleAttribute(String)
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String, double)
+ * getDoubleAttribute(String, double)
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getDoubleAttribute(String, Hashtable, String, boolean)
+ * see nanoxml.XMLElement#getBooleanAttribute(java.lang.String,
+ * java.lang.String,
+ * java.lang.String, boolean)
+ * getBooleanAttribute(String, String, String, boolean)
+ */
+ public Enumeration enumerateAttributeNames()
+ {
+ return this.attributes.keys();
+ }
+
+
+ /**
+ * Enumerates the attribute names.
+ *
+ * @deprecated Use {@link #enumerateAttributeNames()
+ * enumerateAttributeNames} instead.
+ */
+ public Enumeration enumeratePropertyNames()
+ {
+ return this.enumerateAttributeNames();
+ }
+
+
+ /**
+ * Enumerates the child elements.
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li><code>result != null</code>
+ * </ul></dd></dl>
+ *
+ * see nanoxml.XMLElement#addChild(nanoxml.XMLElement)
+ * addChild(XMLElement)
+ * see nanoxml.XMLElement#countChildren()
+ * see nanoxml.XMLElement#getChildren()
+ * see nanoxml.XMLElement#removeChild(nanoxml.XMLElement)
+ * removeChild(XMLElement)
+ */
+ public Enumeration enumerateChildren()
+ {
+ return this.children.elements();
+ }
+
+
+ /**
+ * Returns the child elements as a Vector. It is safe to modify this
+ * Vector.
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li><code>result != null</code>
+ * </ul></dd></dl>
+ *
+ * see nanoxml.XMLElement#addChild(nanoxml.XMLElement)
+ * addChild(XMLElement)
+ * see nanoxml.XMLElement#countChildren()
+ * see nanoxml.XMLElement#enumerateChildren()
+ * see nanoxml.XMLElement#removeChild(nanoxml.XMLElement)
+ * removeChild(XMLElement)
+ */
+ public Vector getChildren()
+ {
+ try {
+ return (Vector) this.children.clone();
+ } catch (Exception e) {
+ // this never happens, however, some Java compilers are so
+ // braindead that they require this exception clause
+ return null;
+ }
+ }
+
+
+ /**
+ * Returns the PCDATA content of the object. If there is no such content,
+ * <CODE>null</CODE> is returned.
+ *
+ * @deprecated Use {@link #getContent() getContent} instead.
+ */
+ public String getContents()
+ {
+ return this.getContent();
+ }
+
+
+ /**
+ * Returns the PCDATA content of the object. If there is no such content,
+ * <CODE>null</CODE> is returned.
+ *
+ * see nanoxml.XMLElement#setContent(java.lang.String)
+ * setContent(String)
+ */
+ public String getContent()
+ {
+ return this.contents;
+ }
+
+
+ /**
+ * Returns the line nr in the source data on which the element is found.
+ * This method returns <code>0</code> there is no associated source data.
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li><code>result >= 0</code>
+ * </ul></dd></dl>
+ */
+ public int getLineNr()
+ {
+ return this.lineNr;
+ }
+
+
+ /**
+ * Returns an attribute of the element.
+ * If the attribute doesn't exist, <code>null</code> is returned.
+ *
+ * @param name The name of the attribute.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setAttribute(java.lang.String, java.lang.Object)
+ * setAttribute(String, Object)
+ * see nanoxml.XMLElement#removeAttribute(java.lang.String)
+ * removeAttribute(String)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getAttribute(java.lang.String, java.lang.Object)
+ * getAttribute(String, Object)
+ * see nanoxml.XMLElement#getAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getAttribute(String, Hashtable, String, boolean)
+ */
+ public Object getAttribute(String name)
+ {
+ return this.getAttribute(name, null);
+ }
+
+
+ /**
+ * Returns an attribute of the element.
+ * If the attribute doesn't exist, <code>defaultValue</code> is returned.
+ *
+ * @param name The name of the attribute.
+ * @param defaultValue Key to use if the attribute is missing.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setAttribute(java.lang.String, java.lang.Object)
+ * setAttribute(String, Object)
+ * see nanoxml.XMLElement#removeAttribute(java.lang.String)
+ * removeAttribute(String)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getAttribute(java.lang.String)
+ * getAttribute(String)
+ * see nanoxml.XMLElement#getAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getAttribute(String, Hashtable, String, boolean)
+ */
+ public Object getAttribute(String name,
+ Object defaultValue)
+ {
+ if (this.ignoreCase) {
+ name = name.toUpperCase();
+ }
+ Object value = this.attributes.get(name);
+ if (value == null) {
+ value = defaultValue;
+ }
+ return value;
+ }
+
+
+ /**
+ * Returns an attribute by looking up a key in a hashtable.
+ * If the attribute doesn't exist, the value corresponding to defaultKey
+ * is returned.
+ * <P>
+ * As an example, if valueSet contains the mapping <code>"one" =>
+ * "1"</code>
+ * and the element contains the attribute <code>attr="one"</code>, then
+ * <code>getAttribute("attr", mapping, defaultKey, false)</code> returns
+ * <code>"1"</code>.
+ *
+ * @param name
+ * The name of the attribute.
+ * @param valueSet
+ * Hashtable mapping keys to values.
+ * @param defaultKey
+ * Key to use if the attribute is missing.
+ * @param allowLiterals
+ * <code>true</code> if literals are valid.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * <li><code>valueSet</code> != null
+ * <li>the keys of <code>valueSet</code> are strings
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setAttribute(java.lang.String, java.lang.Object)
+ * setAttribute(String, Object)
+ * see nanoxml.XMLElement#removeAttribute(java.lang.String)
+ * removeAttribute(String)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getAttribute(java.lang.String)
+ * getAttribute(String)
+ * see nanoxml.XMLElement#getAttribute(java.lang.String, java.lang.Object)
+ * getAttribute(String, Object)
+ */
+ public Object getAttribute(String name,
+ Hashtable valueSet,
+ String defaultKey,
+ boolean allowLiterals)
+ {
+ if (this.ignoreCase) {
+ name = name.toUpperCase();
+ }
+ Object key = this.attributes.get(name);
+ Object result;
+ if (key == null) {
+ key = defaultKey;
+ }
+ result = valueSet.get(key);
+ if (result == null) {
+ if (allowLiterals) {
+ result = key;
+ } else {
+ throw this.invalidValue(name, (String) key);
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * Returns an attribute of the element.
+ * If the attribute doesn't exist, <code>null</code> is returned.
+ *
+ * @param name The name of the attribute.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setAttribute(java.lang.String, java.lang.Object)
+ * setAttribute(String, Object)
+ * see nanoxml.XMLElement#removeAttribute(java.lang.String)
+ * removeAttribute(String)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String,
+ * java.lang.String)
+ * getStringAttribute(String, String)
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getStringAttribute(String, Hashtable, String, boolean)
+ */
+ public String getStringAttribute(String name)
+ {
+ return this.getStringAttribute(name, null);
+ }
+
+
+ /**
+ * Returns an attribute of the element.
+ * If the attribute doesn't exist, <code>defaultValue</code> is returned.
+ *
+ * @param name The name of the attribute.
+ * @param defaultValue Key to use if the attribute is missing.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setAttribute(java.lang.String, java.lang.Object)
+ * setAttribute(String, Object)
+ * see nanoxml.XMLElement#removeAttribute(java.lang.String)
+ * removeAttribute(String)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String)
+ * getStringAttribute(String)
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getStringAttribute(String, Hashtable, String, boolean)
+ */
+ public String getStringAttribute(String name,
+ String defaultValue)
+ {
+ return (String) this.getAttribute(name, defaultValue);
+ }
+
+
+ /**
+ * Returns an attribute by looking up a key in a hashtable.
+ * If the attribute doesn't exist, the value corresponding to defaultKey
+ * is returned.
+ * <P>
+ * As an example, if valueSet contains the mapping <code>"one" =>
+ * "1"</code>
+ * and the element contains the attribute <code>attr="one"</code>, then
+ * <code>getAttribute("attr", mapping, defaultKey, false)</code> returns
+ * <code>"1"</code>.
+ *
+ * @param name
+ * The name of the attribute.
+ * @param valueSet
+ * Hashtable mapping keys to values.
+ * @param defaultKey
+ * Key to use if the attribute is missing.
+ * @param allowLiterals
+ * <code>true</code> if literals are valid.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * <li><code>valueSet</code> != null
+ * <li>the keys of <code>valueSet</code> are strings
+ * <li>the values of <code>valueSet</code> are strings
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setAttribute(java.lang.String, java.lang.Object)
+ * setAttribute(String, Object)
+ * see nanoxml.XMLElement#removeAttribute(java.lang.String)
+ * removeAttribute(String)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String)
+ * getStringAttribute(String)
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String,
+ * java.lang.String)
+ * getStringAttribute(String, String)
+ */
+ public String getStringAttribute(String name,
+ Hashtable valueSet,
+ String defaultKey,
+ boolean allowLiterals)
+ {
+ return (String) this.getAttribute(name, valueSet, defaultKey,
+ allowLiterals);
+ }
+
+
+ /**
+ * Returns an attribute of the element.
+ * If the attribute doesn't exist, <code>0</code> is returned.
+ *
+ * @param name The name of the attribute.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setIntAttribute(java.lang.String, int)
+ * setIntAttribute(String, int)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String, int)
+ * getIntAttribute(String, int)
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getIntAttribute(String, Hashtable, String, boolean)
+ */
+ public int getIntAttribute(String name)
+ {
+ return this.getIntAttribute(name, 0);
+ }
+
+
+ /**
+ * Returns an attribute of the element.
+ * If the attribute doesn't exist, <code>defaultValue</code> is returned.
+ *
+ * @param name The name of the attribute.
+ * @param defaultValue Key to use if the attribute is missing.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setIntAttribute(java.lang.String, int)
+ * setIntAttribute(String, int)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String)
+ * getIntAttribute(String)
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getIntAttribute(String, Hashtable, String, boolean)
+ */
+ public int getIntAttribute(String name,
+ int defaultValue)
+ {
+ if (this.ignoreCase) {
+ name = name.toUpperCase();
+ }
+ String value = (String) this.attributes.get(name);
+ if (value == null) {
+ return defaultValue;
+ } else {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ throw this.invalidValue(name, value);
+ }
+ }
+ }
+
+
+ /**
+ * Returns an attribute by looking up a key in a hashtable.
+ * If the attribute doesn't exist, the value corresponding to defaultKey
+ * is returned.
+ * <P>
+ * As an example, if valueSet contains the mapping <code>"one" => 1</code>
+ * and the element contains the attribute <code>attr="one"</code>, then
+ * <code>getIntAttribute("attr", mapping, defaultKey, false)</code> returns
+ * <code>1</code>.
+ *
+ * @param name
+ * The name of the attribute.
+ * @param valueSet
+ * Hashtable mapping keys to values.
+ * @param defaultKey
+ * Key to use if the attribute is missing.
+ * @param allowLiteralNumbers
+ * <code>true</code> if literal numbers are valid.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * <li><code>valueSet</code> != null
+ * <li>the keys of <code>valueSet</code> are strings
+ * <li>the values of <code>valueSet</code> are Integer objects
+ * <li><code>defaultKey</code> is either <code>null</code>, a
+ * key in <code>valueSet</code> or an integer.
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setIntAttribute(java.lang.String, int)
+ * setIntAttribute(String, int)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String)
+ * getIntAttribute(String)
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String, int)
+ * getIntAttribute(String, int)
+ */
+ public int getIntAttribute(String name,
+ Hashtable valueSet,
+ String defaultKey,
+ boolean allowLiteralNumbers)
+ {
+ if (this.ignoreCase) {
+ name = name.toUpperCase();
+ }
+ Object key = this.attributes.get(name);
+ Integer result;
+ if (key == null) {
+ key = defaultKey;
+ }
+ try {
+ result = (Integer) valueSet.get(key);
+ } catch (ClassCastException e) {
+ throw this.invalidValueSet(name);
+ }
+ if (result == null) {
+ if (! allowLiteralNumbers) {
+ throw this.invalidValue(name, (String) key);
+ }
+ try {
+ result = Integer.valueOf((String) key);
+ } catch (NumberFormatException e) {
+ throw this.invalidValue(name, (String) key);
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * Returns an attribute of the element.
+ * If the attribute doesn't exist, <code>0.0</code> is returned.
+ *
+ * @param name The name of the attribute.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setDoubleAttribute(java.lang.String, double)
+ * setDoubleAttribute(String, double)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String, double)
+ * getDoubleAttribute(String, double)
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getDoubleAttribute(String, Hashtable, String, boolean)
+ */
+ public double getDoubleAttribute(String name)
+ {
+ return this.getDoubleAttribute(name, 0.);
+ }
+
+
+ /**
+ * Returns an attribute of the element.
+ * If the attribute doesn't exist, <code>defaultValue</code> is returned.
+ *
+ * @param name The name of the attribute.
+ * @param defaultValue Key to use if the attribute is missing.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setDoubleAttribute(java.lang.String, double)
+ * setDoubleAttribute(String, double)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String)
+ * getDoubleAttribute(String)
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getDoubleAttribute(String, Hashtable, String, boolean)
+ */
+ public double getDoubleAttribute(String name,
+ double defaultValue)
+ {
+ if (this.ignoreCase) {
+ name = name.toUpperCase();
+ }
+ String value = (String) this.attributes.get(name);
+ if (value == null) {
+ return defaultValue;
+ } else {
+ try {
+ return Double.valueOf(value);
+ } catch (NumberFormatException e) {
+ throw this.invalidValue(name, value);
+ }
+ }
+ }
+
+
+ /**
+ * Returns an attribute by looking up a key in a hashtable.
+ * If the attribute doesn't exist, the value corresponding to defaultKey
+ * is returned.
+ * <P>
+ * As an example, if valueSet contains the mapping <code>"one" =>
+ * 1.0</code>
+ * and the element contains the attribute <code>attr="one"</code>, then
+ * <code>getDoubleAttribute("attr", mapping, defaultKey, false)</code>
+ * returns <code>1.0</code>.
+ *
+ * @param name
+ * The name of the attribute.
+ * @param valueSet
+ * Hashtable mapping keys to values.
+ * @param defaultKey
+ * Key to use if the attribute is missing.
+ * @param allowLiteralNumbers
+ * <code>true</code> if literal numbers are valid.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * <li><code>valueSet != null</code>
+ * <li>the keys of <code>valueSet</code> are strings
+ * <li>the values of <code>valueSet</code> are Double objects
+ * <li><code>defaultKey</code> is either <code>null</code>, a
+ * key in <code>valueSet</code> or a double.
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setDoubleAttribute(java.lang.String, double)
+ * setDoubleAttribute(String, double)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String)
+ * getDoubleAttribute(String)
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String, double)
+ * getDoubleAttribute(String, double)
+ */
+ public double getDoubleAttribute(String name,
+ Hashtable valueSet,
+ String defaultKey,
+ boolean allowLiteralNumbers)
+ {
+ if (this.ignoreCase) {
+ name = name.toUpperCase();
+ }
+ Object key = this.attributes.get(name);
+ Double result;
+ if (key == null) {
+ key = defaultKey;
+ }
+ try {
+ result = (Double) valueSet.get(key);
+ } catch (ClassCastException e) {
+ throw this.invalidValueSet(name);
+ }
+ if (result == null) {
+ if (! allowLiteralNumbers) {
+ throw this.invalidValue(name, (String) key);
+ }
+ try {
+ result = Double.valueOf((String) key);
+ } catch (NumberFormatException e) {
+ throw this.invalidValue(name, (String) key);
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * Returns an attribute of the element.
+ * If the attribute doesn't exist, <code>defaultValue</code> is returned.
+ * If the value of the attribute is equal to <code>trueValue</code>,
+ * <code>true</code> is returned.
+ * If the value of the attribute is equal to <code>falseValue</code>,
+ * <code>false</code> is returned.
+ * If the value doesn't match <code>trueValue</code> or
+ * <code>falseValue</code>, an exception is thrown.
+ *
+ * @param name The name of the attribute.
+ * @param trueValue The value associated with <code>true</code>.
+ * @param falseValue The value associated with <code>true</code>.
+ * @param defaultValue Value to use if the attribute is missing.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * <li><code>trueValue</code> and <code>falseValue</code>
+ * are different strings.
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#setAttribute(java.lang.String, java.lang.Object)
+ * setAttribute(String, Object)
+ * see nanoxml.XMLElement#removeAttribute(java.lang.String)
+ * removeAttribute(String)
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ */
+ public boolean getBooleanAttribute(String name,
+ String trueValue,
+ String falseValue,
+ boolean defaultValue)
+ {
+ if (this.ignoreCase) {
+ name = name.toUpperCase();
+ }
+ Object value = this.attributes.get(name);
+ if (value == null) {
+ return defaultValue;
+ } else if (value.equals(trueValue)) {
+ return true;
+ } else if (value.equals(falseValue)) {
+ return false;
+ } else {
+ throw this.invalidValue(name, (String) value);
+ }
+ }
+
+
+ /**
+ * Returns an attribute by looking up a key in a hashtable.
+ *
+ * @deprecated Use {@link #getIntAttribute(java.lang.String,
+ * java.util.Hashtable, java.lang.String, boolean)
+ * getIntAttribute} instead.
+ */
+ public int getIntProperty(String name,
+ Hashtable valueSet,
+ String defaultKey)
+ {
+ return this.getIntAttribute(name, valueSet, defaultKey, false);
+ }
+
+
+ /**
+ * Returns an attribute.
+ *
+ * @deprecated Use {@link #getStringAttribute(java.lang.String)
+ * getStringAttribute} instead.
+ */
+ public String getProperty(String name)
+ {
+ return this.getStringAttribute(name);
+ }
+
+
+ /**
+ * Returns an attribute.
+ *
+ * @deprecated Use {@link #getStringAttribute(java.lang.String,
+ * java.lang.String) getStringAttribute} instead.
+ */
+ public String getProperty(String name,
+ String defaultValue)
+ {
+ return this.getStringAttribute(name, defaultValue);
+ }
+
+
+ /**
+ * Returns an attribute.
+ *
+ * @deprecated Use {@link #getIntAttribute(java.lang.String, int)
+ * getIntAttribute} instead.
+ */
+ public int getProperty(String name,
+ int defaultValue)
+ {
+ return this.getIntAttribute(name, defaultValue);
+ }
+
+
+ /**
+ * Returns an attribute.
+ *
+ * @deprecated Use {@link #getDoubleAttribute(java.lang.String, double)
+ * getDoubleAttribute} instead.
+ */
+ public double getProperty(String name,
+ double defaultValue)
+ {
+ return this.getDoubleAttribute(name, defaultValue);
+ }
+
+
+ /**
+ * Returns an attribute.
+ *
+ * @deprecated Use {@link #getBooleanAttribute(java.lang.String,
+ * java.lang.String, java.lang.String, boolean)
+ * getBooleanAttribute} instead.
+ */
+ public boolean getProperty(String key,
+ String trueValue,
+ String falseValue,
+ boolean defaultValue)
+ {
+ return this.getBooleanAttribute(key, trueValue, falseValue,
+ defaultValue);
+ }
+
+
+ /**
+ * Returns an attribute by looking up a key in a hashtable.
+ *
+ * @deprecated Use {@link #getAttribute(java.lang.String,
+ * java.util.Hashtable, java.lang.String, boolean)
+ * getAttribute} instead.
+ */
+ public Object getProperty(String name,
+ Hashtable valueSet,
+ String defaultKey)
+ {
+ return this.getAttribute(name, valueSet, defaultKey, false);
+ }
+
+
+ /**
+ * Returns an attribute by looking up a key in a hashtable.
+ *
+ * @deprecated Use {@link #getStringAttribute(java.lang.String,
+ * java.util.Hashtable, java.lang.String, boolean)
+ * getStringAttribute} instead.
+ */
+ public String getStringProperty(String name,
+ Hashtable valueSet,
+ String defaultKey)
+ {
+ return this.getStringAttribute(name, valueSet, defaultKey, false);
+ }
+
+
+ /**
+ * Returns an attribute by looking up a key in a hashtable.
+ *
+ * @deprecated Use {@link #getIntAttribute(java.lang.String,
+ * java.util.Hashtable, java.lang.String, boolean)
+ * getIntAttribute} instead.
+ */
+ public int getSpecialIntProperty(String name,
+ Hashtable valueSet,
+ String defaultKey)
+ {
+ return this.getIntAttribute(name, valueSet, defaultKey, true);
+ }
+
+
+ /**
+ * Returns an attribute by looking up a key in a hashtable.
+ *
+ * @deprecated Use {@link #getDoubleAttribute(java.lang.String,
+ * java.util.Hashtable, java.lang.String, boolean)
+ * getDoubleAttribute} instead.
+ */
+ public double getSpecialDoubleProperty(String name,
+ Hashtable valueSet,
+ String defaultKey)
+ {
+ return this.getDoubleAttribute(name, valueSet, defaultKey, true);
+ }
+
+
+ /**
+ * Returns the name of the element.
+ *
+ * see nanoxml.XMLElement#setName(java.lang.String) setName(String)
+ */
+ public String getName()
+ {
+ return this.name;
+ }
+
+
+ /**
+ * Returns the name of the element.
+ *
+ * @deprecated Use {@link #getName() getName} instead.
+ */
+ public String getTagName()
+ {
+ return this.getName();
+ }
+
+
+ /**
+ * Reads one XML element from a java.io.Reader and parses it.
+ *
+ * @param reader
+ * The reader from which to retrieve the XML data.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>reader != null</code>
+ * <li><code>reader</code> is not closed
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>the state of the receiver is updated to reflect the XML element
+ * parsed from the reader
+ * <li>the reader points to the first character following the last
+ * '>' character of the XML element
+ * </ul></dd></dl><dl>
+ *
+ * @throws java.io.IOException
+ * If an error occured while reading the input.
+ * throws nanoxml.XMLParseException
+ * If an error occured while parsing the read data.
+ */
+ public void parseFromReader(Reader reader)
+ throws IOException, XMLParseException
+ {
+ this.parseFromReader(reader, /*startingLineNr*/ 1);
+ }
+
+
+ /**
+ * Reads one XML element from a java.io.Reader and parses it.
+ *
+ * @param reader
+ * The reader from which to retrieve the XML data.
+ * @param startingLineNr
+ * The line number of the first line in the data.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>reader != null</code>
+ * <li><code>reader</code> is not closed
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>the state of the receiver is updated to reflect the XML element
+ * parsed from the reader
+ * <li>the reader points to the first character following the last
+ * '>' character of the XML element
+ * </ul></dd></dl><dl>
+ *
+ * @throws java.io.IOException
+ * If an error occured while reading the input.
+ * throws nanoxml.XMLParseException
+ * If an error occured while parsing the read data.
+ */
+ public void parseFromReader(Reader reader,
+ int startingLineNr)
+ throws IOException, XMLParseException
+ {
+ this.name = null;
+ this.contents = "";
+ this.attributes = new Hashtable();
+ this.children = new Vector();
+ this.charReadTooMuch = '\0';
+ this.reader = reader;
+ this.parserLineNr = startingLineNr;
+
+ for (;;) {
+ char ch = this.scanWhitespace();
+
+ if (ch != '<') {
+ throw this.expectedInput("<");
+ }
+
+ ch = this.readChar();
+
+ if ((ch == '!') || (ch == '?')) {
+ this.skipSpecialTag(0);
+ } else {
+ this.unreadChar(ch);
+ this.scanElement(this);
+ return;
+ }
+ }
+ }
+
+
+ /**
+ * Reads one XML element from a String and parses it.
+ *
+ * @param string
+ * The of the XML data.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>string != null</code>
+ * <li><code>string.length() > 0</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>the state of the receiver is updated to reflect the XML element
+ * parsed from the reader
+ * </ul></dd></dl><dl>
+ *
+ * throws nanoxml.XMLParseException
+ * If an error occured while parsing the string.
+ */
+ public void parseString(String string)
+ throws XMLParseException
+ {
+ try {
+ this.parseFromReader(new StringReader(string),
+ /*startingLineNr*/ 1);
+ } catch (IOException e) {
+ // Java exception handling suxx
+ }
+ }
+
+
+ /**
+ * Reads one XML element from a String and parses it.
+ *
+ * @param string
+ * The string ofthe XML data.
+ * @param offset
+ * The first character in <code>string</code> to scan.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>string != null</code>
+ * <li><code>offset < string.length()</code>
+ * <li><code>offset >= 0</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>the state of the receiver is updated to reflect the XML element
+ * parsed from the reader
+ * </ul></dd></dl><dl>
+ *
+ * throws nanoxml.XMLParseException
+ * If an error occured while parsing the string.
+ */
+ public void parseString(String string,
+ int offset)
+ throws XMLParseException
+ {
+ this.parseString(string.substring(offset));
+ }
+
+
+ /**
+ * Reads one XML element from a String and parses it.
+ *
+ * @param string
+ * The string of the the XML data.
+ * @param offset
+ * The first character in <code>string</code> to scan.
+ * @param end
+ * The character where to stop scanning.
+ * This character is not scanned.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>string != null</code>
+ * <li><code>end <= string.length()</code>
+ * <li><code>offset < end</code>
+ * <li><code>offset >= 0</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>the state of the receiver is updated to reflect the XML element
+ * parsed from the reader
+ * </ul></dd></dl><dl>
+ *
+ * throws nanoxml.XMLParseException
+ * If an error occured while parsing the string.
+ */
+ public void parseString(String string,
+ int offset,
+ int end)
+ throws XMLParseException
+ {
+ this.parseString(string.substring(offset, end));
+ }
+
+
+ /**
+ * Reads one XML element from a String and parses it.
+ *
+ * @param string
+ * The string of the the XML data.
+ * @param offset
+ * The first character in <code>string</code> to scan.
+ * @param end
+ * The character where to stop scanning.
+ * This character is not scanned.
+ * @param startingLineNr
+ * The line number of the first line in the data.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>string != null</code>
+ * <li><code>end <= string.length()</code>
+ * <li><code>offset < end</code>
+ * <li><code>offset >= 0</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>the state of the receiver is updated to reflect the XML element
+ * parsed from the reader
+ * </ul></dd></dl><dl>
+ *
+ * throws nanoxml.XMLParseException
+ * If an error occured while parsing the string.
+ */
+ public void parseString(String string,
+ int offset,
+ int end,
+ int startingLineNr)
+ throws XMLParseException
+ {
+ string = string.substring(offset, end);
+ try {
+ this.parseFromReader(new StringReader(string), startingLineNr);
+ } catch (IOException e) {
+ // Java exception handling suxx
+ }
+ }
+
+
+ /**
+ * Reads one XML element from a char array and parses it.
+ *
+ * @param input
+ * The string of the the XML data.
+ * @param offset
+ * The first character in <code>string</code> to scan.
+ * @param end
+ * The character where to stop scanning.
+ * This character is not scanned.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>input != null</code>
+ * <li><code>end <= input.length</code>
+ * <li><code>offset < end</code>
+ * <li><code>offset >= 0</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>the state of the receiver is updated to reflect the XML element
+ * parsed from the reader
+ * </ul></dd></dl><dl>
+ *
+ * throws nanoxml.XMLParseException
+ * If an error occured while parsing the string.
+ */
+ public void parseCharArray(char[] input,
+ int offset,
+ int end)
+ throws XMLParseException
+ {
+ this.parseCharArray(input, offset, end, /*startingLineNr*/ 1);
+ }
+
+
+ /**
+ * Reads one XML element from a char array and parses it.
+ *
+ * @param input
+ * The string of the the XML data.
+ * @param offset
+ * The first character in <code>string</code> to scan.
+ * @param end
+ * The character where to stop scanning.
+ * This character is not scanned.
+ * @param startingLineNr
+ * The line number of the first line in the data.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>input != null</code>
+ * <li><code>end <= input.length</code>
+ * <li><code>offset < end</code>
+ * <li><code>offset >= 0</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>the state of the receiver is updated to reflect the XML element
+ * parsed from the reader
+ * </ul></dd></dl><dl>
+ *
+ * throws nanoxml.XMLParseException
+ * If an error occured while parsing the string.
+ */
+ public void parseCharArray(char[] input,
+ int offset,
+ int end,
+ int startingLineNr)
+ throws XMLParseException
+ {
+ try {
+ Reader reader = new CharArrayReader(input, offset, end);
+ this.parseFromReader(reader, startingLineNr);
+ } catch (IOException e) {
+ // This exception will never happen.
+ }
+ }
+
+
+ /**
+ * Removes a child element.
+ *
+ * @param child
+ * The child element to remove.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>child != null</code>
+ * <li><code>child</code> is a child element of the receiver
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>countChildren() => old.countChildren() - 1
+ * <li>enumerateChildren() => old.enumerateChildren() - child
+ * <li>getChildren() => old.enumerateChildren() - child
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#addChild(nanoxml.XMLElement)
+ * addChild(XMLElement)
+ * see nanoxml.XMLElement#countChildren()
+ * see nanoxml.XMLElement#enumerateChildren()
+ * see nanoxml.XMLElement#getChildren()
+ */
+ public void removeChild(XMLElement child)
+ {
+ this.children.removeElement(child);
+ }
+
+
+ /**
+ * Removes an attribute.
+ *
+ * @param name
+ * The name of the attribute.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>enumerateAttributeNames()
+ * => old.enumerateAttributeNames() - name
+ * <li>getAttribute(name) => <code>null</code>
+ * </ul></dd></dl><dl>
+ *
+ * see nanoxml.XMLElement#enumerateAttributeNames()
+ * see nanoxml.XMLElement#setDoubleAttribute(java.lang.String, double)
+ * setDoubleAttribute(String, double)
+ * see nanoxml.XMLElement#setIntAttribute(java.lang.String, int)
+ * setIntAttribute(String, int)
+ * see nanoxml.XMLElement#setAttribute(java.lang.String, java.lang.Object)
+ * setAttribute(String, Object)
+ * see nanoxml.XMLElement#getAttribute(java.lang.String)
+ * getAttribute(String)
+ * see nanoxml.XMLElement#getAttribute(java.lang.String, java.lang.Object)
+ * getAttribute(String, Object)
+ * see nanoxml.XMLElement#getAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getAttribute(String, Hashtable, String, boolean)
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String)
+ * getStringAttribute(String)
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String,
+ * java.lang.String)
+ * getStringAttribute(String, String)
+ * see nanoxml.XMLElement#getStringAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getStringAttribute(String, Hashtable, String, boolean)
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String)
+ * getIntAttribute(String)
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String, int)
+ * getIntAttribute(String, int)
+ * see nanoxml.XMLElement#getIntAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getIntAttribute(String, Hashtable, String, boolean)
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String)
+ * getDoubleAttribute(String)
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String, double)
+ * getDoubleAttribute(String, double)
+ * see nanoxml.XMLElement#getDoubleAttribute(java.lang.String,
+ * java.util.Hashtable,
+ * java.lang.String, boolean)
+ * getDoubleAttribute(String, Hashtable, String, boolean)
+ * see nanoxml.XMLElement#getBooleanAttribute(java.lang.String,
+ * java.lang.String,
+ * java.lang.String, boolean)
+ * getBooleanAttribute(String, String, String, boolean)
+ */
+ public void removeAttribute(String name)
+ {
+ if (this.ignoreCase) {
+ name = name.toUpperCase();
+ }
+ this.attributes.remove(name);
+ }
+
+
+ /**
+ * Removes an attribute.
+ *
+ * @param name
+ * The name of the attribute.
+ *
+ * @deprecated Use {@link #removeAttribute(java.lang.String)
+ * removeAttribute} instead.
+ */
+ public void removeProperty(String name)
+ {
+ this.removeAttribute(name);
+ }
+
+
+ /**
+ * Removes an attribute.
+ *
+ * @param name
+ * The name of the attribute.
+ *
+ * @deprecated Use {@link #removeAttribute(java.lang.String)
+ * removeAttribute} instead.
+ */
+ public void removeChild(String name)
+ {
+ this.removeAttribute(name);
+ }
+
+
+ /**
+ * Creates a new similar XML element.
+ * <P>
+ * You should override this method when subclassing XMLElement.
+ */
+ protected XMLElement createAnotherElement()
+ {
+ return new XMLElement(this.entities,
+ this.ignoreWhitespace,
+ false,
+ this.ignoreCase);
+ }
+
+
+ /**
+ * Changes the content string.
+ *
+ * @param content
+ * The new content string.
+ */
+ public void setContent(String content)
+ {
+ this.contents = content;
+ }
+
+
+ /**
+ * Changes the name of the element.
+ *
+ * @param name
+ * The new name.
+ *
+ * @deprecated Use {@link #setName(java.lang.String) setName} instead.
+ */
+ public void setTagName(String name)
+ {
+ this.setName(name);
+ }
+
+
+ /**
+ * Changes the name of the element.
+ *
+ * @param name
+ * The new name.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name</code> is a valid XML identifier
+ * </ul></dd></dl>
+ *
+ * see nanoxml.XMLElement#getName()
+ */
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+
+ /**
+ * Writes the XML element to a string.
+ *
+ * see nanoxml.XMLElement#write(java.io.Writer) write(Writer)
+ */
+ public String toString()
+ {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ OutputStreamWriter writer = new OutputStreamWriter(out);
+ this.write(writer);
+ writer.flush();
+ return new String(out.toByteArray());
+ } catch (IOException e) {
+ // Java exception handling suxx
+ return super.toString();
+ }
+ }
+
+
+ /**
+ * Writes the XML element to a writer.
+ *
+ * @param writer
+ * The writer to write the XML data to.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>writer != null</code>
+ * <li><code>writer</code> is not closed
+ * </ul></dd></dl>
+ *
+ * @throws java.io.IOException
+ * If the data could not be written to the writer.
+ *
+ * see nanoxml.XMLElement#toString()
+ */
+ public void write(Writer writer)
+ throws IOException
+ {
+ if (this.name == null) {
+ this.writeEncoded(writer, this.contents);
+ return;
+ }
+ writer.write('<');
+ writer.write(this.name);
+ if (! this.attributes.isEmpty()) {
+ Enumeration enumeration = this.attributes.keys();
+ while (enumeration.hasMoreElements()) {
+ writer.write(' ');
+ String key = (String) enumeration.nextElement();
+ String value = (String) this.attributes.get(key);
+ writer.write(key);
+ writer.write('='); writer.write('"');
+ this.writeEncoded(writer, value);
+ writer.write('"');
+ }
+ }
+ if ((this.contents != null) && (this.contents.length() > 0)) {
+ writer.write('>');
+ this.writeEncoded(writer, this.contents);
+ writer.write('<'); writer.write('/');
+ writer.write(this.name);
+ writer.write('>');
+ } else if (this.children.isEmpty()) {
+ writer.write('/'); writer.write('>');
+ } else {
+ writer.write('>');
+ Enumeration enumeration = this.enumerateChildren();
+ while (enumeration.hasMoreElements()) {
+ XMLElement child = (XMLElement) enumeration.nextElement();
+ child.write(writer);
+ }
+ writer.write('<'); writer.write('/');
+ writer.write(this.name);
+ writer.write('>');
+ }
+ }
+
+
+ /**
+ * Writes a string encoded to a writer.
+ *
+ * @param writer
+ * The writer to write the XML data to.
+ * @param str
+ * The string to write encoded.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>writer != null</code>
+ * <li><code>writer</code> is not closed
+ * <li><code>str != null</code>
+ * </ul></dd></dl>
+ */
+ protected void writeEncoded(Writer writer,
+ String str)
+ throws IOException
+ {
+ for (int i = 0; i < str.length(); i += 1) {
+ char ch = str.charAt(i);
+ switch (ch) {
+ case '<':
+ writer.write('&'); writer.write('l'); writer.write('t');
+ writer.write(';');
+ break;
+ case '>':
+ writer.write('&'); writer.write('g'); writer.write('t');
+ writer.write(';');
+ break;
+ case '&':
+ writer.write('&'); writer.write('a'); writer.write('m');
+ writer.write('p'); writer.write(';');
+ break;
+ case '"':
+ writer.write('&'); writer.write('q'); writer.write('u');
+ writer.write('o'); writer.write('t'); writer.write(';');
+ break;
+ case '\'':
+ writer.write('&'); writer.write('a'); writer.write('p');
+ writer.write('o'); writer.write('s'); writer.write(';');
+ break;
+ default:
+ int unicode = (int) ch;
+ if ((unicode < 32) || (unicode > 126)) {
+ writer.write('&'); writer.write('#');
+ writer.write('x');
+ writer.write(Integer.toString(unicode, 16));
+ writer.write(';');
+ } else {
+ writer.write(ch);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Scans an identifier from the current reader.
+ * The scanned identifier is appended to <code>result</code>.
+ *
+ * @param result
+ * The buffer in which the scanned identifier will be put.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>result != null</code>
+ * <li>The next character read from the reader is a valid first
+ * character of an XML identifier.
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>The next character read from the reader won't be an identifier
+ * character.
+ * </ul></dd></dl><dl>
+ */
+ protected void scanIdentifier(StringBuffer result)
+ throws IOException
+ {
+ for (;;) {
+ char ch = this.readChar();
+ if (((ch < 'A') || (ch > 'Z')) && ((ch < 'a') || (ch > 'z'))
+ && ((ch < '0') || (ch > '9')) && (ch != '_') && (ch != '.')
+ && (ch != ':') && (ch != '-') && (ch <= '\u007E')) {
+ this.unreadChar(ch);
+ return;
+ }
+ result.append(ch);
+ }
+ }
+
+
+ /**
+ * This method scans an identifier from the current reader.
+ *
+ * @return the next character following the whitespace.
+ */
+ protected char scanWhitespace()
+ throws IOException
+ {
+ for (;;) {
+ char ch = this.readChar();
+ switch (ch) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ break;
+ default:
+ return ch;
+ }
+ }
+ }
+
+
+ /**
+ * This method scans an identifier from the current reader.
+ * The scanned whitespace is appended to <code>result</code>.
+ *
+ * @return the next character following the whitespace.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>result != null</code>
+ * </ul></dd></dl>
+ */
+ protected char scanWhitespace(StringBuffer result)
+ throws IOException
+ {
+ for (;;) {
+ char ch = this.readChar();
+ switch (ch) {
+ case ' ':
+ case '\t':
+ case '\n':
+ result.append(ch);
+ case '\r':
+ break;
+ default:
+ return ch;
+ }
+ }
+ }
+
+
+ /**
+ * This method scans a delimited string from the current reader.
+ * The scanned string without delimiters is appended to
+ * <code>string</code>.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>string != null</code>
+ * <li>the next char read is the string delimiter
+ * </ul></dd></dl>
+ */
+ protected void scanString(StringBuffer string)
+ throws IOException
+ {
+ char delimiter = this.readChar();
+ if ((delimiter != '\'') && (delimiter != '"')) {
+ throw this.expectedInput("' or \"");
+ }
+ for (;;) {
+ char ch = this.readChar();
+ if (ch == delimiter) {
+ return;
+ } else if (ch == '&') {
+ this.resolveEntity(string);
+ } else {
+ string.append(ch);
+ }
+ }
+ }
+
+
+ /**
+ * Scans a #PCDATA element. CDATA sections and entities are resolved.
+ * The next < char is skipped.
+ * The scanned data is appended to <code>data</code>.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>data != null</code>
+ * </ul></dd></dl>
+ */
+ protected void scanPCData(StringBuffer data)
+ throws IOException
+ {
+ for (;;) {
+ char ch = this.readChar();
+ if (ch == '<') {
+ ch = this.readChar();
+ if (ch == '!') {
+ this.checkCDATA(data);
+ } else {
+ this.unreadChar(ch);
+ return;
+ }
+ } else if (ch == '&') {
+ this.resolveEntity(data);
+ } else {
+ data.append(ch);
+ }
+ }
+ }
+
+
+ /**
+ * Scans a special tag and if the tag is a CDATA section, append its
+ * content to <code>buf</code>.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>buf != null</code>
+ * <li>The first < has already been read.
+ * </ul></dd></dl>
+ */
+ protected boolean checkCDATA(StringBuffer buf)
+ throws IOException
+ {
+ char ch = this.readChar();
+ if (ch != '[') {
+ this.unreadChar(ch);
+ this.skipSpecialTag(0);
+ return false;
+ } else if (! this.checkLiteral("CDATA[")) {
+ this.skipSpecialTag(1); // one [ has already been read
+ return false;
+ } else {
+ int delimiterCharsSkipped = 0;
+ while (delimiterCharsSkipped < 3) {
+ ch = this.readChar();
+ switch (ch) {
+ case ']':
+ if (delimiterCharsSkipped < 2) {
+ delimiterCharsSkipped += 1;
+ } else {
+ buf.append(']');
+ buf.append(']');
+ delimiterCharsSkipped = 0;
+ }
+ break;
+ case '>':
+ if (delimiterCharsSkipped < 2) {
+ for (int i = 0; i < delimiterCharsSkipped; i++) {
+ buf.append(']');
+ }
+ delimiterCharsSkipped = 0;
+ buf.append('>');
+ } else {
+ delimiterCharsSkipped = 3;
+ }
+ break;
+ default:
+ for (int i = 0; i < delimiterCharsSkipped; i += 1) {
+ buf.append(']');
+ }
+ buf.append(ch);
+ delimiterCharsSkipped = 0;
+ }
+ }
+ return true;
+ }
+ }
+
+
+ /**
+ * Skips a comment.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li>The first <!-- has already been read.
+ * </ul></dd></dl>
+ */
+ protected void skipComment()
+ throws IOException
+ {
+ int dashesToRead = 2;
+ while (dashesToRead > 0) {
+ char ch = this.readChar();
+ if (ch == '-') {
+ dashesToRead -= 1;
+ } else {
+ dashesToRead = 2;
+ }
+ }
+ if (this.readChar() != '>') {
+ throw this.expectedInput(">");
+ }
+ }
+
+
+ /**
+ * Skips a special tag or comment.
+ *
+ * @param bracketLevel The number of open square brackets ([) that have
+ * already been read.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li>The first <! has already been read.
+ * <li><code>bracketLevel >= 0</code>
+ * </ul></dd></dl>
+ */
+ protected void skipSpecialTag(int bracketLevel)
+ throws IOException
+ {
+ int tagLevel = 1; // <
+ char stringDelimiter = '\0';
+ if (bracketLevel == 0) {
+ char ch = this.readChar();
+ if (ch == '[') {
+ bracketLevel += 1;
+ } else if (ch == '-') {
+ ch = this.readChar();
+ if (ch == '[') {
+ bracketLevel += 1;
+ } else if (ch == ']') {
+ bracketLevel -= 1;
+ } else if (ch == '-') {
+ this.skipComment();
+ return;
+ }
+ }
+ }
+ while (tagLevel > 0) {
+ char ch = this.readChar();
+ if (stringDelimiter == '\0') {
+ if ((ch == '"') || (ch == '\'')) {
+ stringDelimiter = ch;
+ } else if (bracketLevel <= 0) {
+ if (ch == '<') {
+ tagLevel += 1;
+ } else if (ch == '>') {
+ tagLevel -= 1;
+ }
+ }
+ if (ch == '[') {
+ bracketLevel += 1;
+ } else if (ch == ']') {
+ bracketLevel -= 1;
+ }
+ } else {
+ if (ch == stringDelimiter) {
+ stringDelimiter = '\0';
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Scans the data for literal text.
+ * Scanning stops when a character does not match or after the complete
+ * text has been checked, whichever comes first.
+ *
+ * @param literal the literal to check.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>literal != null</code>
+ * </ul></dd></dl>
+ */
+ protected boolean checkLiteral(String literal)
+ throws IOException
+ {
+ int length = literal.length();
+ for (int i = 0; i < length; i += 1) {
+ if (this.readChar() != literal.charAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Reads a character from a reader.
+ */
+ protected char readChar()
+ throws IOException
+ {
+ if (this.charReadTooMuch != '\0') {
+ char ch = this.charReadTooMuch;
+ this.charReadTooMuch = '\0';
+ return ch;
+ } else {
+ int i = this.reader.read();
+ if (i < 0) {
+ throw this.unexpectedEndOfData();
+ } else if (i == 10) {
+ this.parserLineNr += 1;
+ return '\n';
+ } else {
+ return (char) i;
+ }
+ }
+ }
+
+
+ /**
+ * Scans an XML element.
+ *
+ * @param elt The element that will contain the result.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li>The first < has already been read.
+ * <li><code>elt != null</code>
+ * </ul></dd></dl>
+ */
+ protected void scanElement(XMLElement elt)
+ throws IOException
+ {
+ StringBuffer buf = new StringBuffer();
+ this.scanIdentifier(buf);
+ String name = buf.toString();
+ elt.setName(name);
+ char ch = this.scanWhitespace();
+ while ((ch != '>') && (ch != '/')) {
+ buf.setLength(0);
+ this.unreadChar(ch);
+ this.scanIdentifier(buf);
+ String key = buf.toString();
+ ch = this.scanWhitespace();
+ if (ch != '=') {
+ throw this.expectedInput("=");
+ }
+ this.unreadChar(this.scanWhitespace());
+ buf.setLength(0);
+ this.scanString(buf);
+ elt.setAttribute(key, buf);
+ ch = this.scanWhitespace();
+ }
+ if (ch == '/') {
+ ch = this.readChar();
+ if (ch != '>') {
+ throw this.expectedInput(">");
+ }
+ return;
+ }
+ buf.setLength(0);
+ ch = this.scanWhitespace(buf);
+ if (ch != '<') {
+ this.unreadChar(ch);
+ this.scanPCData(buf);
+ } else {
+ for (;;) {
+ ch = this.readChar();
+ if (ch == '!') {
+ if (this.checkCDATA(buf)) {
+ this.scanPCData(buf);
+ break;
+ } else {
+ ch = this.scanWhitespace(buf);
+ if (ch != '<') {
+ this.unreadChar(ch);
+ this.scanPCData(buf);
+ break;
+ }
+ }
+ } else {
+ if ((ch != '/') || this.ignoreWhitespace) {
+ buf.setLength(0);
+ }
+ if (ch == '/') {
+ this.unreadChar(ch);
+ }
+ break;
+ }
+ }
+ }
+ if (buf.length() == 0) {
+ while (ch != '/') {
+ if (ch == '!') {
+ ch = this.readChar();
+ if (ch != '-') {
+ throw this.expectedInput("Comment or Element");
+ }
+ ch = this.readChar();
+ if (ch != '-') {
+ throw this.expectedInput("Comment or Element");
+ }
+ this.skipComment();
+ } else {
+ this.unreadChar(ch);
+ XMLElement child = this.createAnotherElement();
+ this.scanElement(child);
+ elt.addChild(child);
+ }
+ ch = this.scanWhitespace();
+ if (ch != '<') {
+ throw this.expectedInput("<");
+ }
+ ch = this.readChar();
+ }
+ this.unreadChar(ch);
+ } else {
+ if (this.ignoreWhitespace) {
+ elt.setContent(buf.toString().trim());
+ } else {
+ elt.setContent(buf.toString());
+ }
+ }
+ ch = this.readChar();
+ if (ch != '/') {
+ throw this.expectedInput("/");
+ }
+ this.unreadChar(this.scanWhitespace());
+ if (! this.checkLiteral(name)) {
+ throw this.expectedInput(name);
+ }
+ if (this.scanWhitespace() != '>') {
+ throw this.expectedInput(">");
+ }
+ }
+
+
+ /**
+ * Resolves an entity. The name of the entity is read from the reader.
+ * The value of the entity is appended to <code>buf</code>.
+ *
+ * @param buf Where to put the entity value.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li>The first & has already been read.
+ * <li><code>buf != null</code>
+ * </ul></dd></dl>
+ */
+ protected void resolveEntity(StringBuffer buf)
+ throws IOException
+ {
+ char ch = '\0';
+ StringBuffer keyBuf = new StringBuffer();
+ for (;;) {
+ ch = this.readChar();
+ if (ch == ';') {
+ break;
+ }
+ keyBuf.append(ch);
+ }
+ String key = keyBuf.toString();
+ if (key.charAt(0) == '#') {
+ try {
+ if (key.charAt(1) == 'x') {
+ ch = (char) Integer.parseInt(key.substring(2), 16);
+ } else {
+ ch = (char) Integer.parseInt(key.substring(1), 10);
+ }
+ } catch (NumberFormatException e) {
+ throw this.unknownEntity(key);
+ }
+ buf.append(ch);
+ } else {
+ char[] value = (char[]) this.entities.get(key);
+ if (value == null) {
+ throw this.unknownEntity(key);
+ }
+ buf.append(value);
+ }
+ }
+
+
+ /**
+ * Pushes a character back to the read-back buffer.
+ *
+ * @param ch The character to push back.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li>The read-back buffer is empty.
+ * <li><code>ch != '\0'</code>
+ * </ul></dd></dl>
+ */
+ protected void unreadChar(char ch)
+ {
+ this.charReadTooMuch = ch;
+ }
+
+
+ /**
+ * Creates a parse exception for when an invalid valueset is given to
+ * a method.
+ *
+ * @param name The name of the entity.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * </ul></dd></dl>
+ */
+ protected XMLParseException invalidValueSet(String name)
+ {
+ String msg = "Invalid value set (entity name = \"" + name + "\")";
+ return new XMLParseException(this.getName(), this.parserLineNr, msg);
+ }
+
+
+ /**
+ * Creates a parse exception for when an invalid value is given to a
+ * method.
+ *
+ * @param name The name of the entity.
+ * @param value The value of the entity.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>value != null</code>
+ * </ul></dd></dl>
+ */
+ protected XMLParseException invalidValue(String name,
+ String value)
+ {
+ String msg = "Attribute \"" + name + "\" does not contain a valid "
+ + "value (\"" + value + "\")";
+ return new XMLParseException(this.getName(), this.parserLineNr, msg);
+ }
+
+
+ /**
+ * Creates a parse exception for when the end of the data input has been
+ * reached.
+ */
+ protected XMLParseException unexpectedEndOfData()
+ {
+ String msg = "Unexpected end of data reached";
+ return new XMLParseException(this.getName(), this.parserLineNr, msg);
+ }
+
+
+ /**
+ * Creates a parse exception for when a syntax error occured.
+ *
+ * @param context The context in which the error occured.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>context != null</code>
+ * <li><code>context.length() > 0</code>
+ * </ul></dd></dl>
+ */
+ protected XMLParseException syntaxError(String context)
+ {
+ String msg = "Syntax error while parsing " + context;
+ return new XMLParseException(this.getName(), this.parserLineNr, msg);
+ }
+
+
+ /**
+ * Creates a parse exception for when the next character read is not
+ * the character that was expected.
+ *
+ * @param charSet The set of characters (in human readable form) that was
+ * expected.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>charSet != null</code>
+ * <li><code>charSet.length() > 0</code>
+ * </ul></dd></dl>
+ */
+ protected XMLParseException expectedInput(String charSet)
+ {
+ String msg = "Expected: " + charSet;
+ return new XMLParseException(this.getName(), this.parserLineNr, msg);
+ }
+
+
+ /**
+ * Creates a parse exception for when an entity could not be resolved.
+ *
+ * @param name The name of the entity.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>name != null</code>
+ * <li><code>name.length() > 0</code>
+ * </ul></dd></dl>
+ */
+ protected XMLParseException unknownEntity(String name)
+ {
+ String msg = "Unknown or invalid entity: &" + name + ";";
+ return new XMLParseException(this.getName(), this.parserLineNr, msg);
+ }
+
+}
diff --git a/laf-plugin/src/main/java/org/pushingpixels/lafplugin/XMLParseException.java b/laf-plugin/src/main/java/org/pushingpixels/lafplugin/XMLParseException.java
new file mode 100644
index 0000000..9bd4d91
--- /dev/null
+++ b/laf-plugin/src/main/java/org/pushingpixels/lafplugin/XMLParseException.java
@@ -0,0 +1,130 @@
+/* XMLParseException.java
+ *
+ * $Revision: 39 $
+ * $Date: 2009-10-14 19:49:29 -0700 (Wed, 14 Oct 2009) $
+ * $Name$
+ *
+ * This file is part of NanoXML 2 Lite.
+ * Copyright (C) 2000-2002 Marc De Scheemaecker, All Rights Reserved.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the
+ * use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software in
+ * a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ *
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ *
+ * 3. This notice may not be removed or altered from any source distribution.
+ *****************************************************************************/
+
+
+package org.pushingpixels.lafplugin;
+
+
+/**
+ * An XMLParseException is thrown when an error occures while parsing an XML
+ * string.
+ * <P>
+ * $Revision: 39 $<BR>
+ * $Date: 2009-10-14 19:49:29 -0700 (Wed, 14 Oct 2009) $<P>
+ *
+ * see nanoxml.XMLElement
+ *
+ * @author Marc De Scheemaecker
+ * @version $Name$, $Revision: 39 $
+ */
+public class XMLParseException
+ extends RuntimeException
+{
+
+ /**
+ * Indicates that no line number has been associated with this exception.
+ */
+ public static final int NO_LINE = -1;
+
+
+ /**
+ * The line number in the source code where the error occurred, or
+ * <code>NO_LINE</code> if the line number is unknown.
+ *
+ * <dl><dt><b>Invariants:</b></dt><dd>
+ * <ul><li><code>lineNr > 0 || lineNr == NO_LINE</code>
+ * </ul></dd></dl>
+ */
+ private int lineNr;
+
+
+ /**
+ * Creates an exception.
+ *
+ * @param name The name of the element where the error is located.
+ * @param message A message describing what went wrong.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>message != null</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>getLineNr() => NO_LINE
+ * </ul></dd></dl><dl>
+ */
+ public XMLParseException(String name,
+ String message)
+ {
+ super("XML Parse Exception during parsing of "
+ + ((name == null) ? "the XML definition"
+ : ("a " + name + " element"))
+ + ": " + message);
+ this.lineNr = XMLParseException.NO_LINE;
+ }
+
+
+ /**
+ * Creates an exception.
+ *
+ * @param name The name of the element where the error is located.
+ * @param lineNr The number of the line in the input.
+ * @param message A message describing what went wrong.
+ *
+ * </dl><dl><dt><b>Preconditions:</b></dt><dd>
+ * <ul><li><code>message != null</code>
+ * <li><code>lineNr > 0</code>
+ * </ul></dd></dl>
+ *
+ * <dl><dt><b>Postconditions:</b></dt><dd>
+ * <ul><li>getLineNr() => lineNr
+ * </ul></dd></dl><dl>
+ */
+ public XMLParseException(String name,
+ int lineNr,
+ String message)
+ {
+ super("XML Parse Exception during parsing of "
+ + ((name == null) ? "the XML definition"
+ : ("a " + name + " element"))
+ + " at line " + lineNr + ": " + message);
+ this.lineNr = lineNr;
+ }
+
+
+ /**
+ * Where the error occurred, or <code>NO_LINE</code> if the line number is
+ * unknown.
+ *
+ * see nanoxml.XMLParseException#NO_LINE
+ */
+ public int getLineNr()
+ {
+ return this.lineNr;
+ }
+
+}
diff --git a/laf-plugin/src/main/resources/org/pushingpixels/lafplugin/LafPlugin.license b/laf-plugin/src/main/resources/org/pushingpixels/lafplugin/LafPlugin.license
new file mode 100644
index 0000000..8e48e22
--- /dev/null
+++ b/laf-plugin/src/main/resources/org/pushingpixels/lafplugin/LafPlugin.license
@@ -0,0 +1,26 @@
+Copyright (c) 2005-2010, Kirill Grouchnikov and contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+ * Neither the name of Laf-Plugin Kirill Grouchnikov nor
+the names of its contributors may be used to endorse or promote products
+derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/laf-plugin/src/main/resources/org/pushingpixels/lafplugin/NanoXML.license b/laf-plugin/src/main/resources/org/pushingpixels/lafplugin/NanoXML.license
new file mode 100644
index 0000000..dee31e9
--- /dev/null
+++ b/laf-plugin/src/main/resources/org/pushingpixels/lafplugin/NanoXML.license
@@ -0,0 +1,21 @@
+ NanoXML is distributed under the zlib/libpng license, which is OSS
+ (Open Source Software) compliant.
+
+Copyright �2000-2002 Marc De Scheemaecker, All Rights Reserved.
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the author be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented;
+ you must not claim that you wrote the original software.
+ If you use this software in a product, an acknowledgment
+ in the product documentation would be appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such,
+ and must not be misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any
+ source distribution.
diff --git a/laf-widget/3rd/augment-infonode.bat b/laf-widget/3rd/augment-infonode.bat
new file mode 100644
index 0000000..22ff7ae
--- /dev/null
+++ b/laf-widget/3rd/augment-infonode.bat
@@ -0,0 +1,33 @@
+mkdir TMP
+
+cd TMP
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/jar" xf ../ilf-gpl.jar
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.LafMainClassAugmenter -verbose -main net.infonode.gui.laf.InfoNodeLookAndFeel -delegates ButtonUI;CheckBoxUI;CheckBoxMenuItemUI;ColorChooserUI;DesktopIconUI;DesktopPaneUI;EditorPaneUI;FormattedTextFieldUI;LabelUI;ListUI;MenuUI;MenuBarUI;OptionPaneUI;PanelUI;PasswordFieldUI;PopupMenuUI;PopupMenuSeparatorUI;ProgressBarUI;RadioButtonUI;RadioButtonMenuItemUI; [...]
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.UiDelegateAugmenter -verbose -pattern .*UI\u002Eclass .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.IconGhostingAugmenter -verbose -class net.infonode.gui.laf.__Forwarding__ButtonUI -method paintIcon .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.IconGhostingAugmenter -verbose -class net.infonode.gui.laf.__Forwarding__ToggleButtonUI -method paintIcon .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.infonode.gui.laf.__Forwarding__PanelUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.infonode.gui.laf.__Forwarding__MenuBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.infonode.gui.laf.__Forwarding__MenuUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.infonode.gui.laf.__Forwarding__ScrollBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.infonode.gui.laf.__Forwarding__ToolBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.infonode.gui.laf.__Forwarding__DesktopPaneUI .
+
+
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/jar" cfm ../augmented/ilf-gpl.jar META-INF/manifest.mf .
+
+cd ..
+
+rmdir TMP /s /q
\ No newline at end of file
diff --git a/laf-widget/3rd/augment-liquid.bat b/laf-widget/3rd/augment-liquid.bat
new file mode 100644
index 0000000..842480c
--- /dev/null
+++ b/laf-widget/3rd/augment-liquid.bat
@@ -0,0 +1,31 @@
+mkdir TMP
+
+cd TMP
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/jar" xf ../liquidlnf.jar
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.LafMainClassAugmenter -verbose -main com.birosoft.liquid.LiquidLookAndFeel -delegates ColorChooserUI;DesktopIconUI;DesktopPaneUI;EditorPaneUI;LabelUI;PopupMenuUI;TextAreaUI;TextPaneUI;ToolBarSeparatorUI;ToolTipUI;TreeUI;ViewportUI; -dir .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.UiDelegateAugmenter -verbose -pattern .*UI\u002Eclass .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.IconGhostingAugmenter -verbose -class com.birosoft.liquid.LiquidButtonUI -method paintIcon .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.IconGhostingAugmenter -verbose -class com.birosoft.liquid.LiquidToggleButtonUI -method paintIcon .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.birosoft.liquid.LiquidPanelUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.birosoft.liquid.LiquidMenuBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.birosoft.liquid.LiquidMenuUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.birosoft.liquid.LiquidScrollBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.birosoft.liquid.LiquidToolBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.birosoft.liquid.__Forwarding__DesktopPaneUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/jar" cfm ../augmented/liquidlnf.jar META-INF/manifest.mf .
+
+cd ..
+
+rmdir TMP /s /q
\ No newline at end of file
diff --git a/laf-widget/3rd/augment-looks.bat b/laf-widget/3rd/augment-looks.bat
new file mode 100644
index 0000000..0dddb19
--- /dev/null
+++ b/laf-widget/3rd/augment-looks.bat
@@ -0,0 +1,31 @@
+mkdir TMP
+
+cd TMP
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/jar" xf ../looks-2.1.3.jar
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../looks-2.1.3.jar;../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.LafMainClassAugmenter -verbose -main com.jgoodies.looks.plastic.PlasticXPLookAndFeel -delegates CheckBoxUI;ColorChooserUI;DesktopIconUI;DesktopPaneUI;EditorPaneUI;FormattedTextFieldUI;LabelUI;ListUI;PanelUI;ProgressBarUI;RadioButtonUI;RootPaneUI;SliderUI;TableUI;TableHeaderUI;TextFieldUI;TextPaneUI;ToolTipUI;ViewportUI; -dir .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.UiDelegateAugmenter -verbose -pattern .*UI\u002Eclass .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.IconGhostingAugmenter -verbose -class com.jgoodies.looks.plastic.PlasticButtonUI -method paintIcon .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.IconGhostingAugmenter -verbose -class com.jgoodies.looks.plastic.PlasticToggleButtonUI -method paintIcon .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.jgoodies.looks.plastic.__Forwarding__PanelUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.jgoodies.looks.plastic.PlasticMenuBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.jgoodies.looks.plastic.PlasticMenuUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.jgoodies.looks.plastic.PlasticScrollBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.jgoodies.looks.plastic.PlasticToolBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.jgoodies.looks.plastic.__Forwarding__DesktopPaneUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/jar" cfm ../augmented/looks-2.1.3.jar META-INF/manifest.mf .
+
+cd ..
+
+rmdir TMP /s /q
\ No newline at end of file
diff --git a/laf-widget/3rd/augment-napkin.bat b/laf-widget/3rd/augment-napkin.bat
new file mode 100644
index 0000000..27e87ef
--- /dev/null
+++ b/laf-widget/3rd/augment-napkin.bat
@@ -0,0 +1,29 @@
+mkdir TMP
+
+cd TMP
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/jar" xf ../napkinlaf.jar
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.UiDelegateAugmenter -verbose -pattern .*UI\u002Eclass .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.IconGhostingAugmenter -verbose -class net.sourceforge.napkinlaf.NapkinButtonUI -method paintIcon .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.IconGhostingAugmenter -verbose -class net.sourceforge.napkinlaf.NapkinToggleButtonUI -method paintIcon .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.sourceforge.napkinlaf.NapkinPanelUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.sourceforge.napkinlaf.NapkinMenuBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.sourceforge.napkinlaf.NapkinMenuUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.sourceforge.napkinlaf.NapkinScrollBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.sourceforge.napkinlaf.NapkinToolBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.sourceforge.napkinlaf.NapkinDesktopPaneUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/jar" cfm ../augmented/napkinlaf.jar META-INF/manifest.mf .
+
+cd ..
+
+rmdir TMP /s /q
\ No newline at end of file
diff --git a/laf-widget/3rd/augment-pagosoft.bat b/laf-widget/3rd/augment-pagosoft.bat
new file mode 100644
index 0000000..fbff3c4
--- /dev/null
+++ b/laf-widget/3rd/augment-pagosoft.bat
@@ -0,0 +1,31 @@
+mkdir TMP
+
+cd TMP
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/jar" xf ../PgsLookAndFeel.jar
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.LafMainClassAugmenter -verbose -main com.pagosoft.plaf.PgsLookAndFeel -delegates ColorChooserUI;DesktopIconUI;DesktopPaneUI;InternalFrameUI;ListUI;OptionPaneUI;PanelUI;PopupMenuUI;PopupMenuSeparatorUI;RootPaneUI;SeparatorUI;SliderUI;SpinnerUI;TableUI;TableHeaderUI;TextPaneUI;ToolBarSeparatorUI;TreeUI;ViewportUI -dir .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.UiDelegateAugmenter -verbose -pattern .*UI\u002Eclass .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.IconGhostingAugmenter -verbose -class com.pagosoft.plaf.PgsButtonUI -method paintIcon .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.IconGhostingAugmenter -verbose -class com.pagosoft.plaf.PgsToggleButtonUI -method paintIcon .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.pagosoft.plaf.__Forwarding__PanelUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.pagosoft.plaf.PgsMenuBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.pagosoft.plaf.PgsMenuUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.pagosoft.plaf.PgsScrollBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.pagosoft.plaf.PgsToolBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class com.pagosoft.plaf.__Forwarding__DesktopPaneUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/jar" cfm ../augmented/PgsLookAndFeel.jar META-INF/manifest.mf .
+
+cd ..
+
+rmdir TMP /s /q
\ No newline at end of file
diff --git a/laf-widget/3rd/augment-squareness.bat b/laf-widget/3rd/augment-squareness.bat
new file mode 100644
index 0000000..a26d74d
--- /dev/null
+++ b/laf-widget/3rd/augment-squareness.bat
@@ -0,0 +1,31 @@
+mkdir TMP
+
+cd TMP
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/jar" xf ../squareness.jar
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.LafMainClassAugmenter -verbose -main net.beeger.squareness.SquarenessLookAndFeel -delegates CheckBoxMenuItemUI;ColorChooserUI;DesktopIconUI;DesktopPaneUI;EditorPaneUI;FormattedTextFieldUI;LabelUI;ListUI;MenuUI;MenuBarUI;MenuItemUI;OptionPaneUI;PanelUI;PasswordFieldUI;PopupMenuUI;PopupMenuSeparatorUI;RadioButtonMenuItemUI;ScrollBarUI;SeparatorUI;TableUI;Ta [...]
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar org.jvnet.lafwidget.ant.UiDelegateAugmenter -verbose -pattern .*UI\u002Eclass .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.IconGhostingAugmenter -verbose -class net.beeger.squareness.delegate.SquarenessButtonUI -method paintIcon .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.IconGhostingAugmenter -verbose -class net.beeger.squareness.delegate.SquarenessToggleButtonUI -method paintIcon .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.beeger.squareness.__Forwarding__PanelUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.beeger.squareness.__Forwarding__MenuBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.beeger.squareness.__Forwarding__MenuUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.beeger.squareness.__Forwarding__ScrollBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.beeger.squareness.delegate.SquarenessToolBarUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -classpath ../../drop/laf-widget.jar;../../lib/asm-all-2.2.2.jar;../../lib/ant.jar org.jvnet.lafwidget.ant.ContainerGhostingAugmenter -verbose -class net.beeger.squareness.__Forwarding__DesktopPaneUI .
+
+"C:\Program Files\Java\jdk1.5.0_11\/bin/jar" cfm ../augmented/squareness.jar META-INF/manifest.mf .
+
+cd ..
+
+rmdir TMP /s /q
\ No newline at end of file
diff --git a/laf-widget/3rd/run-ghost-infonode.bat b/laf-widget/3rd/run-ghost-infonode.bat
new file mode 100644
index 0000000..1fab2e5
--- /dev/null
+++ b/laf-widget/3rd/run-ghost-infonode.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=net.infonode.gui.laf.InfoNodeLookAndFeel -cp ../drop/laf-widget-tst.jar;../drop/laf-widget.jar;augmented/ilf-gpl.jar test.ButtonRolloverDemo -highcontrast
\ No newline at end of file
diff --git a/laf-widget/3rd/run-ghost-liquid.bat b/laf-widget/3rd/run-ghost-liquid.bat
new file mode 100644
index 0000000..f6fad39
--- /dev/null
+++ b/laf-widget/3rd/run-ghost-liquid.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=com.birosoft.liquid.LiquidLookAndFeel -cp ../drop/laf-widget-tst.jar;../drop/laf-widget.jar;augmented/liquidlnf.jar test.ButtonRolloverDemo -contrast
\ No newline at end of file
diff --git a/laf-widget/3rd/run-ghost-looks.bat b/laf-widget/3rd/run-ghost-looks.bat
new file mode 100644
index 0000000..9c4ebdc
--- /dev/null
+++ b/laf-widget/3rd/run-ghost-looks.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=com.jgoodies.looks.plastic.PlasticXPLookAndFeel -cp ../drop/laf-widget-tst.jar;../drop/laf-widget.jar;augmented/looks-2.1.3.jar test.ButtonRolloverDemo -highcontrast
\ No newline at end of file
diff --git a/laf-widget/3rd/run-ghost-napkin.bat b/laf-widget/3rd/run-ghost-napkin.bat
new file mode 100644
index 0000000..2c6494d
--- /dev/null
+++ b/laf-widget/3rd/run-ghost-napkin.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=net.sourceforge.napkinlaf.NapkinLookAndFeel -cp ../drop/laf-widget-tst.jar;../drop/laf-widget.jar;augmented/napkinlaf.jar test.ButtonRolloverDemo -highcontrast
\ No newline at end of file
diff --git a/laf-widget/3rd/run-ghost-pagosoft.bat b/laf-widget/3rd/run-ghost-pagosoft.bat
new file mode 100644
index 0000000..109ad69
--- /dev/null
+++ b/laf-widget/3rd/run-ghost-pagosoft.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=com.pagosoft.plaf.PgsLookAndFeel -cp ../drop/laf-widget-tst.jar;../drop/laf-widget.jar;augmented/PgsLookAndFeel.jar test.ButtonRolloverDemo -highcontrast
\ No newline at end of file
diff --git a/laf-widget/3rd/run-ghost-squareness.bat b/laf-widget/3rd/run-ghost-squareness.bat
new file mode 100644
index 0000000..0c40dd5
--- /dev/null
+++ b/laf-widget/3rd/run-ghost-squareness.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=net.beeger.squareness.SquarenessLookAndFeel -cp ../drop/laf-widget-tst.jar;../drop/laf-widget.jar;augmented/squareness.jar test.ButtonRolloverDemo -highcontrast
\ No newline at end of file
diff --git a/laf-widget/3rd/run-ghost-substance.bat b/laf-widget/3rd/run-ghost-substance.bat
new file mode 100644
index 0000000..b1cdf48
--- /dev/null
+++ b/laf-widget/3rd/run-ghost-substance.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=org.jvnet.substance.skin.SubstanceModerateLookAndFeel -cp ../drop/laf-widget-tst.jar;augmented/substance.jar test.ButtonRolloverDemo -highcontrast
\ No newline at end of file
diff --git a/laf-widget/3rd/run-infonode.bat b/laf-widget/3rd/run-infonode.bat
new file mode 100644
index 0000000..4909d05
--- /dev/null
+++ b/laf-widget/3rd/run-infonode.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=net.infonode.gui.laf.InfoNodeLookAndFeel -cp ../drop/laf-widget-tst.jar;../drop/laf-widget.jar;augmented/ilf-gpl.jar test.check.SampleFrame
\ No newline at end of file
diff --git a/laf-widget/3rd/run-liquid.bat b/laf-widget/3rd/run-liquid.bat
new file mode 100644
index 0000000..92c010b
--- /dev/null
+++ b/laf-widget/3rd/run-liquid.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=com.birosoft.liquid.LiquidLookAndFeel -cp ../drop/laf-widget-tst.jar;../drop/laf-widget.jar;augmented/liquidlnf.jar test.check.SampleFrame
\ No newline at end of file
diff --git a/laf-widget/3rd/run-looks.bat b/laf-widget/3rd/run-looks.bat
new file mode 100644
index 0000000..dbf2289
--- /dev/null
+++ b/laf-widget/3rd/run-looks.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=com.jgoodies.looks.plastic.PlasticXPLookAndFeel -cp ../drop/laf-widget-tst.jar;../drop/laf-widget.jar;augmented/looks-2.1.3.jar test.check.SampleFrame
\ No newline at end of file
diff --git a/laf-widget/3rd/run-napkin.bat b/laf-widget/3rd/run-napkin.bat
new file mode 100644
index 0000000..87e2855
--- /dev/null
+++ b/laf-widget/3rd/run-napkin.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=net.sourceforge.napkinlaf.NapkinLookAndFeel -cp ../drop/laf-widget-tst.jar;../drop/laf-widget.jar;augmented/napkinlaf.jar test.check.SampleFrame
\ No newline at end of file
diff --git a/laf-widget/3rd/run-pagosoft.bat b/laf-widget/3rd/run-pagosoft.bat
new file mode 100644
index 0000000..0850954
--- /dev/null
+++ b/laf-widget/3rd/run-pagosoft.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=com.pagosoft.plaf.PgsLookAndFeel -cp ../drop/laf-widget-tst.jar;../drop/laf-widget.jar;augmented/PgsLookAndFeel.jar test.check.SampleFrame
\ No newline at end of file
diff --git a/laf-widget/3rd/run-squareness.bat b/laf-widget/3rd/run-squareness.bat
new file mode 100644
index 0000000..fbeaea9
--- /dev/null
+++ b/laf-widget/3rd/run-squareness.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=net.beeger.squareness.SquarenessLookAndFeel -cp ../drop/laf-widget-tst.jar;../drop/laf-widget.jar;augmented/squareness.jar test.check.SampleFrame
\ No newline at end of file
diff --git a/laf-widget/3rd/run-substance.bat b/laf-widget/3rd/run-substance.bat
new file mode 100644
index 0000000..a1b89b0
--- /dev/null
+++ b/laf-widget/3rd/run-substance.bat
@@ -0,0 +1 @@
+"C:\Program Files\Java\jdk1.5.0_11\/bin/java" -Dswing.defaultlaf=org.jvnet.substance.SubstanceLookAndFeel -cp ../drop/laf-widget-tst.jar;augmented/substance.jar test.check.SampleFrame
\ No newline at end of file
diff --git a/laf-widget/build.gradle b/laf-widget/build.gradle
new file mode 100755
index 0000000..6f5ff9e
--- /dev/null
+++ b/laf-widget/build.gradle
@@ -0,0 +1,63 @@
+dependencies {
+ compile project(":trident")
+ compile group: 'asm', name: 'asm-all', version: '2.2.3'
+ compile group: 'org.apache.ant', name: 'ant', version: '1.7.0'
+}
+
+jar {
+ manifest {
+ attributes(
+ "Laf-Widget-Version": version,
+ "Laf-Widget-VersionName": versionKey,
+ )
+ }
+}
+
+uploadPublished {
+ repositories {
+ mavenDeployer {
+ configurePOM(pom)
+ }
+ }
+}
+
+install {
+ configurePOM(repositories.mavenInstaller.pom)
+}
+
+private def configurePOM(def pom) {
+ configureBasePom(pom)
+ pom.project {
+ name "laf-widget"
+ description "A fork of @kirilcool's laf-widget project"
+ url "http://insubstantial.github.com/insubstantial/laf-widget/"
+ licenses {
+ license {
+ name 'The BSD License'
+ url 'http://www.opensource.org/licenses/bsd-license.php'
+ distribution 'repo'
+ comments 'Covers ASM, BlogOfBug, JibX, LafWidget, and Looks contributions'
+ }
+ license {
+ name 'CC BY-SA 2.5'
+ url 'http://creativecommons.org/licenses/by-sa/2.5/'
+ distribution 'repo'
+ comments 'Covers the TangoIcons'
+ }
+ }
+ }
+ // deal with a gradle bug where transitive=false is not passed into the generated POM
+ pom.whenConfigured {cpom ->
+ cpom.dependencies.each {dep ->
+ switch (dep.artifactId) {
+ case 'trident':
+ dep.classifier = 'swing'
+ break
+ case 'ant':
+ case 'asm-all':
+ dep.scope = 'provided'
+ break;
+ }
+ }
+ }
+}
diff --git a/laf-widget/settings.gradle b/laf-widget/settings.gradle
new file mode 100755
index 0000000..5e18687
--- /dev/null
+++ b/laf-widget/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'laf-widget'
\ No newline at end of file
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/LAFAdapter.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LAFAdapter.java
new file mode 100644
index 0000000..53526f9
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LAFAdapter.java
@@ -0,0 +1,244 @@
+package org.pushingpixels.lafwidget;
+
+import java.awt.EventQueue;
+import java.util.Set;
+import javax.swing.*;
+import java.beans.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.lafwidget.*;
+
+public class LAFAdapter {
+ /**The listener for global LAF changes.*/
+ private static ReinitListener reinitListener;
+ /**The listener for component LAF updates.*/
+ private static InternalUIListener internalListener;
+ /**Have we already initialised?*/
+ private static boolean initialised=false;
+ /**The table of delegate classes for the current LAF.*/
+ private static UIDefaults uiDelegates;
+ /**The list of all UI classes.
+ *Taken from https://laf-widget.dev.java.net/docs/all-component-ui-ids.txt
+ */
+ private final static String[] UI_CLASSNAMES={
+ "ButtonUI","CheckBoxUI","CheckBoxMenuItemUI","ColorChooserUI","ComboBoxUI","DesktopIconUI",
+ "DesktopPaneUI","EditorPaneUI","FormattedTextFieldUI","InternalFrameUI","LabelUI","ListUI","MenuUI",
+ "MenuBarUI","MenuItemUI","OptionPaneUI","PanelUI","PasswordFieldUI","PopupMenuUI","PopupMenuSeparatorUI",
+ "ProgressBarUI","RadioButtonUI","RadioButtonMenuItemUI","RootPaneUI","ScrollBarUI","ScrollPaneUI",
+ "SeparatorUI","SliderUI","SpinnerUI","SplitPaneUI","TabbedPaneUI","TableUI","TableHeaderUI","TextAreaUI",
+ "TextFieldUI","TextPaneUI","ToggleButtonUI","ToolBarUI","ToolBarSeparatorUI","ToolTipUI","TreeUI","ViewportUI"};
+ private final static String LAF_PROPERTY = "Widgeted_LAFS";
+
+ /**Returns the UI as normal, but intercepts the call, so a
+ * listener can be attached. This is called internally from Swing code
+ * to create the UI delegate for a component.
+ *<b>THIS METHOD SHOULDN'T BE CALLED FROM USER CODE!</b>
+ */
+ public static ComponentUI createUI(JComponent c) {
+ if (c == null || initialised == false) //something isn't right -> bail
+ return null;
+ //Now we use the same discovery mechanism to find the real createUI method
+ ComponentUI uiObject = uiDelegates.getUI(c);
+ //System.out.println("Creating UI: "+c.getClass()+" "+Integer.toHexString(System.identityHashCode(c)));
+ uninstallLafWidgets(c);
+ //here we need to check we don't add a second listener. This happens
+ //if createUI gets called a second time, as when creating JDesktopIcons
+ if (!isPropertyListening(c))
+ c.addPropertyChangeListener("UI",internalListener);
+ return uiObject; //return the actual UI delegate
+ }
+
+ /**Tests if the property listener has already been attached to a component
+ *@param c the component to check
+ *@return true if the listener is already present
+ */
+ private static boolean isPropertyListening(JComponent c) {
+ PropertyChangeListener[] pc=c.getPropertyChangeListeners();
+ if (pc.length==0) //common-case
+ return false;
+ for (int i=0,ilen=pc.length;i<ilen;i++) {
+ //Property listeners are usually proxied but test explicitly just in case
+ if (pc[i] instanceof PropertyChangeListenerProxy &&
+ ((PropertyChangeListenerProxy)pc[i]).getListener()==internalListener)
+ return true;
+ else if (pc[i]==internalListener)
+ return true;
+ }
+ return false;
+ }
+
+ private static class InternalUIListener implements PropertyChangeListener {
+ /**UI can change at any point, so we need to listen for these
+ * events. This property fires AFTER the UI change.
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ JComponent c=(JComponent)evt.getSource();
+ // Remove old listeners that was installed when createUI was called
+ c.removePropertyChangeListener("UI",internalListener);
+ installLafWidgets(c); //here we do the install on the LAF
+ }
+ }
+
+ /**Installs the available LAF-Widgets onto the given JComponent
+ *and adds a mapping to the set of widgeted components if any were found.
+ *If the LAF indicates it is self-widgeting then nothing is done.
+ *@param c the component to widgetise
+ */
+ private static void installLafWidgets(JComponent c) {
+ //test if this LAF uses needs LAFWidget integration
+ if (LafWidgetRepository.getRepository().getLafSupport()
+ .getClass().equals(LafWidgetSupport.class)) {
+ Set<LafWidget> lafWidgets=LafWidgetRepository.getRepository().getMatchingWidgets(c);
+ if (lafWidgets.size()>0) {//if a new UI has been set
+ for (LafWidget lw : lafWidgets) {
+ lw.installUI();
+ lw.installComponents();
+ lw.installDefaults();
+ lw.installListeners();
+ }
+ c.putClientProperty(LAF_PROPERTY,lafWidgets); //stash the list of installed widgets
+ }
+ }
+ }
+
+ /**Removes any installed widgets from the component shown. If a mapping
+ *exists which indicates this component had widgets installed then they
+ *are removed here and the mapping also removed.
+ *@param c the component to uninstall widgets from
+ */
+ private static void uninstallLafWidgets(JComponent c) {
+ //if there were previously widgets installed, then uninstall them
+ Set<LafWidget> lafWidgets=(Set<LafWidget>)c.getClientProperty(LAF_PROPERTY);
+ if (lafWidgets!=null) {
+ for (LafWidget lw : lafWidgets) {
+ lw.uninstallListeners();
+ lw.uninstallDefaults();
+ lw.uninstallComponents();
+ lw.uninstallUI();
+ }
+ c.putClientProperty(LAF_PROPERTY,null); //remove widget set
+ }
+ }
+
+ /**This initialises the Widgeting system. All future components created will
+ *be widgeted. This code maybe called from any thread and ensures that the
+ *actual initialisation is done on the Event Thread. If called from another thread,
+ *this method will block until the initialisation is complete.
+ */
+ public static void startWidget() {
+ widget(true);
+ }
+
+ /**This tears-down the widgeting system. Calling this method prevents any
+ *further components being widgetised. Any existing components with widgets
+ *will continue to have them. All system hooks will be removed and the widgeting
+ *system will stop attaching to new components. This method is safe to call
+ *from any thread, as the work will be done on the Event Thread. If called from
+ *another thread, this method will block until the initialisation is complete.
+ */
+ public static void stopWidget() {
+ widget(false);
+ }
+
+ /**Private helper to start or stop the widgeting. EDT checks present here.
+ *@param enable whether to start or stop the widgeting
+ */
+ private static void widget(boolean enable) {
+ Init init = new Init(enable);
+ if (EventQueue.isDispatchThread()) {
+ init.run();
+ } else {// This code must run on the EDT for data visibility reasons
+ try {
+ EventQueue.invokeAndWait(init);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**Inserts all the substitute LAF pairs into the UIDefaults table. This also
+ *places a LAF change listener on the UIManager. This is to cause a re-initialisation
+ *when the LAF changes so new components created after a global LAF change will
+ *still get widgets.
+ */
+ private static class Init implements Runnable {
+ /**Sets whether we should be setting up or tearing down the widgeting.*/
+ private final boolean enable; //final so it doesn't matter where this initialises from (JSR-133)
+
+ private Init(final boolean enable) {
+ this.enable=enable;
+ }
+
+ @Override
+ public void run() {
+ if (!EventQueue.isDispatchThread())
+ throw new IllegalStateException("This must be run on the EDT");
+ try { //do the setup process or removal process
+ if (enable)
+ setup();
+ else
+ tearDown();
+ } catch (Exception e) {
+ initialised=false;
+ uiDelegates=null;
+ internalListener=null;
+ reinitListener=null;
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void setup() throws Exception {
+ //check we don't initialise twice
+ if (initialised)
+ return;
+ //We use the LAF defaults table so we don't mess with the developer
+ //table
+ reinitListener = new ReinitListener();
+ internalListener = new InternalUIListener();
+ uiDelegates=new UIDefaults(); //stores the actual UI delegates
+ UIDefaults defaults = UIManager.getLookAndFeelDefaults();
+ //Store the class for each delegate locally and replace the Swing delegate
+ for (String uiClassID : UI_CLASSNAMES) {
+ uiDelegates.put(uiClassID,defaults.getString(uiClassID));
+ defaults.put(uiClassID,LAFAdapter.class.getName());
+ }
+ //listen for global LAF changes
+ UIManager.addPropertyChangeListener(reinitListener);
+ initialised=true;
+ }
+
+ public static void tearDown() throws Exception {
+ if (!initialised)
+ return;
+ UIManager.removePropertyChangeListener(reinitListener);
+ //reset the current UI which will overwrite all our values
+ UIManager.setLookAndFeel(UIManager.getLookAndFeel());
+ uiDelegates=null;
+ reinitListener=null;
+ internalListener=null;
+ initialised=false;
+ }
+ }
+
+ /**
+ * Listens for Look and Feel changes and re-initialises the
+ * class. This fires from UIManager after a LAF change.
+ */
+ private static class ReinitListener implements PropertyChangeListener {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("lookAndFeel".equals(evt.getPropertyName())) {
+ // The look and feel was changed so we need to re-insert
+ // our hook into the new UIDefaults map, first de-initialise
+ // Don't want to call tearDown() as we're already in a LAF change.
+ UIManager.removePropertyChangeListener(reinitListener);
+ initialised=false;
+ uiDelegates=null;
+ reinitListener=null;
+ internalListener=null;
+ startWidget();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidget.java
new file mode 100644
index 0000000..4e55c5b
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidget.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget;
+
+import java.util.Locale;
+
+import javax.swing.JComponent;
+import javax.swing.JPasswordField;
+import javax.swing.UIManager;
+
+import org.pushingpixels.lafwidget.preview.DefaultPreviewPainter;
+import org.pushingpixels.lafwidget.preview.PreviewPainter;
+import org.pushingpixels.lafwidget.tabbed.DefaultTabPreviewPainter;
+import org.pushingpixels.lafwidget.tabbed.TabHoverPreviewWidget;
+import org.pushingpixels.lafwidget.tabbed.TabOverviewDialogWidget;
+import org.pushingpixels.lafwidget.tabbed.TabPreviewPainter;
+import org.pushingpixels.lafwidget.text.LockBorderWidget;
+import org.pushingpixels.lafwidget.text.PasswordStrengthChecker;
+
+/**
+ * Interface for LAF widgets (behavioural traits).
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface LafWidget<T extends JComponent> {
+ /**
+ * <p>
+ * Client property name for specifying that the {@link LockBorderWidget}
+ * should put a lock icon. This property can be set either on a single
+ * component or globally on {@link UIManager}. The value in both cases
+ * should be either {@link Boolean#TRUE} or {@link Boolean#FALSE}.
+ * </p>
+ *
+ * @since 3.2
+ */
+ public final static String HAS_LOCK_ICON = "lafwidgets.hasLockIcon";
+
+ /**
+ * <p>
+ * Client property name for specifying the preview painter for tabbed pane.
+ * This property can be set on a single tabbed pane. The value should be an
+ * instance of {@link TabPreviewPainter}. Default implementation of this
+ * {@link DefaultTabPreviewPainter}. Tabbed panes that have associated
+ * preview painters, have two widgets installed:
+ * </p>
+ * <ul>
+ * <li>Tab overview dialog from {@link TabOverviewDialogWidget}.</li>
+ * <li>Tab hover preview from {@link TabHoverPreviewWidget}.</li>
+ * </ul>
+ *
+ * <p>
+ * Here is an example of tabbed pane with default tab preview painter
+ * installed:
+ * </p>
+ *
+ * <code>
+ * JTabbedPane jtp = new JTabbedPane();<br>
+ * jtp.putClientProperty(LafWidget.TABBED_PANE_PREVIEW_PAINTER,<br>
+ * new DefaultTabPreviewPainter());
+ * </code>
+ */
+ public final static String TABBED_PANE_PREVIEW_PAINTER = "lafwidgets.tabbedpanePreviewPainter";
+
+ /**
+ * <p>
+ * Client property name for specifying the preview painter for a component.
+ * This property can be set either on a component or globally on
+ * {@link UIManager}. The value in both cases should be an instance of
+ * {@link PreviewPainter}. Default implementation is available in the
+ * {@link DefaultPreviewPainter}.
+ * </p>
+ *
+ * <p>
+ * Here is an example of a scroll pane with default preview painter
+ * installed on the internal component:
+ * </p>
+ *
+ * <code>
+ * JPanel myPanel = new JPanel();<br>
+ * myPanel.putClientProperty(LafWidget.PANE_PREVIEW_PAINTER,<br>
+ * new DefaultPreviewPainter());<br>
+ * JScrollPane jsp = new JScrollPane(myPanel);
+ * </code>
+ */
+ public final static String COMPONENT_PREVIEW_PAINTER = "lafwidgets.componentPreviewPainter";
+
+ /**
+ * <p>
+ * Client property name for specifying password strength checker for a
+ * password field. The value should be an instance of
+ * {@link PasswordStrengthChecker}, otherwise will be ignored. This property
+ * must be set on a specific {@link JPasswordField}. Here is an example:
+ * </p>
+ *
+ * <code>
+ * JPasswordField jpf = new JPasswordField("password", 10);<br>
+ * jpf.putClientProperty(LafWidget.PASSWORD_STRENGTH_CHECKER,<br>
+ * new PasswordStrengthChecker() {<br>
+ * public PasswordStrength getStrength(char[] password) {<br>
+ * if (password == null)<br>
+ * return PasswordStrength.WEAK;<br>
+ * int length = password.length;<br>
+ * if (length < 3)<br>
+ * return PasswordStrength.WEAK;<br>
+ * if (length < 6)<br>
+ * return PasswordStrength.MEDIUM;<br>
+ * return PasswordStrength.STRONG;<br>
+ * }<br>
+ * <br>
+ * public String getDescription(PasswordStrength strength) {<br>
+ * if (strength == PasswordStrength.WEAK)<br>
+ * return "<html>This password is <b>way</b> too weak</html>";<br>
+ * if (strength == PasswordStrength.MEDIUM)<br>
+ * return "<html>Come on, you can do<br> a little better than that</html>";<br>
+ * if (strength == PasswordStrength.STRONG)<br>
+ * return "OK";<br>
+ * return null;<br>
+ * }<br>
+ * });
+ * </code>
+ */
+ public final static String PASSWORD_STRENGTH_CHECKER = "lafwidgets.passwordStrengthChecker";
+
+ /**
+ * <p>
+ * Client property name for specifying that the text component contents
+ * should be selected on focus gain. This property can be set either on a
+ * single text component or globally on {@link UIManager}. The value in both
+ * cases should be either {@link Boolean#TRUE} or {@link Boolean#FALSE}.
+ * </p>
+ *
+ * <p>
+ * Here is an example of globally set property (all text components that
+ * don't specify {@link Boolean#FALSE} as a client property will have the
+ * "select all on focus gain" behaviour):
+ * </p>
+ *
+ * <code>
+ * UIManager.put(LafWidget.TEXT_SELECT_ON_FOCUS, Boolean.TRUE);
+ * </code>
+ */
+ public final static String TEXT_SELECT_ON_FOCUS = "lafwidgets.textSelectAllOnFocus";
+
+ /**
+ * <p>
+ * Client property name for specifying that the text component contents
+ * should flip selection on ESCAPE key press. This property can be set on a
+ * single text component. The value should be either {@link Boolean#TRUE} or
+ * {@link Boolean#FALSE}.
+ * </p>
+ *
+ * <p>
+ * Here is an example of this property set on this specific text field:
+ * </p>
+ *
+ * <code>
+ * myTextField.put(LafWidget.TEXT_FLIP_SELECT_ON_ESCAPE, Boolean.TRUE);
+ * </code>
+ */
+ public final static String TEXT_FLIP_SELECT_ON_ESCAPE = "lafwidgets.textFlipSelectOnEscape";
+
+ /**
+ * <p>
+ * Client property name for specifying that the text component should have
+ * the edit context menu (with Cut / Copy / Paste / ... menu items). This
+ * property can be set either on a single text component or globally on
+ * {@link UIManager}. The value in both cases should be either
+ * {@link Boolean#TRUE} or {@link Boolean#FALSE}.
+ * </p>
+ *
+ * <p>
+ * Here is an example of globally set property (all text components that
+ * don't specify {@link Boolean#FALSE} as a client property will have the
+ * the edit context menu):
+ * </p>
+ *
+ * <code>
+ * UIManager.put(LafWidget.TEXT_EDIT_CONTEXT_MENU, Boolean.TRUE);
+ * </code>
+ */
+ public final static String TEXT_EDIT_CONTEXT_MENU = "lafwidgets.textEditContextMenu";
+
+ /**
+ * Client property name for specifying that the tree component should have
+ * automatic drag and drop support. This property can be set either on a
+ * single tree or globally on {@link UIManager}. The value in both cases
+ * should be either {@link Boolean#TRUE} or {@link Boolean#FALSE}.<br>
+ * <br>
+ * Here is an example of globally set property (all trees that don't specify
+ * {@link Boolean#FALSE} as a client property will have the automatic drag
+ * and drop support): <br>
+ * <br>
+ * <code>
+ * UIManager.put(LafWidget.TREE_AUTO_DND_SUPPORT, Boolean.TRUE);
+ * </code>
+ */
+ public final static String TREE_AUTO_DND_SUPPORT = "lafwidgets.treeAutoDnDSupport";
+
+ /**
+ * <p>
+ * Client property name for specifying that a scroll pane should have
+ * auto-scroll support invoked on middle-mouse button click. This property
+ * can be installed on a single scroll pane or globally on {@link UIManager}
+ * , and the value should be either {@link Boolean#TRUE} or
+ * {@link Boolean#FALSE}.
+ * </p>
+ * <p>
+ * Here is an example of a scroll bar that has auto-scroll installed.
+ * </p>
+ * <code>
+ * JScrollPane scrollPane = ...<br>
+ * scrollPane.putClientProperty(LafWidget.AUTO_SCROLL, Boolean.TRUE);
+ * </code>
+ *
+ * @since version 3.4
+ */
+ public final static String AUTO_SCROLL = "lafwidget.scroll.auto";
+
+ /**
+ * <p>
+ * Client property name for specifying that the label lookup for custom
+ * widgets and sub-components installed by various UI delegates should
+ * ignore the global locale (as returned by {@link Locale#getDefault()} and
+ * use the component-specific locale (as returned by
+ * {@link JComponent#getLocale()} instead. This property can be installed
+ * only on a single component, and the value should be either
+ * {@link Boolean#TRUE} or {@link Boolean#FALSE}.
+ * </p>
+ *
+ * <p>
+ * Here is an example of menu bar that will be localized according to its
+ * own locale
+ * </p>
+ * <code>
+ * JMenuBar jmb = ...<br>
+ * jmb.putClientProperty(LafWidget.IGNORE_GLOBAL_LOCALE, Boolean.TRUE);
+ * </code>
+ *
+ * @since version 3.1
+ */
+ public final static String IGNORE_GLOBAL_LOCALE = "lafwidgets.ignoreGlobalLocale";
+
+ /**
+ * Associates a component with <code>this</code> widget.
+ *
+ * @param jcomp
+ * Component.
+ */
+ public void setComponent(T jcomp);
+
+ /**
+ * Returns indication whether <code>this</code> widget requires custom LAF
+ * support. Some widgets such as {@link TabOverviewDialogWidget} or
+ * {@link TabHoverPreviewWidget} require custom implementation based on the
+ * internals of the specific LAF. Relevant functions in the base
+ * {@link LafWidgetSupport} support throw
+ * {@link UnsupportedOperationException}.
+ *
+ * @return <code>true</code> if <code>this</code> widget requires custom LAF
+ * support, <code>false</code> otherwise.
+ */
+ public boolean requiresCustomLafSupport();
+
+ /**
+ * Installs UI on the associated component.
+ */
+ public void installUI();
+
+ /**
+ * Installs default settings for the associated component.
+ */
+ public void installDefaults();
+
+ /**
+ * Installs listeners for the associated component.
+ */
+ public void installListeners();
+
+ /**
+ * Installs components for the associated component.
+ */
+ public void installComponents();
+
+ /**
+ * Uninstalls UI on the associated component.
+ */
+ public void uninstallUI();
+
+ /**
+ * Uninstalls default settings for the associated component.
+ */
+ public void uninstallDefaults();
+
+ /**
+ * Uninstalls listeners for the associated component.
+ */
+ public void uninstallListeners();
+
+ /**
+ * Uninstalls components for the associated component.
+ */
+ public void uninstallComponents();
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetAdapter.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetAdapter.java
new file mode 100644
index 0000000..383c1f1
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetAdapter.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget;
+
+import javax.swing.JComponent;
+
+/**
+ * Base implementation of {@link LafWidget} interface.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class LafWidgetAdapter<T extends JComponent> implements
+ LafWidget<T> {
+ /**
+ * Associated component.
+ */
+ protected T jcomp;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#setComponent(javax.swing.JComponent)
+ */
+ @Override
+ public void setComponent(T jcomp) {
+ this.jcomp = jcomp;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#installUI()
+ */
+ @Override
+ public void installUI() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#installComponents()
+ */
+ @Override
+ public void installComponents() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#installDefaults()
+ */
+ @Override
+ public void installDefaults() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#uninstallUI()
+ */
+ @Override
+ public void uninstallUI() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#uninstallComponents()
+ */
+ @Override
+ public void uninstallComponents() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#uninstallDefaults()
+ */
+ @Override
+ public void uninstallDefaults() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetRepository.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetRepository.java
new file mode 100644
index 0000000..cd22c58
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetRepository.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.*;
+
+import javax.swing.JComponent;
+
+/**
+ * Repository of LAF widgets.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LafWidgetRepository {
+ /**
+ * All registered widgets. Key is {@link Class} in the UI component
+ * hierarchy, value is a {@link Set} of fully-qualified widget class names.
+ */
+ protected Map<Class<?>, Set<LafWidgetClassInfo>> widgets;
+
+ /**
+ * Contains fully qualified class names of widgets that should not be
+ * installed on any components.
+ */
+ protected Set<String> widgetClassesToIgnore;
+
+ /**
+ * Currently registered LAF support.
+ */
+ protected LafWidgetSupport lafSupport;
+
+ /**
+ * Indicates whether the currently registered LAF support is custom (not
+ * {@link LafWidgetSupport}).
+ */
+ protected boolean isCustomLafSupportSet;
+
+ /**
+ * Singleton instance.
+ */
+ protected static LafWidgetRepository repository;
+
+ /**
+ * Resource bundle for <b>Substance</b> labels.
+ */
+ private static ResourceBundle LABEL_BUNDLE = null;
+
+ /**
+ * Class loader for the {@link #LABEL_BUNDLE}.
+ *
+ * @since version 1.1
+ */
+ private static ClassLoader labelBundleClassLoader;
+
+ /**
+ * Information on a single class.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected static class LafWidgetClassInfo {
+ /**
+ * Class name.
+ */
+ public String className;
+
+ /**
+ * Indicates whether the matching should be exact.
+ */
+ public boolean isExact;
+
+ /**
+ * Creates a new info object.
+ *
+ * @param className
+ * Class name.
+ * @param isExact
+ * Indicates whether the matching should be exact.
+ */
+ public LafWidgetClassInfo(String className, boolean isExact) {
+ this.className = className;
+ this.isExact = isExact;
+ }
+ }
+
+ /**
+ * Creates a new repository. Marked private to enforce single instance.
+ */
+ private LafWidgetRepository() {
+ this.widgets = new HashMap<Class<?>, Set<LafWidgetClassInfo>>();
+ this.lafSupport = new LafWidgetSupport();
+ this.isCustomLafSupportSet = false;
+ this.widgetClassesToIgnore = new HashSet<String>();
+ }
+
+ /**
+ * Returns the widget repository.
+ *
+ * @return Widget repository.
+ */
+ public static synchronized LafWidgetRepository getRepository() {
+ if (LafWidgetRepository.repository == null) {
+ LafWidgetRepository.repository = new LafWidgetRepository();
+ LafWidgetRepository.repository.populate();
+ }
+ return LafWidgetRepository.repository;
+ }
+
+ /**
+ * Populates the repository from the specified URL. The URL should point to
+ * a properties file, the key being the fully-qualified class name of the
+ * widget implementation, the value being semicolon-separated
+ * fully-qualified class names of classes in UI component hierarchy. Sample
+ * property file:
+ *
+ * <pre>
+ * org.pushingpixels.lafwidget.text.PasswordStrengthCheckerWidget = javax.swing.JPasswordField
+ * org.pushingpixels.lafwidget.text.LockBorderWidget = javax.swing.text.JTextComponent;javax.swing.JComboBox
+ * </pre>
+ *
+ * @param url
+ * URL that points to a properties file.
+ */
+ protected void populateFrom(URL url) {
+ Properties props = new Properties();
+ InputStream is = null;
+ try {
+ is = url.openStream();
+ props.load(is);
+
+ Enumeration<?> names = props.propertyNames();
+ while (names.hasMoreElements()) {
+ String name = (String) names.nextElement();
+ String value = props.getProperty(name);
+ String[] values = value.split(";");
+ for (int i = 0; i < values.length; i++) {
+ String className = values[i].trim();
+ boolean isExact = className.startsWith("%");
+ if (isExact)
+ className = className.substring(1);
+ try {
+ this.registerWidget(name, Class.forName(className),
+ isExact);
+ } catch (ClassNotFoundException cnfe) {
+ }
+ }
+ }
+
+ } catch (IOException ioe) {
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Populates the widget repository. The classpath is scanned for all
+ * resources that match the name <code>META-INF/lafwidget.properties</code>.
+ *
+ * @see #populateFrom(URL)
+ */
+ public void populate() {
+ // the following is fix by Dag Joar and Christian Schlichtherle
+ // for application running with -Xbootclasspath VM flag. In this case,
+ // the using MyClass.class.getClassLoader() would return null,
+ // but the context class loader will function properly
+ // that classes will be properly loaded regardless of whether the lib is
+ // added to the system class path, the extension class path and
+ // regardless of the class loader architecture set up by some
+ // frameworks.
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+
+ try {
+ Enumeration<?> rs = cl
+ .getResources("META-INF/lafwidget.properties");
+ while (rs.hasMoreElements()) {
+ URL rUrl = (URL) rs.nextElement();
+ this.populateFrom(rUrl);
+ }
+ } catch (IOException ioe) {
+ }
+ }
+
+ /**
+ * Registers a new widget for the specified UI classes. The list should
+ * contain {@link Class} instances.
+ *
+ * @param widgetClassName
+ * Full-qualified class name for the widget.
+ * @param supportedClasses
+ * Classes supported by the widget.
+ */
+ public synchronized void registerWidget(String widgetClassName,
+ List<Class<?>> supportedClasses) {
+ for (Class<?> clazz : supportedClasses)
+ this.registerWidget(widgetClassName, clazz, false);
+ }
+
+ /**
+ * Registers a new widget for the specified UI class.
+ *
+ * @param widgetClassName
+ * Full-qualified class name for the widget.
+ * @param supportedClass
+ * Class supported by the widget.
+ * @param isExact
+ * if <code>true</code>, the widget will be available only for
+ * the components of the specified class. If <code>false</code>,
+ * the widget be available for the components of the specified
+ * class and all its descendants (as defined in the
+ * {@link Class#isAssignableFrom(Class)} ).
+ */
+ public synchronized void registerWidget(String widgetClassName,
+ Class<?> supportedClass, boolean isExact) {
+ if (JComponent.class.isAssignableFrom(supportedClass)) {
+ if (!this.widgets.containsKey(supportedClass))
+ this.widgets.put(supportedClass,
+ new HashSet<LafWidgetClassInfo>());
+ }
+ // Guard against multiple registrations of the same widget.
+ // This can happen if more than one jar on the classpath defines
+ // the same widget.
+ for (LafWidgetClassInfo registered : this.widgets.get(supportedClass)) {
+ if (registered.className.equals(widgetClassName))
+ return;
+ }
+ this.widgets.get(supportedClass).add(
+ new LafWidgetClassInfo(widgetClassName, isExact));
+ }
+
+ /**
+ * Returns a set of widgets that match the specified component. The
+ * component hierarchy is scanned bottom-up and all matching widget classes
+ * are used to instantiate new instance of widgets. In case the
+ * {@link #isCustomLafSupportSet} is <code>false</code>, only widgets that
+ * return <code>false</code> in {@link LafWidget#requiresCustomLafSupport()}
+ * are returned.
+ *
+ *
+ * @param jcomp
+ * UI component.
+ * @return Set of widgets that match the specified component.
+ */
+ public synchronized Set<LafWidget> getMatchingWidgets(JComponent jcomp) {
+ Set<LafWidget> result = new HashSet<LafWidget>();
+ Class<?> clazz = jcomp.getClass();
+ boolean isOriginator = true;
+ while (clazz != null) {
+ Set<LafWidgetClassInfo> registered = this.widgets.get(clazz);
+ if (registered != null) {
+ for (Iterator<LafWidgetClassInfo> it = registered.iterator(); it
+ .hasNext();) {
+ LafWidgetClassInfo widgetClassInfo = it.next();
+ if (widgetClassInfo.isExact && !isOriginator)
+ continue;
+ try {
+ String widgetClassName = widgetClassInfo.className;
+ // check if the application requested to ignore the
+ // specific widget
+ if (this.widgetClassesToIgnore
+ .contains(widgetClassName))
+ continue;
+
+ // The code below will fail if no such class exists.
+ // This allows safely removing the relevant widget
+ // classes making the jar size smaller (lite versions).
+ Object widgetObj = Class.forName(widgetClassName)
+ .newInstance();
+ if (widgetObj instanceof LafWidget) {
+ LafWidget widget = (LafWidget) widgetObj;
+ // only add widgets that do not require special LAF
+ // support if no such support has been set.
+ if (!widget.requiresCustomLafSupport()
+ || this.isCustomLafSupportSet) {
+ widget.setComponent(jcomp);
+ result.add(widget);
+ }
+ }
+ // the exceptions are ignored - see the explanation
+ // above.
+ } catch (InstantiationException ie) {
+ } catch (IllegalAccessException iae) {
+ } catch (ClassNotFoundException cnfe) {
+ }
+ }
+ }
+ clazz = clazz.getSuperclass();
+ isOriginator = false;
+ }
+ return result;
+ }
+
+ /**
+ * Sets LAF support.
+ *
+ * @param lafSupport
+ * LAF support.
+ * @throws IllegalArgumentException
+ * If the LAF support is <code>null</code>.
+ */
+ public void setLafSupport(LafWidgetSupport lafSupport) {
+ if (lafSupport == null)
+ throw new IllegalArgumentException("LAF support can't be null");
+ this.lafSupport = lafSupport;
+ this.isCustomLafSupportSet = (this.lafSupport.getClass() != LafWidgetSupport.class);
+ }
+
+ /**
+ * Unsets custom LAF support and reverts to the base LAF support.
+ */
+ public void unsetLafSupport() {
+ this.lafSupport = new LafWidgetSupport();
+ this.isCustomLafSupportSet = false;
+ }
+
+ /**
+ * Returns the currently set LAF support. The result is guaranteed to be
+ * non-<code>null</code>.
+ *
+ * @return Currently set non-<code>null</code> LAF support.
+ */
+ public LafWidgetSupport getLafSupport() {
+ return this.lafSupport;
+ }
+
+ /**
+ * Retrieves the current label bundle.
+ *
+ * @return The current label bundle.
+ * @see #resetLabelBundle()
+ */
+ public static synchronized ResourceBundle getLabelBundle() {
+ // fix for RFE 157 on Substance (allowing custom class loader for
+ // resource bundles which can remove server calls
+ // in applets)
+ if (LafWidgetRepository.labelBundleClassLoader == null) {
+ LafWidgetRepository.LABEL_BUNDLE = ResourceBundle.getBundle(
+ "org.pushingpixels.lafwidget.resources.Labels", Locale
+ .getDefault());
+ } else {
+ LafWidgetRepository.LABEL_BUNDLE = ResourceBundle.getBundle(
+ "org.pushingpixels.lafwidget.resources.Labels", Locale
+ .getDefault(),
+ LafWidgetRepository.labelBundleClassLoader);
+ }
+ return LafWidgetRepository.LABEL_BUNDLE;
+ }
+
+ /**
+ * Retrieves the label bundle for the specified locale.
+ *
+ * @param locale
+ * Locale.
+ * @return The label bundle for the specified locale.
+ */
+ public static synchronized ResourceBundle getLabelBundle(Locale locale) {
+ // fix for RFE 157 on Substance (allowing custom class loader for
+ // resource bundles which can remove server calls
+ // in applets)
+ if (LafWidgetRepository.labelBundleClassLoader == null) {
+ return ResourceBundle.getBundle(
+ "org.pushingpixels.lafwidget.resources.Labels", locale);
+ } else {
+ return ResourceBundle.getBundle(
+ "org.pushingpixels.lafwidget.resources.Labels", locale,
+ LafWidgetRepository.labelBundleClassLoader);
+ }
+ }
+
+ /**
+ * Resets the current label bundle. Useful when the application changes
+ * Locale at runtime.
+ *
+ * @see #getLabelBundle()
+ */
+ public static synchronized void resetLabelBundle() {
+ LafWidgetRepository.LABEL_BUNDLE = null;
+ }
+
+ /**
+ * Sets the class loader for {@link #LABEL_BUNDLE}.
+ *
+ * @param labelBundleClassLoader
+ * Class loader for {@link #LABEL_BUNDLE}.
+ * @since version 1.1
+ */
+ public static void setLabelBundleClassLoader(
+ ClassLoader labelBundleClassLoader) {
+ LafWidgetRepository.labelBundleClassLoader = labelBundleClassLoader;
+ }
+
+ /**
+ * Marks widget with the specified class name to never be installed on any
+ * components.
+ *
+ * @param widgetClassName
+ * Fully qualified widget class name.
+ */
+ public synchronized void addToIgnoreWidgets(String widgetClassName) {
+ this.widgetClassesToIgnore.add(widgetClassName);
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetSupport.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetSupport.java
new file mode 100644
index 0000000..93521e0
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetSupport.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget;
+
+import java.awt.*;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import javax.swing.*;
+import javax.swing.JInternalFrame.JDesktopIcon;
+import javax.swing.plaf.TabbedPaneUI;
+import javax.swing.plaf.basic.BasicTabbedPaneUI;
+import javax.swing.text.JTextComponent;
+
+import org.pushingpixels.lafwidget.desktop.DesktopIconHoverPreviewWidget;
+import org.pushingpixels.lafwidget.menu.MenuSearchWidget;
+import org.pushingpixels.lafwidget.tabbed.TabHoverPreviewWidget;
+import org.pushingpixels.lafwidget.tabbed.TabOverviewDialogWidget;
+import org.pushingpixels.lafwidget.text.LockBorderWidget;
+import org.pushingpixels.lafwidget.text.PasswordStrengthCheckerWidget;
+import org.pushingpixels.lafwidget.utils.LafConstants;
+import org.pushingpixels.lafwidget.utils.LafConstants.PasswordStrength;
+
+/**
+ * LAF-specific support for widgets. Each LAF should override relevant functions
+ * based on the internal implementation. Note that if
+ * {@link LafWidgetRepository#setLafSupport(LafWidgetSupport)} is called with a
+ * custom implementation, that implementation should not throw exceptions in any
+ * function.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LafWidgetSupport {
+ /**
+ * Returns the component for desktop icon hover (internal frame preview)
+ * functionality. Is used in the {@link DesktopIconHoverPreviewWidget}
+ * widget.
+ *
+ * @param desktopIcon
+ * Desktop icon.
+ * @return The component for desktop icon hover (internal frame preview)
+ * functionality.
+ */
+ public JComponent getComponentForHover(JDesktopIcon desktopIcon) {
+ return desktopIcon;
+ }
+
+ /**
+ * Returns indication whether the menu search functionality should be
+ * installed on the specified menu bar. Is used in the
+ * {@link MenuSearchWidget} widget.
+ *
+ * @param menuBar
+ * Menu bar.
+ * @return <code>true</code> if the menu search functionality should be
+ * installed on the specified menu bar, <code>false</code>
+ * otherwise.
+ */
+ public boolean toInstallMenuSearch(JMenuBar menuBar) {
+ return (MenuSearchWidget.getMenuItemCount(menuBar) > 40);
+ }
+
+ /**
+ * Returns indication whether additional functionality should be installed
+ * on the specified component.
+ *
+ * @param comp
+ * Component.
+ * @return <code>true</code> if additional functionality should be installed
+ * on the specified component, <code>false</code> otherwise.
+ */
+ public boolean toInstallExtraElements(Component comp) {
+ return true;
+ }
+
+ /**
+ * Returns the search icon that matches the specified parameters. Is used in
+ * the {@link MenuSearchWidget} widget.
+ *
+ * @param dimension
+ * Search icon dimension.
+ * @param componentOrientation
+ * The orientation for the search icon. Should be considered in
+ * the implementation code for proper RTL support.
+ * @return The search icon that matches the specified parameters.
+ */
+ public Icon getSearchIcon(int dimension,
+ ComponentOrientation componentOrientation) {
+ return LafWidgetUtilities.getSearchIcon(dimension, componentOrientation
+ .isLeftToRight());
+ }
+
+ /**
+ * Returns the icon that matches the specified number. This function is used
+ * in {@link MenuSearchWidget} to set icons on menu search results. See
+ * default implementation in {@link LafWidgetUtilities#getHexaMarker(int)}
+ * that returns binary-based icons for numbers from 0 to 15. Is used in the
+ * {@link MenuSearchWidget} widget.
+ *
+ * @param number
+ * Number.
+ * @return The icon that matches the specified number.
+ */
+ public Icon getNumberIcon(int number) {
+ return LafWidgetUtilities.getHexaMarker(number);
+ }
+
+ /**
+ * Marks the specified button as <code>flat</code>. A flat button doesn't
+ * show its background unless selected, armed, pressed or (possibly) hovered
+ * over. Some LAFs have flat buttons on toolbars. Is used in
+ * {@link MenuSearchWidget} and {@link TabOverviewDialogWidget} widgets.
+ *
+ * @param button
+ * Button to mark as flat.
+ */
+ public void markButtonAsFlat(AbstractButton button) {
+ }
+
+ /**
+ * Returns the index of the rollover tab in the specified tabbed pane. Is
+ * used in the {@link TabHoverPreviewWidget} widget.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @return The index of the rollover tab in the specified tabbed pane.
+ * @throws UnsupportedOperationException
+ * In the base implementation.
+ */
+ public int getRolloverTabIndex(JTabbedPane tabbedPane) {
+ TabbedPaneUI ui = tabbedPane.getUI();
+ if (ui instanceof BasicTabbedPaneUI) {
+ try {
+ Class<?> clazz = ui.getClass();
+ while (clazz != null) {
+ try {
+ Method mtd = clazz.getDeclaredMethod("getRolloverTab",
+ new Class[0]);
+ if (mtd != null) {
+ mtd.setAccessible(true);
+ int result = (Integer) mtd
+ .invoke(ui, new Object[0]);
+ return result;
+ }
+ } catch (NoSuchMethodException nsme) {
+ }
+ clazz = clazz.getSuperclass();
+ }
+ } catch (Throwable t) {
+ // ignore all fall through
+ }
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Sets the tab area insets for the specified tabbed pane. Is used in the
+ * {@link TabOverviewDialogWidget} widget.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabAreaInsets
+ * Tab area insets.
+ * @throws UnsupportedOperationException
+ * In the base implementation.
+ */
+ public void setTabAreaInsets(JTabbedPane tabbedPane, Insets tabAreaInsets) {
+ Insets old = this.getTabAreaInsets(tabbedPane);
+ TabbedPaneUI ui = tabbedPane.getUI();
+ if (ui instanceof BasicTabbedPaneUI) {
+ try {
+ Class<?> clazz = ui.getClass();
+ while (clazz != null) {
+ try {
+ Field fld = clazz.getDeclaredField("tabAreaInsets");
+ if (fld != null) {
+ fld.setAccessible(true);
+ fld.set(ui, tabAreaInsets);
+ // Fire a property change event so that the tabbed
+ // pane can revalidate itself
+ LafWidgetUtilities.firePropertyChangeEvent(
+ tabbedPane, "tabAreaInsets", old,
+ tabAreaInsets);
+ return;
+ }
+ } catch (NoSuchFieldException nsfe) {
+ }
+ clazz = clazz.getSuperclass();
+ }
+ } catch (Throwable t) {
+ // ignore all fall through
+ }
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the tab area insets for the specified tabbed pane.Is used in the
+ * {@link TabOverviewDialogWidget} widget.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @return The tab area insets for the specified tabbed pane.
+ */
+ public Insets getTabAreaInsets(JTabbedPane tabbedPane) {
+ TabbedPaneUI ui = tabbedPane.getUI();
+ if (ui instanceof BasicTabbedPaneUI) {
+ try {
+ Class<?> clazz = ui.getClass();
+ while (clazz != null) {
+ try {
+ Field fld = clazz.getDeclaredField("tabAreaInsets");
+ if (fld != null) {
+ fld.setAccessible(true);
+ Insets result = (Insets) fld.get(ui);
+ return result;
+ }
+ } catch (NoSuchFieldException nsfe) {
+ }
+ clazz = clazz.getSuperclass();
+ }
+ } catch (Throwable t) {
+ // ignore all fall through
+ }
+ }
+ Insets result = UIManager.getInsets("TabbedPane.tabAreaInsets");
+ if (result == null)
+ result = new Insets(0, 0, 0, 0);
+ return result;
+ }
+
+ /**
+ * Returns the tab rectangle for the specified tab in a tabbed pane.Is used
+ * in the {@link TabHoverPreviewWidget} widget.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Index of a tab.
+ * @return The tab rectangle for the specified parameters.
+ * @throws UnsupportedOperationException
+ * In the base implementation.
+ */
+ public Rectangle getTabRectangle(JTabbedPane tabbedPane, int tabIndex) {
+ TabbedPaneUI ui = tabbedPane.getUI();
+ if (ui instanceof BasicTabbedPaneUI) {
+ try {
+ Class<?> clazz = ui.getClass();
+ while (clazz != null) {
+ try {
+ Field fld = clazz.getDeclaredField("rects");
+ if (fld != null) {
+ fld.setAccessible(true);
+ Rectangle[] rects = (Rectangle[]) fld.get(ui);
+ return rects[tabIndex];
+ }
+ } catch (NoSuchFieldException nsfe) {
+ }
+ clazz = clazz.getSuperclass();
+ }
+ } catch (Throwable t) {
+ // ignore all fall through
+ }
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Paints password strength marker. Is used in the
+ * {@link PasswordStrengthCheckerWidget} widget. The default implementation
+ * uses orange color for {@link org.pushingpixels.lafwidget.utils.LafConstants.PasswordStrength#WEAK}
+ * passwords, yellow color for {@link org.pushingpixels.lafwidget.utils.LafConstants.PasswordStrength#MEDIUM}
+ * passwords and green color for
+ * {@link org.pushingpixels.lafwidget.utils.LafConstants.PasswordStrength#STRONG} passwords.
+ *
+ * @param g
+ * Graphics context.
+ * @param x
+ * X coordinate for the marker.
+ * @param y
+ * Y coordinate for the marker.
+ * @param width
+ * Marker width.
+ * @param height
+ * Marker height.
+ * @param pStrength
+ * Password strength.
+ */
+ public void paintPasswordStrengthMarker(Graphics g, int x, int y,
+ int width, int height, PasswordStrength pStrength) {
+ Graphics2D g2 = (Graphics2D) g.create();
+ if (pStrength == PasswordStrength.WEAK)
+ g2.setColor(Color.orange);
+ if (pStrength == PasswordStrength.MEDIUM)
+ g2.setColor(Color.yellow);
+ if (pStrength == PasswordStrength.STRONG)
+ g2.setColor(Color.green);
+ g2.fillRect(x, y, width, height);
+ g2.dispose();
+ }
+
+ /**
+ * Checks whether the specified component should show a lock icon. Is used
+ * in the {@link LockBorderWidget} widget.
+ *
+ * @param comp
+ * Component.
+ * @return <code>true</code> if the specified component should show a lock
+ * icon, <code>false</code> otherwise.
+ */
+ public boolean hasLockIcon(Component comp) {
+ // check the HAS_LOCK_ICON property
+ boolean isEditableTextComponent = (comp instanceof JTextComponent) ? ((JTextComponent) comp)
+ .isEditable()
+ : false;
+ if (comp instanceof JComponent) {
+ if (!isEditableTextComponent
+ && Boolean.TRUE.equals(((JComponent) comp)
+ .getClientProperty(LafWidget.HAS_LOCK_ICON)))
+ return true;
+ if (Boolean.FALSE.equals(((JComponent) comp)
+ .getClientProperty(LafWidget.HAS_LOCK_ICON)))
+ return false;
+ }
+ if (!isEditableTextComponent
+ && Boolean.TRUE.equals(UIManager.get(LafWidget.HAS_LOCK_ICON)))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Returns the lock icon. Is used in {@link LockBorderWidget} widget.
+ *
+ * @return Lock icon. Should be sufficiently small (preferrably not more
+ * than 5-6 pixels wide).
+ */
+ public Icon getLockIcon(Component c) {
+ return LafWidgetUtilities.getSmallLockIcon();
+ }
+
+ /**
+ * Returns the arrow icon (the icon used in combo box drop button, scroll
+ * bar buttons etc.).
+ *
+ * @param orientation
+ * One of {@link SwingConstants#NORTH} or
+ * {@link SwingConstants#SOUTH}.
+ * @return Arrow icon.
+ */
+ public Icon getArrowIcon(int orientation) {
+ return null;
+ }
+
+ /**
+ * Returns the size of the lookup icon. Override to handle high DPI mode.
+ *
+ * @return The size of the lookup icon.
+ */
+ public int getLookupIconSize() {
+ return 14;
+ }
+
+ /**
+ * Returns the size of the lookup button. Override to handle high DPI mode.
+ *
+ * @return The size of the lookup button.
+ */
+ public int getLookupButtonSize() {
+ return 16;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetUtilities.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetUtilities.java
new file mode 100644
index 0000000..b126e2e
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetUtilities.java
@@ -0,0 +1,662 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget;
+
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.text.JTextComponent;
+
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+
+/**
+ * Various utility functions.
+ *
+ * @author Kirill Grouchnikov
+ * @author Romain Guy
+ */
+public class LafWidgetUtilities {
+ /**
+ * Name for the internal client property that marks a component as
+ * previewable.
+ */
+ public static final String PREVIEW_MODE = "lafwidgets.internal.previewMode";
+
+ /**
+ * Private constructor. Is here to enforce using static methods only.
+ */
+ private LafWidgetUtilities() {
+ }
+
+ /**
+ * Retrieves transparent image of specified dimension.
+ *
+ * @param width
+ * Image width.
+ * @param height
+ * Image height.
+ * @return Transparent image of specified dimension.
+ */
+ public static BufferedImage getBlankImage(int width, int height) {
+ BufferedImage image = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+
+ // get graphics and set hints
+ Graphics2D graphics = (Graphics2D) image.getGraphics().create();
+ graphics.setColor(new Color(0, 0, 0, 0));
+ graphics.setComposite(AlphaComposite.Src);
+ graphics.fillRect(0, 0, width, height);
+ graphics.dispose();
+
+ return image;
+ }
+
+ /**
+ * Creates a compatible image (for efficient processing and drawing).
+ *
+ * @param image
+ * The original image.
+ * @return Compatible version of the original image.
+ * @author Romain Guy
+ */
+ public static BufferedImage createCompatibleImage(BufferedImage image) {
+ GraphicsEnvironment e = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice d = e.getDefaultScreenDevice();
+ GraphicsConfiguration c = d.getDefaultConfiguration();
+ BufferedImage compatibleImage = c.createCompatibleImage(image
+ .getWidth(), image.getHeight());
+ Graphics g = compatibleImage.getGraphics();
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+ return compatibleImage;
+ }
+
+ /**
+ * Creates a thumbnail of the specified width.
+ *
+ * @param image
+ * The original image.
+ * @param requestedThumbWidth
+ * The width of the resulting thumbnail.
+ * @return Thumbnail of the specified width.
+ * @author Romain Guy
+ */
+ public static BufferedImage createThumbnail(BufferedImage image,
+ int requestedThumbWidth) {
+ float ratio = (float) image.getWidth() / (float) image.getHeight();
+ int width = image.getWidth();
+ BufferedImage thumb = image;
+
+ do {
+ width /= 2;
+ if (width < requestedThumbWidth) {
+ width = requestedThumbWidth;
+ }
+
+ BufferedImage temp = new BufferedImage(width,
+ (int) (width / ratio), BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2 = temp.createGraphics();
+ g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
+ g2.dispose();
+
+ thumb = temp;
+ } while (width != requestedThumbWidth);
+
+ return thumb;
+ }
+
+ /**
+ * Returns search icon.
+ *
+ * @param dimension
+ * Icon dimension.
+ * @param leftToRight
+ * Indicates the orientation of the resulting icon.
+ * @return Search icon.
+ */
+ public static Icon getSearchIcon(int dimension, boolean leftToRight) {
+ BufferedImage result = LafWidgetUtilities.getBlankImage(dimension,
+ dimension);
+
+ Graphics2D graphics = (Graphics2D) result.getGraphics().create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ graphics.setColor(Color.black);
+
+ graphics.setStroke(new BasicStroke(1.5f));
+ if (leftToRight) {
+ int xc = (int) (0.6 * dimension);
+ int yc = (int) (0.45 * dimension);
+ int r = (int) (0.3 * dimension);
+
+ graphics.drawOval(xc - r, yc - r, 2 * r, 2 * r);
+
+ graphics.setStroke(new BasicStroke(3.0f));
+ GeneralPath handle = new GeneralPath();
+ handle.moveTo((float) (xc - r / Math.sqrt(2.0)), (float) (yc + r
+ / Math.sqrt(2.0)));
+ handle.lineTo(1.8f, dimension - 2.2f);
+ graphics.draw(handle);
+ } else {
+ int xc = (int) (0.4 * dimension);
+ int yc = (int) (0.45 * dimension);
+ int r = (int) (0.3 * dimension);
+
+ graphics.drawOval(xc - r, yc - r, 2 * r, 2 * r);
+
+ graphics.setStroke(new BasicStroke(3.0f));
+ GeneralPath handle = new GeneralPath();
+ handle.moveTo((float) (xc + r / Math.sqrt(2.0)), (float) (yc + r
+ / Math.sqrt(2.0)));
+ handle.lineTo(dimension - 2.5f, dimension - 2.2f);
+ graphics.draw(handle);
+ }
+
+ graphics.dispose();
+ return new ImageIcon(result);
+ }
+
+ /**
+ * Returns small icon representation of the specified integer value. The
+ * remainder of dividing the integer by 16 is translated to four circles
+ * arranged in 2*2 grid.
+ *
+ * @param value
+ * Integer value to represent.
+ * @return Icon representation of the specified integer value.
+ */
+ public static Icon getHexaMarker(int value) {
+ BufferedImage result = LafWidgetUtilities.getBlankImage(9, 9);
+
+ value %= 16;
+ Color offColor = Color.gray;
+ Color onColor = Color.black;
+
+ boolean bit1 = ((value & 0x1) != 0);
+ boolean bit2 = ((value & 0x2) != 0);
+ boolean bit3 = ((value & 0x4) != 0);
+ boolean bit4 = ((value & 0x8) != 0);
+
+ Graphics2D graphics = (Graphics2D) result.getGraphics().create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ graphics.setColor(bit1 ? onColor : offColor);
+ graphics.fillOval(5, 5, 4, 4);
+ graphics.setColor(bit2 ? onColor : offColor);
+ graphics.fillOval(5, 0, 4, 4);
+ graphics.setColor(bit3 ? onColor : offColor);
+ graphics.fillOval(0, 5, 4, 4);
+ graphics.setColor(bit4 ? onColor : offColor);
+ graphics.fillOval(0, 0, 4, 4);
+
+ graphics.dispose();
+ return new ImageIcon(result);
+ }
+
+ /**
+ * Makes the specified component and all its descendants previewable.
+ *
+ * @param comp
+ * Component.
+ * @param dbSnapshot
+ * The "snapshot" map that will contain the original
+ * double-buffer status of the specified component and all its
+ * descendants. Key is {@link JComponent}, value is
+ * {@link Boolean}.
+ */
+ public static void makePreviewable(Component comp,
+ Map<Component, Boolean> dbSnapshot) {
+ if (comp instanceof JComponent) {
+ JComponent jcomp = (JComponent) comp;
+ // if (jcomp.getParent() instanceof CellRendererPane) {
+ // System.out.println(jcomp.getClass().getSimpleName() + ":"
+ // + jcomp.hashCode());
+ // }
+ dbSnapshot.put(jcomp, jcomp.isDoubleBuffered());
+ jcomp.setDoubleBuffered(false);
+ jcomp.putClientProperty(LafWidgetUtilities.PREVIEW_MODE,
+ Boolean.TRUE);
+ }
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++)
+ LafWidgetUtilities.makePreviewable(cont.getComponent(i),
+ dbSnapshot);
+ }
+ }
+
+ /**
+ * Restores the regular (non-previewable) status of the specified component
+ * and all its descendants.
+ *
+ * @param comp
+ * Component.
+ * @param dbSnapshot
+ * The "snapshot" map that contains the original double-buffer
+ * status of the specified component and all its descendants. Key
+ * is {@link JComponent}, value is {@link Boolean}.
+ */
+ public static void restorePreviewable(Component comp,
+ Map<Component, Boolean> dbSnapshot) {
+ if (comp instanceof JComponent) {
+ JComponent jcomp = (JComponent) comp;
+ if (dbSnapshot.containsKey(comp)) {
+ // the key may exist, but may be set to null (lovely boxing quirk)
+ // treat null as false, since that is the default
+ Boolean buffered = dbSnapshot.get(comp);
+ jcomp.setDoubleBuffered(buffered == null ? false : buffered);
+ jcomp.putClientProperty(LafWidgetUtilities.PREVIEW_MODE, null);
+ } else {
+ // this can happen in case the application has
+ // renderers (combos, ...). Take the property from the parent
+ Component parent = comp.getParent();
+ if (parent instanceof JComponent && dbSnapshot.containsKey(parent)) {
+ // the key may exist, but may be set to null (lovely boxing quirk)
+ // treat null as false, since that is the default
+ Boolean buffered = dbSnapshot.get(parent);
+ jcomp.setDoubleBuffered(buffered == null ? false : buffered);
+ jcomp.putClientProperty(LafWidgetUtilities.PREVIEW_MODE,
+ null);
+ }
+ // System.out.println("Not found");
+ // Component c = jcomp;
+ // while (c != null) {
+ // System.out.println("\t" + c.getClass().getSimpleName()
+ // + ":" + c.hashCode());
+ // c = c.getParent();
+ // }
+ }
+ }
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++)
+ LafWidgetUtilities.restorePreviewable(cont.getComponent(i),
+ dbSnapshot);
+ }
+ }
+
+ /**
+ * Returns a lock icon.
+ *
+ * @return Lock icon.
+ */
+ public static Icon getSmallLockIcon() {
+ BufferedImage result = LafWidgetUtilities.getBlankImage(6, 8);
+
+ Color fore = Color.black;
+ Color fill = new Color(208, 208, 48);
+
+ Graphics2D graphics = (Graphics2D) result.getGraphics().create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+
+ graphics.setColor(fill);
+ graphics.fillRect(1, 3, 4, 4);
+ graphics.setColor(fore);
+ graphics.drawLine(0, 3, 0, 7);
+ graphics.drawLine(5, 3, 5, 7);
+ graphics.drawLine(0, 7, 5, 7);
+ graphics.drawLine(1, 2, 4, 2);
+ graphics.drawLine(1, 1, 1, 2);
+ graphics.drawLine(4, 1, 4, 2);
+ graphics.drawLine(2, 0, 3, 0);
+ graphics.drawLine(2, 4, 3, 4);
+ graphics.drawLine(2, 5, 3, 5);
+
+ graphics.dispose();
+ return new ImageIcon(result);
+ }
+
+ /**
+ * Checks whether the specified text component has
+ * "select all on focus gain" property.
+ *
+ * @param textComp
+ * Text component.
+ * @return <code>true</code> if the specified text component has "select all
+ * on focus gain" property, <code>false</code> otherwise.
+ */
+ public static boolean hasTextFocusSelectAllProperty(JTextComponent textComp) {
+ Component comp = textComp;
+ while (comp != null) {
+ if (comp instanceof JComponent) {
+ Object textFocusSelectAllProperty = ((JComponent) comp)
+ .getClientProperty(LafWidget.TEXT_SELECT_ON_FOCUS);
+ if (Boolean.TRUE.equals(textFocusSelectAllProperty))
+ return true;
+ if (Boolean.FALSE.equals(textFocusSelectAllProperty))
+ return false;
+ }
+ comp = comp.getParent();
+ }
+ return (Boolean.TRUE.equals(UIManager
+ .get(LafWidget.TEXT_SELECT_ON_FOCUS)));
+ }
+
+ /**
+ * Checks whether the specified text component has "flip select on escape"
+ * property.
+ *
+ * @param textComp
+ * Text component.
+ * @return <code>true</code> if the specified text component has "flip
+ * select on escape" property, <code>false</code> otherwise.
+ */
+ public static boolean hasTextFlipSelectOnEscapeProperty(
+ JTextComponent textComp) {
+ Object textFocusSelectAllProperty = textComp
+ .getClientProperty(LafWidget.TEXT_FLIP_SELECT_ON_ESCAPE);
+ return (Boolean.TRUE.equals(textFocusSelectAllProperty));
+ }
+
+ /**
+ * Checks whether the specified text component has edit context menu
+ * property.
+ *
+ * @param textComp
+ * Text component.
+ * @return <code>true</code> if the specified text component has edit
+ * context menu property, <code>false</code> otherwise.
+ */
+ public static boolean hasTextEditContextMenu(JTextComponent textComp) {
+ Object textEditContextMenuProperty = textComp
+ .getClientProperty(LafWidget.TEXT_EDIT_CONTEXT_MENU);
+ if (Boolean.TRUE.equals(textEditContextMenuProperty))
+ return true;
+ if (Boolean.FALSE.equals(textEditContextMenuProperty))
+ return false;
+ return (Boolean.TRUE.equals(UIManager
+ .get(LafWidget.TEXT_EDIT_CONTEXT_MENU)));
+ }
+
+ /**
+ * Checks whether the specified scroll pane supports auto scroll.
+ *
+ * @param scrollPane
+ * Scroll pane component.
+ * @return <code>true</code> if the specified scroll pane supports auto
+ * scroll, <code>false</code> otherwise.
+ */
+ public static boolean hasAutoScroll(JScrollPane scrollPane) {
+ Object compProperty = scrollPane
+ .getClientProperty(LafWidget.AUTO_SCROLL);
+ if (Boolean.TRUE.equals(compProperty))
+ return true;
+ if (Boolean.FALSE.equals(compProperty))
+ return false;
+ return (Boolean.TRUE.equals(UIManager.get(LafWidget.AUTO_SCROLL)));
+ }
+
+ /**
+ * Checks whether the specified tree component has automatic drag and drop
+ * support.
+ *
+ * @param tree
+ * Tree component.
+ * @return <code>true</code> if the specified text component has automatic
+ * drag and drop support, <code>false</code> otherwise.
+ */
+ public static boolean hasAutomaticDnDSupport(JTree tree) {
+ Object dndProperty = tree
+ .getClientProperty(LafWidget.TREE_AUTO_DND_SUPPORT);
+ if (Boolean.TRUE.equals(dndProperty))
+ return true;
+ if (Boolean.FALSE.equals(dndProperty))
+ return false;
+ return (Boolean.TRUE.equals(UIManager
+ .get(LafWidget.TREE_AUTO_DND_SUPPORT)));
+ }
+
+ /**
+ * Checks whether the label lookup should use component-specific locale on
+ * the specified component.
+ *
+ * @param jcomp
+ * Component.
+ * @return <code>true</code> if the custom labels should be looked up based
+ * on the component locale as returned by
+ * {@link JComponent#getLocale()}, <code>false</code> if the custom
+ * labels should be looked up based on the global locale as returned
+ * by {@link Locale#getDefault()}.
+ */
+ public static boolean toIgnoreGlobalLocale(JComponent jcomp) {
+ if (jcomp == null)
+ return false;
+ return Boolean.TRUE.equals(jcomp
+ .getClientProperty(LafWidget.IGNORE_GLOBAL_LOCALE));
+ }
+
+ /**
+ * Returns the resource bundle for the specified component.
+ *
+ * @param jcomp
+ * Component.
+ * @return Resource bundle for the specified component.
+ */
+ public static ResourceBundle getResourceBundle(JComponent jcomp) {
+ if (toIgnoreGlobalLocale(jcomp)) {
+ return LafWidgetRepository.getLabelBundle(jcomp.getLocale());
+ } else {
+ return LafWidgetRepository.getLabelBundle();
+ }
+ }
+
+ /**
+ * Checks whether the specified component has been configured (specifically
+ * or globally) to have no animations of the specific facet. Can be used to
+ * cull unnecessary code in animation listeners on large tables and lists.
+ *
+ * @param comp
+ * Component.
+ * @param animationFacet
+ * Animation facet.
+ * @return <code>true</code> if the specified component has been configured
+ * (specifically or globally) to have no animations of the specific
+ * facet, <code>false</code> otherwise.
+ */
+ public static boolean hasNoAnimations(Component comp,
+ AnimationFacet animationFacet) {
+ return !AnimationConfigurationManager.getInstance().isAnimationAllowed(
+ animationFacet, comp);
+ }
+
+ /**
+ * Returns the current icon for the specified button. This method is <b>for
+ * internal use only</b>.
+ *
+ * @param b
+ * Button.
+ * @return Icon for the specified button.
+ */
+ public static Icon getIcon(AbstractButton b) {
+ Icon icon = b.getIcon();
+ if (icon == null)
+ return null;
+ ButtonModel model = b.getModel();
+ Icon tmpIcon = null;
+
+ if (icon != null) {
+ if (!model.isEnabled()) {
+ if (model.isSelected()) {
+ tmpIcon = b.getDisabledSelectedIcon();
+ } else {
+ tmpIcon = b.getDisabledIcon();
+ }
+ } else if (model.isPressed() && model.isArmed()) {
+ tmpIcon = b.getPressedIcon();
+ } else if (b.isRolloverEnabled() && model.isRollover()) {
+ if (model.isSelected()) {
+ tmpIcon = b.getRolloverSelectedIcon();
+ } else {
+ tmpIcon = b.getRolloverIcon();
+ }
+ } else if (model.isSelected()) {
+ tmpIcon = b.getSelectedIcon();
+ }
+
+ if (tmpIcon != null) {
+ icon = tmpIcon;
+ }
+ }
+ return icon;
+ }
+
+ public static boolean toIgnoreAnimations(Component comp) {
+ if (comp instanceof JMenuItem)
+ return false;
+ return (SwingUtilities.getAncestorOfClass(CellRendererPane.class, comp) != null);
+ }
+
+ /**
+ * Tests UI threading violations on changing the state the specified
+ * component.
+ *
+ * @param comp
+ * Component.
+ * @throws UiThreadingViolationException
+ * If the component is changing state off Event Dispatch Thread.
+ */
+ public static void testComponentStateChangeThreadingViolation(Component comp) {
+ if (!SwingUtilities.isEventDispatchThread()) {
+ UiThreadingViolationException uiThreadingViolationError = new UiThreadingViolationException(
+ "Component state change must be done on Event Dispatch Thread");
+ uiThreadingViolationError.printStackTrace(System.err);
+ throw uiThreadingViolationError;
+ }
+ }
+
+ /**
+ * Fires the matching property change event on the specific component.
+ *
+ * @param component
+ * Component.
+ * @param propertyName
+ * Property name.
+ * @param oldValue
+ * Old property value.
+ * @param newValue
+ * New property value.
+ */
+ public static void firePropertyChangeEvent(JComponent component,
+ String propertyName, Object oldValue, Object newValue) {
+ PropertyChangeEvent pce = new PropertyChangeEvent(component,
+ propertyName, oldValue, newValue);
+ for (PropertyChangeListener general : component
+ .getPropertyChangeListeners()) {
+ general.propertyChange(pce);
+ }
+ for (PropertyChangeListener specific : component
+ .getPropertyChangeListeners(propertyName)) {
+ specific.propertyChange(pce);
+ }
+ }
+
+ /**
+ * Returns the composite to use for painting the specified component. The
+ * result should be set on the {@link Graphics2D} before any custom
+ * rendering is done. This method can be used by application painting code
+ * and by look-and-feel delegates.
+ *
+ * @param c
+ * Component.
+ * @param translucency
+ * The translucency of the original painting.
+ * @param g
+ * The original graphics context.
+ * @return The composite to use for painting the specified component.
+ */
+ public static Composite getAlphaComposite(Component c, float translucency,
+ Graphics g) {
+ float xFactor = 1.0f;
+ if (g instanceof Graphics2D) {
+ Graphics2D g2d = (Graphics2D) g;
+ Composite existingComposite = g2d.getComposite();
+ if (existingComposite instanceof AlphaComposite) {
+ AlphaComposite ac = (AlphaComposite) existingComposite;
+ if (ac.getRule() == AlphaComposite.SRC_OVER)
+ xFactor = ac.getAlpha();
+ }
+ }
+ float finalAlpha = translucency * xFactor;
+ if (finalAlpha < 0.0f) {
+ finalAlpha = 0.0f;
+ }
+ if (finalAlpha > 1.0f) {
+ finalAlpha = 1.0f;
+ }
+ if (finalAlpha == 1.0f) {
+ return AlphaComposite.SrcOver;
+ }
+ return AlphaComposite.SrcOver.derive(finalAlpha);
+ }
+
+ public static Composite getAlphaComposite(Component c, float translucency) {
+ return getAlphaComposite(c, translucency, null);
+ }
+
+ /**
+ * Returns the composite to use for painting the specified component. The
+ * result should be set on the {@link Graphics2D} before any custom
+ * rendering is done. This method can be used by application painting code
+ * and by look-and-feel delegates.
+ *
+ * @param c
+ * Component.
+ * @return The composite to use for painting the specified component.
+ */
+ public static Composite getAlphaComposite(Component c, Graphics g) {
+ return getAlphaComposite(c, 1.0f, g);
+ }
+
+ /**
+ * Returns the composite to use for painting the specified component. The
+ * result should be set on the {@link Graphics2D} before any custom
+ * rendering is done. This method can be used by application painting code
+ * and by look-and-feel delegates.
+ *
+ * @param c
+ * Component.
+ * @return The composite to use for painting the specified component.
+ */
+ public static Composite getAlphaComposite(Component c) {
+ return getAlphaComposite(c, 1.0f, null);
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetUtilities2.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetUtilities2.java
new file mode 100644
index 0000000..6ab15e9
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/LafWidgetUtilities2.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget;
+
+import java.awt.Component;
+import java.awt.Container;
+
+import javax.swing.JComponent;
+import javax.swing.JPasswordField;
+import javax.swing.JTabbedPane;
+import javax.swing.UIManager;
+
+import org.pushingpixels.lafwidget.preview.PreviewPainter;
+import org.pushingpixels.lafwidget.tabbed.TabPreviewPainter;
+import org.pushingpixels.lafwidget.text.PasswordStrengthChecker;
+
+/**
+ * Various utility functions.
+ *
+ * @author Kirill Grouchnikov
+ * @author Romain Guy
+ */
+public class LafWidgetUtilities2 {
+ /**
+ * Private constructor. Is here to enforce using static methods only.
+ */
+ private LafWidgetUtilities2() {
+ }
+
+ /**
+ * Returns the preview painter for the specified tabbed pane.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @return Preview painter for the specified tabbed pane.
+ */
+ public static TabPreviewPainter getTabPreviewPainter(JTabbedPane tabbedPane) {
+ if (tabbedPane == null)
+ return null;
+
+ // check property on tabbed pane
+ Object tabProp = tabbedPane
+ .getClientProperty(LafWidget.TABBED_PANE_PREVIEW_PAINTER);
+ if (tabProp instanceof TabPreviewPainter)
+ return (TabPreviewPainter) tabProp;
+
+ return null;
+ }
+
+ /**
+ * Returns the preview painter for the specified component.
+ *
+ * @param comp
+ * Component.
+ * @return Preview painter for the specified component.
+ * @since 2.1
+ */
+ public static PreviewPainter getComponentPreviewPainter(Component comp) {
+ if (comp == null)
+ return null;
+
+ // check property on component
+ if (comp instanceof JComponent) {
+ Object compProp = ((JComponent) comp)
+ .getClientProperty(LafWidget.COMPONENT_PREVIEW_PAINTER);
+ if (compProp instanceof PreviewPainter)
+ return (PreviewPainter) compProp;
+ }
+
+ // check property on parent
+ Container parent = comp.getParent();
+ if (parent instanceof JComponent) {
+ Object parentProp = ((JComponent) parent)
+ .getClientProperty(LafWidget.COMPONENT_PREVIEW_PAINTER);
+ if (parentProp instanceof PreviewPainter)
+ return (PreviewPainter) parentProp;
+ }
+
+ Object globProp = UIManager.get(LafWidget.COMPONENT_PREVIEW_PAINTER);
+ if (globProp instanceof PreviewPainter)
+ return (PreviewPainter) globProp;
+
+ return null;
+ }
+
+ /**
+ * Returns the password strength checker for the specified password field.
+ *
+ * @param jpf
+ * Password field.
+ * @return Password strength checker for the specified password field. The
+ * result can be <code>null</code>.
+ */
+ public static PasswordStrengthChecker getPasswordStrengthChecker(
+ JPasswordField jpf) {
+ Object obj = jpf.getClientProperty(LafWidget.PASSWORD_STRENGTH_CHECKER);
+ if ((obj != null) && (obj instanceof PasswordStrengthChecker))
+ return (PasswordStrengthChecker) obj;
+ return null;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/Resettable.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/Resettable.java
new file mode 100644
index 0000000..e27bf73
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/Resettable.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget;
+
+/**
+ * Base interface for widgets that support the <b>reset</b> operation.
+ *
+ * @author Kirill Grouchnikov.
+ */
+public interface Resettable {
+ /**
+ * Resets the state of <code>this</code> widget.
+ */
+ public void reset();
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/UiThreadingViolationException.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/UiThreadingViolationException.java
new file mode 100644
index 0000000..1b759e5
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/UiThreadingViolationException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget;
+
+/**
+ * This exception is thrown when the code detects violations of UI threading
+ * rules.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class UiThreadingViolationException extends RuntimeException {
+ /**
+ * Creates a new instance of this exception.
+ *
+ * @param message
+ * Message.
+ */
+ public UiThreadingViolationException(String message) {
+ super(message);
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/AnimationConfigurationManager.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/AnimationConfigurationManager.java
new file mode 100644
index 0000000..1955260
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/AnimationConfigurationManager.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.animation;
+
+import java.awt.Component;
+import java.util.*;
+
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.ease.Spline;
+import org.pushingpixels.trident.ease.TimelineEase;
+
+/**
+ * Animation configuration manager.
+ *
+ * @author Kirill Grouchnikov
+ * @since 2.1
+ */
+public class AnimationConfigurationManager {
+ private static final Spline DEFAULT_EASE = new Spline(0.5f);
+
+ /**
+ * Singleton instance.
+ */
+ private static AnimationConfigurationManager instance;
+
+ private long timelineDuration;
+
+ /**
+ * Contains {@link AnimationFacet} instances.
+ */
+ private Set<AnimationFacet> globalAllowed;
+
+ /**
+ * Key - {@link AnimationFacet}, value - set of {@link Class} instances.
+ */
+ private Map<AnimationFacet, Set<Class<?>>> classAllowed;
+
+ /**
+ * Key - {@link AnimationFacet}, value - set of {@link Class} instances.
+ */
+ private Map<AnimationFacet, Set<Class<?>>> classDisallowed;
+
+ /**
+ * Key - {@link AnimationFacet}, value - set of {@link Component} instances.
+ */
+ private Map<AnimationFacet, Set<Component>> instanceAllowed;
+
+ /**
+ * Key - {@link AnimationFacet}, value - set of {@link Component} instances.
+ */
+ private Map<AnimationFacet, Set<Component>> instanceDisallowed;
+
+ /**
+ * Returns the configuration manager instance.
+ *
+ * @return Configuration manager instance.
+ */
+ public static synchronized AnimationConfigurationManager getInstance() {
+ if (instance == null) {
+ instance = new AnimationConfigurationManager();
+ }
+ return instance;
+ }
+
+ /**
+ * Creates a new instance.
+ */
+ private AnimationConfigurationManager() {
+ this.globalAllowed = new HashSet<AnimationFacet>();
+ this.classAllowed = new HashMap<AnimationFacet, Set<Class<?>>>();
+ this.classDisallowed = new HashMap<AnimationFacet, Set<Class<?>>>();
+ this.instanceAllowed = new HashMap<AnimationFacet, Set<Component>>();
+ this.instanceDisallowed = new HashMap<AnimationFacet, Set<Component>>();
+ this.timelineDuration = 200;
+ }
+
+ /**
+ * Allows animations of the specified facet on all controls.
+ *
+ * @param animationFacet
+ * Animation facet to allow.
+ */
+ public synchronized void allowAnimations(AnimationFacet animationFacet) {
+ this.globalAllowed.add(animationFacet);
+ }
+
+ /**
+ * Allows animations of the specified facet on all controls of specified
+ * class.
+ *
+ * @param animationFacet
+ * Animation facet to allow.
+ * @param clazz
+ * Control class for allowing the animation facet.
+ */
+ public synchronized void allowAnimations(AnimationFacet animationFacet,
+ Class<?> clazz) {
+ Set<Class<?>> existingAllowed = this.classAllowed.get(animationFacet);
+ if (existingAllowed == null) {
+ existingAllowed = new HashSet<Class<?>>();
+ this.classAllowed.put(animationFacet, existingAllowed);
+ }
+ existingAllowed.add(clazz);
+
+ Set<Class<?>> existingDisallowed = this.classDisallowed
+ .get(animationFacet);
+ if (existingDisallowed != null) {
+ existingDisallowed.remove(clazz);
+ }
+ }
+
+ /**
+ * Allows animations of the specified facet on all controls of specified
+ * classes.
+ *
+ * @param animationFacet
+ * Animation facet to allow.
+ * @param clazz
+ * Control classes for allowing the animation facet.
+ */
+ public synchronized void allowAnimations(AnimationFacet animationFacet,
+ Class<?>[] clazz) {
+ for (int i = 0; i < clazz.length; i++) {
+ allowAnimations(animationFacet, clazz[i]);
+ }
+ }
+
+ /**
+ * Allows animations of the specified facet on the specified control.
+ *
+ * @param animationFacet
+ * Animation facet to allow.
+ * @param comp
+ * Control for allowing the animation facet.
+ */
+ public synchronized void allowAnimations(AnimationFacet animationFacet,
+ Component comp) {
+ Set<Component> existingAllowed = this.instanceAllowed
+ .get(animationFacet);
+ if (existingAllowed == null) {
+ existingAllowed = new HashSet<Component>();
+ this.instanceAllowed.put(animationFacet, existingAllowed);
+ }
+ existingAllowed.add(comp);
+
+ Set<Component> existingDisallowed = this.instanceDisallowed
+ .get(animationFacet);
+ if (existingDisallowed != null) {
+ existingDisallowed.remove(comp);
+ }
+ }
+
+ /**
+ * Disallows animations of the specified facet on all controls.
+ *
+ * @param animationFacet
+ * Animation facet to disallow.
+ */
+ public synchronized void disallowAnimations(AnimationFacet animationFacet) {
+ this.globalAllowed.remove(animationFacet);
+ }
+
+ /**
+ * Disallows animations of the specified facet on all controls of specified
+ * class.
+ *
+ * @param animationFacet
+ * Animation facet to disallow.
+ * @param clazz
+ * Control class for disallowing the animation facet.
+ */
+ public synchronized void disallowAnimations(AnimationFacet animationFacet,
+ Class<?> clazz) {
+ Set<Class<?>> existingAllowed = this.classAllowed.get(animationFacet);
+ if (existingAllowed != null) {
+ existingAllowed.remove(clazz);
+ if (existingAllowed.size() == 0)
+ this.classAllowed.remove(animationFacet);
+ }
+
+ Set<Class<?>> existingDisallowed = this.classDisallowed
+ .get(animationFacet);
+ if (existingDisallowed == null) {
+ existingDisallowed = new HashSet<Class<?>>();
+ this.classDisallowed.put(animationFacet, existingDisallowed);
+ }
+ existingDisallowed.add(clazz);
+ }
+
+ /**
+ * Disallows animations of the specified facet on all controls of specified
+ * classes.
+ *
+ * @param animationFacet
+ * Animation facet to disallow.
+ * @param clazz
+ * Control classes for disallowing the animation facet.
+ */
+ public synchronized void disallowAnimations(AnimationFacet animationFacet,
+ Class<?>[] clazz) {
+ for (int i = 0; i < clazz.length; i++) {
+ disallowAnimations(animationFacet, clazz[i]);
+ }
+ }
+
+ /**
+ * Disallows animations of the specified facet on the specified control.
+ *
+ * @param animationFacet
+ * Animation facet to disallow.
+ * @param comp
+ * Control for disallowing the animation facet.
+ */
+ public synchronized void disallowAnimations(AnimationFacet animationFacet,
+ Component comp) {
+ Set<Component> existingAllowed = this.instanceAllowed
+ .get(animationFacet);
+ if (existingAllowed != null) {
+ existingAllowed.remove(comp);
+ if (existingAllowed.size() == 0)
+ this.instanceAllowed.remove(animationFacet);
+ }
+
+ Set<Component> existingDisallowed = this.instanceDisallowed
+ .get(animationFacet);
+ if (existingDisallowed == null) {
+ existingDisallowed = new HashSet<Component>();
+ this.instanceDisallowed.put(animationFacet, existingDisallowed);
+ }
+ existingDisallowed.add(comp);
+ }
+
+ /**
+ * Checks whether the specified animation facet is allowed on the specified
+ * component.
+ *
+ * @param animationFacet
+ * Animation facet.
+ * @param comp
+ * Component. Can be <code>null</code>.
+ * @return <code>true</code> if the specified animation facet is allowed on
+ * the specified component, <code>false</code> otherwise.
+ */
+ public synchronized boolean isAnimationAllowed(
+ AnimationFacet animationFacet, Component comp) {
+ Set<Component> instanceDisallowed = this.instanceDisallowed
+ .get(animationFacet);
+ if (instanceDisallowed != null) {
+ if (instanceDisallowed.contains(comp))
+ return false;
+ }
+ Set<Component> instanceAllowed = this.instanceAllowed
+ .get(animationFacet);
+ if (instanceAllowed != null) {
+ if (instanceAllowed.contains(comp))
+ return true;
+ }
+
+ if (comp != null) {
+ Class<?> clazz = comp.getClass();
+ Set<Class<?>> classAllowed = this.classAllowed.get(animationFacet);
+ Set<Class<?>> classDisallowed = this.classDisallowed
+ .get(animationFacet);
+ if (classDisallowed != null) {
+ for (Class<?> disallowed : classDisallowed) {
+ if (disallowed.isAssignableFrom(clazz))
+ return false;
+ }
+ }
+ if (classAllowed != null) {
+ for (Class<?> allowed : classAllowed) {
+ if (allowed.isAssignableFrom(clazz))
+ return true;
+ }
+ }
+ }
+ if (this.globalAllowed.contains(animationFacet))
+ return true;
+ return false;
+ }
+
+ public void setTimelineDuration(long timelineDuration) {
+ this.timelineDuration = timelineDuration;
+ }
+
+ public long getTimelineDuration() {
+ return timelineDuration;
+ }
+
+ public void configureTimeline(Timeline timeline) {
+ timeline.setDuration(this.timelineDuration);
+ timeline.setEase(DEFAULT_EASE);
+ }
+
+ public void configureModifiedTimeline(Timeline timeline) {
+ timeline.setDuration(5 * this.timelineDuration);
+ timeline.setEase(new TimelineEase() {
+ @Override
+ public float map(float durationFraction) {
+ if (durationFraction < 0.8f) {
+ return 0.0f;
+ }
+ return 5.0f * (durationFraction - 0.8f);
+ }
+ });
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/AnimationFacet.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/AnimationFacet.java
new file mode 100644
index 0000000..9dddf60
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/AnimationFacet.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.animation;
+
+/**
+ * Animation facet.
+ *
+ * @author Kirill Grouchnikov.
+ */
+public final class AnimationFacet {
+ /**
+ * Animation facet display name.
+ */
+ protected String displayName;
+
+ /**
+ * Creates a new animation facet.
+ *
+ * @param displayName
+ * Display name for the animation facet.
+ * @param isDefaultAllowed
+ * Indicates whether this animation facet is allowed by default.
+ */
+ public AnimationFacet(String displayName, boolean isDefaultAllowed) {
+ this.displayName = displayName;
+ if (isDefaultAllowed) {
+ AnimationConfigurationManager.getInstance().allowAnimations(this);
+ }
+ }
+
+ /**
+ * Arming a component.
+ */
+ public static final AnimationFacet ARM = new AnimationFacet(
+ "lafwidgets.core.arm", true);
+
+ /**
+ * Pressing a component.
+ */
+ public static final AnimationFacet PRESS = new AnimationFacet(
+ "lafwidgets.core.press", true);
+
+ /**
+ * Focusing a component.
+ */
+ public static final AnimationFacet FOCUS = new AnimationFacet(
+ "lafwidgets.core.focus", true);
+
+ /**
+ * <p>
+ * Focus loop animation. Disabled by default, use
+ * {@link AnimationConfigurationManager#allowAnimations(AnimationFacet)} to
+ * enable.
+ * </p>
+ *
+ * @since version 3.0
+ */
+ public static final AnimationFacet FOCUS_LOOP_ANIMATION = new AnimationFacet(
+ "lafwidgets.core.focusLoopAnimation", false);
+
+ /**
+ * Rollover a component.
+ */
+ public static final AnimationFacet ROLLOVER = new AnimationFacet(
+ "lafwidgets.core.rollover", true);
+
+ /**
+ * Selecting a component.
+ */
+ public static final AnimationFacet SELECTION = new AnimationFacet(
+ "lafwidgets.core.selection", true);
+
+ /**
+ * <i>Ghosting image</i> effects on button icons when the button is
+ * rolled-over. Disabled by default, use
+ * {@link AnimationConfigurationManager#allowAnimations(AnimationFacet)} to
+ * enable.
+ */
+ public static final AnimationFacet GHOSTING_ICON_ROLLOVER = new AnimationFacet(
+ "lafwidgets.core.ghosting.iconRollover", false);
+
+ /**
+ * <i>Ghosting image</i> effects on buttons when the button is pressed.
+ * Disabled by default, use
+ * {@link AnimationConfigurationManager#allowAnimations(AnimationFacet)} to
+ * enable.
+ */
+ public static final AnimationFacet GHOSTING_BUTTON_PRESS = new AnimationFacet(
+ "lafwidgets.core.ghosting.buttonPress", false);
+
+ /**
+ * Glow effect on icons when the relevant control is rolled over. Disabled
+ * by default, use
+ * {@link AnimationConfigurationManager#allowAnimations(AnimationFacet)} to
+ * enable.
+ */
+ public static final AnimationFacet ICON_GLOW = new AnimationFacet(
+ "lafwidgets.core.iconGlow", false);
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return this.displayName;
+ }
+}
\ No newline at end of file
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/effects/GhostAnimationWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/effects/GhostAnimationWidget.java
new file mode 100644
index 0000000..e949892
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/effects/GhostAnimationWidget.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.animation.effects;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.AbstractButton;
+
+import org.pushingpixels.lafwidget.LafWidgetAdapter;
+
+/**
+ * Widget that tracks changes to the button for ghost effects.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class GhostAnimationWidget extends LafWidgetAdapter<AbstractButton> {
+ /**
+ * Model change listener for ghost image effects.
+ */
+ private GhostingListener ghostModelChangeListener;
+
+ /**
+ * Property change listener. Listens on changes to the
+ * {@link AbstractButton#MODEL_CHANGED_PROPERTY} property.
+ */
+ protected PropertyChangeListener ghostPropertyListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installDefaults()
+ */
+ @Override
+ public void installDefaults() {
+ this.jcomp.setRolloverEnabled(true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ this.ghostPropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (AbstractButton.MODEL_CHANGED_PROPERTY.equals(evt
+ .getPropertyName())) {
+ if (ghostModelChangeListener != null)
+ ghostModelChangeListener.unregisterListeners();
+ ghostModelChangeListener = new GhostingListener(jcomp,
+ jcomp.getModel());
+ ghostModelChangeListener.registerListeners();
+ }
+
+ // if ("icon.bounds".equals(evt.getPropertyName())) {
+ // try {
+ // throw new IOException(evt.getNewValue().toString());
+ // } catch (Exception ie) {
+ // ie.printStackTrace();
+ // }
+ // }
+ }
+ };
+ jcomp.addPropertyChangeListener(this.ghostPropertyListener);
+
+ this.ghostModelChangeListener = new GhostingListener(jcomp, jcomp
+ .getModel());
+ this.ghostModelChangeListener.registerListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ jcomp.removePropertyChangeListener(this.ghostPropertyListener);
+ this.ghostPropertyListener = null;
+
+ this.ghostModelChangeListener.unregisterListeners();
+ this.ghostModelChangeListener = null;
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/effects/GhostPaintingUtils.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/effects/GhostPaintingUtils.java
new file mode 100644
index 0000000..d947008
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/effects/GhostPaintingUtils.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2005-2007 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.animation.effects;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.swing.*;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.TimelineState;
+
+/**
+ * Utility class that implements the ghost effects.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class GhostPaintingUtils {
+ /**
+ * Minimal starting opacity for icon ghosting. Change to a higher value for
+ * debugging / demoing purposes.
+ */
+ public static float MIN_ICON_GHOSTING_ALPHA = 0.15f;
+
+ /**
+ * Maximal starting opacity for icon ghosting. Change to a higher value for
+ * debugging / demoing purposes.
+ */
+ public static float MAX_ICON_GHOSTING_ALPHA = 0.5f;
+
+ /**
+ * Minimal starting opacity for press ghosting. Change to a higher value for
+ * debugging / demoing purposes.
+ */
+ public static float MIN_PRESS_GHOSTING_ALPHA = 0.15f;
+
+ /**
+ * Maximal starting opacity for press ghosting. Change to a higher value for
+ * debugging / demoing purposes.
+ */
+ public static float MAX_PRESS_GHOSTING_ALPHA = 0.3f;
+
+ /**
+ * Global decay factor.
+ */
+ public static float DECAY_FACTOR = 1.0f;
+
+ /**
+ * Cache of component ghost images. Used to speed up the rendering of the
+ * ghost effects.
+ */
+ private static LinkedHashMap<String, BufferedImage> componentGhostCache = new LinkedHashMap<String, BufferedImage>() {
+ @Override
+ protected boolean removeEldestEntry(
+ java.util.Map.Entry<String, BufferedImage> eldest) {
+ return this.size() > 50;
+ }
+ };
+
+ /**
+ * Cache of icon ghost images. Used to speed up the rendering of the ghost
+ * effects.
+ */
+ private static LinkedHashMap<String, BufferedImage> iconGhostCache = new LinkedHashMap<String, BufferedImage>() {
+ @Override
+ protected boolean removeEldestEntry(
+ java.util.Map.Entry<String, BufferedImage> eldest) {
+ return this.size() > 50;
+ }
+ };
+
+ /**
+ * Returns a scaled ghost image of the specified component.
+ *
+ * @param comp
+ * Component.
+ * @param scaleFactor
+ * Scale factor.
+ * @return A scaled ghost image of the specified component.
+ */
+ protected static synchronized BufferedImage getComponentGhostImage(
+ JComponent comp, Timeline ghostPressTimeline, double scaleFactor) {
+ String key = ghostPressTimeline.getTimelinePosition() + ":"
+ + comp.hashCode() + ":" + scaleFactor;
+
+ BufferedImage result = componentGhostCache.get(key);
+ if (result == null) {
+ Rectangle bounds = comp.getBounds();
+
+ double iWidth = bounds.width * scaleFactor;
+ double iHeight = bounds.height * scaleFactor;
+ result = LafWidgetUtilities.getBlankImage((int) iWidth,
+ (int) iHeight);
+ Graphics2D iGraphics = result.createGraphics();
+ iGraphics.scale(scaleFactor, scaleFactor);
+ comp.paint(iGraphics);
+ iGraphics.dispose();
+
+ componentGhostCache.put(key, result);
+ }
+ return result;
+ }
+
+ /**
+ * Returns a scaled ghost image of the specified icon.
+ *
+ * @param comp
+ * Component.
+ * @param icon
+ * Icon.
+ * @param scaleFactor
+ * Scale factor.
+ * @return A scaled ghost image of the specified icon.
+ */
+ protected static synchronized BufferedImage getIconGhostImage(
+ JComponent comp, Timeline ghostRolloverTimeline, Icon icon,
+ double scaleFactor) {
+ String key = ghostRolloverTimeline.getTimelinePosition() + ":"
+ + comp.hashCode() + ":" + icon.hashCode() + ":" + scaleFactor;
+
+ BufferedImage result = iconGhostCache.get(key);
+ if (result == null) {
+ int oWidth = icon.getIconWidth();
+ int oHeight = icon.getIconHeight();
+ double iWidth = oWidth * scaleFactor;
+ double iHeight = oHeight * scaleFactor;
+ result = LafWidgetUtilities.getBlankImage((int) iWidth,
+ (int) iHeight);
+ Graphics2D iGraphics = result.createGraphics();
+ iGraphics.scale(scaleFactor, scaleFactor);
+ icon.paintIcon(comp, iGraphics, 0, 0);
+ iGraphics.dispose();
+
+ iconGhostCache.put(key, result);
+ }
+ return result;
+ }
+
+ /**
+ * Paints ghost images on the specified component.
+ *
+ * @param mainComponent
+ * Component.
+ * @param g
+ * Graphics context.
+ */
+ public static void paintGhostImages(Component mainComponent, Graphics g) {
+ if (!mainComponent.isShowing())
+ return;
+ if (!mainComponent.isVisible())
+ return;
+ // The following check is for offscreen rendering. The component
+ // may be showing and visible, but have no peer (non displayable).
+ if (!mainComponent.isDisplayable())
+ return;
+ if (SwingUtilities.getWindowAncestor(mainComponent) == null)
+ return;
+
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ Rectangle mainRect = mainComponent.getBounds();
+ mainRect.setLocation(mainComponent.getLocationOnScreen());
+ if (AnimationConfigurationManager.getInstance().isAnimationAllowed(
+ AnimationFacet.GHOSTING_BUTTON_PRESS, mainComponent)) {
+ Map<JComponent, Timeline> runningGhostPressTimelines = GhostingListener
+ .getRunningGhostPressTimelines();
+ for (Map.Entry<JComponent, Timeline> entry : runningGhostPressTimelines
+ .entrySet()) {
+ JComponent comp = entry.getKey();
+ Timeline timeline = entry.getValue();
+
+ if (comp == mainComponent)
+ continue;
+
+ if (!comp.isShowing())
+ continue;
+ if (!comp.isVisible())
+ continue;
+ // The following check is for offscreen rendering. The component
+ // may be showing and visible, but have no peer (non
+ // displayable).
+ if (!comp.isDisplayable())
+ return;
+
+ Rectangle compRect = comp.getBounds();
+ compRect.setLocation(comp.getLocationOnScreen());
+
+ int dx = compRect.x - mainRect.x;
+ int dy = compRect.y - mainRect.y;
+
+ compRect.x -= compRect.width / 2;
+ compRect.y -= compRect.height / 2;
+ compRect.width *= 2;
+ compRect.height *= 2;
+
+ if (mainRect.intersects(compRect)) {
+ float fade = timeline.getTimelinePosition();
+ // 0.0 --> 0.3
+ // 1.0 --> 0.0
+ double start = MAX_PRESS_GHOSTING_ALPHA - 0.0015
+ * compRect.getWidth();
+ float coef = Math.max((float) start,
+ MIN_PRESS_GHOSTING_ALPHA);
+ float opFactor = coef * (1.0f - DECAY_FACTOR * fade);
+ double iFactor = 1.0 + fade;
+
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ mainComponent, opFactor));
+
+ Rectangle bounds = comp.getBounds();
+
+ BufferedImage ghost = getComponentGhostImage(comp,
+ timeline, iFactor);
+ dx -= ((ghost.getWidth() - bounds.width) / 2);
+ dy -= ((ghost.getHeight() - bounds.height) / 2);
+ graphics.drawImage(ghost, dx, dy, null);
+ }
+ }
+ }
+
+ if (AnimationConfigurationManager.getInstance().isAnimationAllowed(
+ AnimationFacet.GHOSTING_ICON_ROLLOVER, mainComponent)) {
+ Map<JComponent, Timeline> runningGhostRolloverTimelines = GhostingListener
+ .getRunningGhostRolloverTimelines();
+ for (Map.Entry<JComponent, Timeline> entry : runningGhostRolloverTimelines
+ .entrySet()) {
+ JComponent comp = entry.getKey();
+ Timeline timeline = entry.getValue();
+ if (comp == mainComponent)
+ continue;
+
+ if (!(comp instanceof JComponent))
+ continue;
+
+ JComponent jc = (JComponent) comp;
+
+ if (!jc.isShowing())
+ continue;
+ if (!jc.isVisible())
+ continue;
+
+ Rectangle compRect = jc.getBounds();
+ compRect.setLocation(jc.getLocationOnScreen());
+
+ int dx = compRect.x - mainRect.x;
+ int dy = compRect.y - mainRect.y;
+
+ compRect.x -= compRect.width / 2;
+ compRect.y -= compRect.height / 2;
+ compRect.width *= 2;
+ compRect.height *= 2;
+
+ if (mainRect.intersects(compRect)) {
+ float fade = timeline.getTimelinePosition();
+ // Rectangle bounds = comp.getBounds();
+ Icon icon = null;
+ Rectangle iconRect = (Rectangle) jc
+ .getClientProperty("icon.bounds");
+ if (iconRect != null) {
+ if (jc instanceof AbstractButton) {
+ icon = LafWidgetUtilities
+ .getIcon((AbstractButton) jc);
+ } else {
+ icon = (Icon) jc.getClientProperty("icon");
+ }
+ }
+
+ if ((icon != null) && (iconRect != null)) {
+ double iFactor = 1.0 + fade;
+ // double iWidth = icon.getIconWidth() * iFactor;
+ // double iHeight = icon.getIconHeight() * iFactor;
+ // BufferedImage iImage = LafWidgetUtilities
+ // .getBlankImage((int) iWidth, (int) iHeight);
+ // Graphics2D iGraphics = (Graphics2D) iImage
+ // .createGraphics();
+ // iGraphics.scale(iFactor, iFactor);
+ // icon.paintIcon(comp, iGraphics, 0, 0);
+ // iGraphics.dispose();
+
+ BufferedImage iImage = getIconGhostImage(comp,
+ timeline, icon, iFactor);
+
+ // System.out.println(iconRect);
+
+ // BufferedImage bImage = SubstanceCoreUtilities.blur(
+ // iImage, 2);
+
+ int iWidth = iImage.getWidth();
+ int iHeight = iImage.getHeight();
+ dx -= ((iWidth - icon.getIconWidth()) / 2);
+ dy -= ((iHeight - icon.getIconHeight()) / 2);
+
+ double start = MAX_ICON_GHOSTING_ALPHA
+ - (MAX_ICON_GHOSTING_ALPHA - MIN_ICON_GHOSTING_ALPHA)
+ * (iWidth - 16) / 48;
+ float coef = Math.max((float) start,
+ MIN_ICON_GHOSTING_ALPHA);
+ float opFactor = coef * (1.0f - DECAY_FACTOR * fade);
+ graphics.setComposite(LafWidgetUtilities
+ .getAlphaComposite(mainComponent, opFactor));
+
+ graphics.drawImage(iImage, dx + iconRect.x, dy
+ + iconRect.y, null);
+ }
+ }
+ }
+ }
+ graphics.dispose();
+ }
+
+ /**
+ * Paints the ghost icon inside the bounds of the specified button.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param b
+ * Button.
+ * @param icon
+ * Icon to paint.
+ */
+ public static void paintGhostIcon(Graphics2D graphics, AbstractButton b,
+ Icon icon) {
+ paintGhostIcon(graphics, b, icon, (Rectangle) b
+ .getClientProperty("icon.bounds"));
+ }
+
+ /**
+ * Paints the ghost icon inside the bounds of the specified button.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param b
+ * Button.
+ * @param iconRectangle
+ * Rectangle of the button icon.
+ */
+ public static void paintGhostIcon(Graphics2D graphics, AbstractButton b,
+ Rectangle iconRectangle) {
+ paintGhostIcon(graphics, b, LafWidgetUtilities.getIcon(b),
+ iconRectangle);
+ }
+
+ /**
+ * Paints the ghost icon inside the bounds of the specified button.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param b
+ * Button.
+ * @param icon
+ * Icon to paint.
+ * @param iconRectangle
+ * Rectangle of the button icon.
+ */
+ public static void paintGhostIcon(Graphics2D graphics, Component b,
+ Icon icon, Rectangle iconRectangle) {
+ // System.out.println(b.getText() + ":" + icon + ":" + iconRectangle);
+ if (!AnimationConfigurationManager.getInstance().isAnimationAllowed(
+ AnimationFacet.GHOSTING_ICON_ROLLOVER, b)) {
+ return;
+ }
+
+ if (!(b instanceof JComponent))
+ return;
+
+ GhostingListener gl = (GhostingListener) ((JComponent) b)
+ .getClientProperty(GhostingListener.GHOST_LISTENER_KEY);
+ if (gl == null)
+ return;
+
+ Timeline ghostRolloverTimeline = gl.getGhostIconRolloverTimeline();
+
+ if (ghostRolloverTimeline.getState() != TimelineState.IDLE) {
+ float fade = ghostRolloverTimeline.getTimelinePosition();
+ if ((icon != null) && (iconRectangle != null)) {
+ double iFactor = 1.0 + fade;
+ BufferedImage iImage = getIconGhostImage((JComponent) b,
+ ghostRolloverTimeline, icon, iFactor);
+
+ int iWidth = iImage.getWidth();
+ int iHeight = iImage.getHeight();
+ int dx = ((iWidth - icon.getIconWidth()) / 2);
+ int dy = ((iHeight - icon.getIconHeight()) / 2);
+
+ double start = MAX_ICON_GHOSTING_ALPHA
+ - (MAX_ICON_GHOSTING_ALPHA - MIN_ICON_GHOSTING_ALPHA)
+ * (iWidth - 16) / 48;
+ float coef = Math.max((float) start, MIN_ICON_GHOSTING_ALPHA);
+ float opFactor = coef * (1.0f - DECAY_FACTOR * fade);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(b,
+ opFactor));
+
+ graphics.drawImage(iImage, iconRectangle.x - dx,
+ iconRectangle.y - dy, null);
+ }
+ }
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/effects/GhostingListener.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/effects/GhostingListener.java
new file mode 100644
index 0000000..261ae52
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/animation/effects/GhostingListener.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2005-2007 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.animation.effects;
+
+import java.awt.*;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.*;
+import org.pushingpixels.trident.swing.SwingRepaintCallback;
+
+/**
+ * Listener for the "ghosting image" effects on buttons.
+ *
+ * @author Kirill Grouchnikov
+ * @see AnimationFacet#GHOSTING_BUTTON_PRESS
+ * @see AnimationFacet#GHOSTING_ICON_ROLLOVER
+ */
+public class GhostingListener {
+ /**
+ * Listener on the model changes.
+ */
+ protected ChangeListener modelListener;
+
+ /**
+ * The associated component.
+ */
+ protected JComponent comp;
+
+ /**
+ * The associated model.
+ */
+ protected ButtonModel buttonModel;
+
+ static final String GHOST_LISTENER_KEY = "lafwidget.internal.ghostListenerKey";
+
+ /**
+ * Key - {@link AnimationFacet}, value - {@link Boolean}
+ */
+ protected Map<AnimationFacet, Boolean> prevStateMap;
+
+ private Timeline ghostIconRolloverTimeline;
+
+ private Timeline ghostComponentPressedTimeline;
+
+ private static Map<JComponent, Timeline> runningGhostRolloverTimelines = new HashMap<JComponent, Timeline>();
+
+ private static Map<JComponent, Timeline> runningGhostPressTimelines = new HashMap<JComponent, Timeline>();
+
+ /**
+ * Creates a new listener on model changes that can cause ghost animation
+ * transitions.
+ *
+ * @param comp
+ * Component.
+ * @param buttonModel
+ * Model for the component.
+ */
+ public GhostingListener(final JComponent comp, ButtonModel buttonModel) {
+ this.comp = comp;
+ this.buttonModel = buttonModel;
+
+ this.prevStateMap = new HashMap<AnimationFacet, Boolean>();
+ this.prevStateMap.put(AnimationFacet.GHOSTING_ICON_ROLLOVER,
+ buttonModel.isRollover());
+ this.prevStateMap.put(AnimationFacet.GHOSTING_BUTTON_PRESS, buttonModel
+ .isPressed());
+
+ this.ghostIconRolloverTimeline = new Timeline(comp);
+ AnimationConfigurationManager.getInstance().configureTimeline(
+ this.ghostIconRolloverTimeline);
+ this.ghostIconRolloverTimeline.addCallback(new SwingRepaintCallback(
+ comp));
+
+ this.ghostComponentPressedTimeline = new Timeline(comp);
+ AnimationConfigurationManager.getInstance().configureTimeline(
+ this.ghostComponentPressedTimeline);
+ this.ghostComponentPressedTimeline
+ .addCallback(new SwingRepaintCallback(comp));
+
+ TimelineCallback ghostCallback = new UIThreadTimelineCallbackAdapter() {
+ private boolean wasShowing = true;
+
+ protected void repaintTopLevelWindows(float timelinePosition) {
+ if (comp == null)
+ return;
+
+ boolean isShowing = comp.isShowing();
+ if (isShowing) {
+ Window compWindow = SwingUtilities.getWindowAncestor(comp);
+ if (!compWindow.isDisplayable() || !compWindow.isShowing()
+ || !compWindow.isVisible()) {
+ isShowing = false;
+ }
+ }
+
+ if (!isShowing) {
+ if (wasShowing) {
+ // need to repaint all other windows
+ // once - otherwise we will see
+ // painting artifacts from
+ // a pressed button that was made
+ // invisible
+ for (Window w : Window.getWindows()) {
+ if (w.isDisplayable() && w.isVisible()
+ && w.isShowing()) {
+ w.repaint();
+ }
+ }
+ }
+ wasShowing = false;
+ return;
+ }
+ Component root = SwingUtilities.getRoot(comp);
+ Rectangle compRect = comp.getBounds();
+ compRect.setLocation(comp.getLocationOnScreen());
+ compRect.x -= compRect.width / 2;
+ compRect.y -= compRect.height / 2;
+ compRect.width *= 2;
+ compRect.height *= 2;
+ int rootRepaintX = compRect.x - root.getLocationOnScreen().x;
+ int rootRepaintY = compRect.y - root.getLocationOnScreen().y;
+
+ root.repaint(rootRepaintX, rootRepaintY, compRect.width,
+ compRect.height);
+
+ // fix for issue 363 on Substance - repaint
+ // all top-level windows that intersect
+ // with this rectangle
+ for (Window w : Window.getWindows()) {
+ if (w == root)
+ continue;
+ if (w.isDisplayable() && w.isVisible() && w.isShowing()) {
+ if (w.getBounds().intersects(compRect)) {
+ int winRepaintX = compRect.x
+ - w.getLocationOnScreen().x;
+ int winRepaintY = compRect.y
+ - w.getLocationOnScreen().y;
+ w.repaint(winRepaintX, winRepaintY, compRect.width,
+ compRect.height);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ if ((oldState == TimelineState.DONE)
+ && (newState == TimelineState.IDLE)) {
+ this.repaintTopLevelWindows(1.0f);
+ }
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ this.repaintTopLevelWindows(timelinePosition);
+ }
+ };
+
+ this.ghostIconRolloverTimeline.addCallback(ghostCallback);
+ this.ghostComponentPressedTimeline.addCallback(ghostCallback);
+
+ this.ghostIconRolloverTimeline
+ .addCallback(new TimelineCallbackAdapter() {
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ if ((oldState == TimelineState.DONE)
+ && (newState == TimelineState.IDLE)) {
+ synchronized (GhostingListener.class) {
+ runningGhostRolloverTimelines
+ .remove(ghostIconRolloverTimeline);
+ }
+ }
+ }
+ });
+
+ this.ghostComponentPressedTimeline
+ .addCallback(new TimelineCallbackAdapter() {
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ if ((oldState == TimelineState.DONE)
+ && (newState == TimelineState.IDLE)) {
+ synchronized (GhostingListener.class) {
+ runningGhostPressTimelines
+ .remove(ghostComponentPressedTimeline);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Tracks a single change to the model.
+ *
+ * @param animationFacet
+ * Animation facet.
+ * @param newState
+ * New value of the relevant attribute of the model.
+ */
+ protected void trackModelChange(AnimationFacet animationFacet,
+ boolean newState) {
+ if (LafWidgetUtilities.toIgnoreAnimations(this.comp))
+ return;
+ try {
+ if (this.prevStateMap.containsKey(animationFacet)) {
+ boolean prevState = this.prevStateMap.get(animationFacet);
+ if (!prevState && newState) {
+ if (animationFacet == AnimationFacet.GHOSTING_ICON_ROLLOVER) {
+ synchronized (GhostingListener.class) {
+ runningGhostRolloverTimelines.put(comp,
+ ghostIconRolloverTimeline);
+ }
+ ghostIconRolloverTimeline.play();
+ }
+ if (animationFacet == AnimationFacet.GHOSTING_BUTTON_PRESS) {
+ synchronized (GhostingListener.class) {
+ runningGhostPressTimelines.put(comp,
+ ghostComponentPressedTimeline);
+ }
+ ghostComponentPressedTimeline.play();
+ }
+ }
+ }
+ } finally {
+ this.prevStateMap.put(animationFacet, newState);
+ }
+ }
+
+ /**
+ * Registers listeners on the relevant model changes.
+ */
+ public void registerListeners() {
+ this.modelListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (AnimationConfigurationManager.getInstance()
+ .isAnimationAllowed(
+ AnimationFacet.GHOSTING_ICON_ROLLOVER, comp)) {
+ trackModelChange(AnimationFacet.GHOSTING_ICON_ROLLOVER,
+ buttonModel.isRollover());
+ }
+ if (AnimationConfigurationManager.getInstance()
+ .isAnimationAllowed(
+ AnimationFacet.GHOSTING_BUTTON_PRESS, comp)) {
+ trackModelChange(AnimationFacet.GHOSTING_BUTTON_PRESS,
+ buttonModel.isPressed());
+ }
+ }
+ };
+ this.buttonModel.addChangeListener(this.modelListener);
+ this.comp.putClientProperty(GHOST_LISTENER_KEY, this);
+ }
+
+ /**
+ * Unregisters all listeners on model changes.
+ */
+ public void unregisterListeners() {
+ this.buttonModel.removeChangeListener(this.modelListener);
+ this.comp.putClientProperty(GHOST_LISTENER_KEY, null);
+ }
+
+ public static synchronized Map<JComponent, Timeline> getRunningGhostRolloverTimelines() {
+ return Collections.unmodifiableMap(runningGhostRolloverTimelines);
+ }
+
+ public static synchronized Map<JComponent, Timeline> getRunningGhostPressTimelines() {
+ return Collections.unmodifiableMap(runningGhostPressTimelines);
+ }
+
+ public Timeline getGhostComponentPressedTimeline() {
+ return ghostComponentPressedTimeline;
+ }
+
+ public Timeline getGhostIconRolloverTimeline() {
+ return ghostIconRolloverTimeline;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentContainerGhostingTask.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentContainerGhostingTask.java
new file mode 100644
index 0000000..3ff391b
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentContainerGhostingTask.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import java.io.File;
+import java.util.*;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Ant task for augmenting LAF classes with image ghosting functionality. Is
+ * based on JiBX ant task (BSD-licensed).
+ *
+ * @author Kirill Grouchnikov.
+ */
+public class AugmentContainerGhostingTask extends Task {
+ /**
+ * Verbosty indication.
+ */
+ private boolean m_verbose;
+
+ /**
+ * Classpath.
+ */
+ private Path m_classpath;
+
+ /**
+ * Fileset.
+ */
+ private List<FileSet> m_fileSet;
+
+ /**
+ * List of delegates to update.
+ */
+ private List<ContainerGhostingType> m_delegatesToUpdate;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.tools.ant.Task#init()
+ */
+ @Override
+ public void init() throws BuildException {
+ super.init();
+ this.m_fileSet = new ArrayList<FileSet>();
+ this.m_delegatesToUpdate = new ArrayList<ContainerGhostingType>();
+ }
+
+ /**
+ * Returns an array of paths of all the files specified by the
+ * <classpath> or <classpathset> tags. Note that the
+ * <classpath> and <classpathset> tags cannot both be specified.
+ *
+ * @return Array of file paths.
+ */
+ private String[] getPaths() {
+ String[] pathArray = null;
+ if (this.m_classpath != null) {
+ //
+ // If a <classpath> tag has been set, m_classpath will
+ // not be null. In this case, just return the array of
+ // paths directly.
+ //
+ pathArray = this.m_classpath.list();
+ } else {
+ //
+ // Store the directory paths specified by each of the
+ // <classpathset> tags.
+ //
+ pathArray = new String[this.m_fileSet.size()];
+
+ for (int i = 0; i < this.m_fileSet.size(); i++) {
+ FileSet fileSet = this.m_fileSet.get(i);
+ File directory = fileSet.getDir(this.getProject());
+ pathArray[i] = directory.getAbsolutePath();
+ }
+ }
+ return pathArray;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.tools.ant.Task#execute()
+ */
+ @Override
+ public void execute() throws BuildException {
+ try {
+ // System.out.println(this.m_verbose + ", " + this.m_pattern);
+ //
+ String[] pathArr = this.getPaths();
+ ContainerGhostingAugmenter augmenter = new ContainerGhostingAugmenter();
+ if (this.m_verbose)
+ augmenter.setVerbose(this.m_verbose);
+
+ for (int i = 0; i < pathArr.length; i++) {
+ augmenter.process(pathArr[0], new File(pathArr[0]),
+ this.m_delegatesToUpdate);
+ }
+ } catch (AugmentException ae) {
+ throw new BuildException(ae);
+ }
+ // process(this.m_classpath, new File(args[0]), p);
+ }
+
+ /**
+ * @param fSet
+ */
+ public void addClassPathSet(FileSet fSet) {
+ this.m_fileSet.add(fSet);
+ }
+
+ /**
+ * Adds information on a single class-method pair for injecting the
+ * container ghosting code.
+ *
+ * @param containerGhosting
+ * Class-method pair for injecting the container ghosting code.
+ */
+ public void addContainerGhosting(ContainerGhostingType containerGhosting) {
+ this.m_delegatesToUpdate.add(containerGhosting);
+ }
+
+ /**
+ * Returns the current classpath.
+ *
+ * @return Current classpath.
+ */
+ public Path getClasspath() {
+ return this.m_classpath;
+ }
+
+ /**
+ * Sets the classpath for this task. Multiple calls append the new classpath
+ * to the current one, rather than overwriting it.
+ *
+ * @param classpath
+ * The new classpath as a Path object.
+ */
+ public void setClasspath(Path classpath) {
+ if (this.m_classpath == null) {
+ this.m_classpath = classpath;
+ } else {
+ this.m_classpath.append(classpath);
+ }
+ }
+
+ /**
+ * Creates the classpath for this task and returns it. If the classpath has
+ * already been created, the method just returns that one.
+ *
+ * @return The created classpath.
+ */
+ public Path createClasspath() {
+ if (this.m_classpath == null) {
+ this.m_classpath = new Path(this.getProject());
+ }
+
+ return this.m_classpath;
+ }
+
+ /**
+ * @param bool
+ */
+ public void setVerbose(boolean bool) {
+ this.m_verbose = bool;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentException.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentException.java
new file mode 100644
index 0000000..fb2da2a
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+/**
+ * Exception thrown when augmentation failed.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class AugmentException extends RuntimeException {
+
+ /**
+ * Creates a new exception.
+ *
+ * @param message
+ * Message.
+ * @param cause
+ * Cause.
+ */
+ public AugmentException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Creates a new exception.
+ *
+ * @param message
+ * Message.
+ */
+ public AugmentException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentIconGhostingTask.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentIconGhostingTask.java
new file mode 100644
index 0000000..fde0227
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentIconGhostingTask.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import java.io.File;
+import java.util.*;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Ant task for augmenting LAF classes with icon ghosting functionality. Is
+ * based on JiBX ant task (BSD-licensed).
+ *
+ * @author Kirill Grouchnikov.
+ */
+public class AugmentIconGhostingTask extends Task {
+ /**
+ * Verbosty indication.
+ */
+ private boolean m_verbose;
+
+ /**
+ * Classpath.
+ */
+ private Path m_classpath;
+
+ /**
+ * Fileset.
+ */
+ private List<FileSet> m_fileSet;
+
+ /**
+ * List of delegates to create.
+ */
+ private List<IconGhostingType> m_delegatesToUpdate;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.tools.ant.Task#init()
+ */
+ @Override
+ public void init() throws BuildException {
+ super.init();
+ this.m_fileSet = new ArrayList<FileSet>();
+ this.m_delegatesToUpdate = new ArrayList<IconGhostingType>();
+ }
+
+ /**
+ * Returns an array of paths of all the files specified by the
+ * <classpath> or <classpathset> tags. Note that the
+ * <classpath> and <classpathset> tags cannot both be specified.
+ *
+ * @return Array of file paths.
+ */
+ private String[] getPaths() {
+ String[] pathArray = null;
+ if (this.m_classpath != null) {
+ //
+ // If a <classpath> tag has been set, m_classpath will
+ // not be null. In this case, just return the array of
+ // paths directly.
+ //
+ pathArray = this.m_classpath.list();
+ } else {
+ //
+ // Store the directory paths specified by each of the
+ // <classpathset> tags.
+ //
+ pathArray = new String[this.m_fileSet.size()];
+
+ for (int i = 0; i < this.m_fileSet.size(); i++) {
+ FileSet fileSet = this.m_fileSet.get(i);
+ File directory = fileSet.getDir(this.getProject());
+ pathArray[i] = directory.getAbsolutePath();
+ }
+ }
+ return pathArray;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.tools.ant.Task#execute()
+ */
+ @Override
+ public void execute() throws BuildException {
+ try {
+ // System.out.println(this.m_verbose + ", " + this.m_pattern);
+ //
+
+ for (Iterator<IconGhostingType> it = this.m_delegatesToUpdate
+ .iterator(); it.hasNext();) {
+ IconGhostingType igt = it.next();
+ if (this.m_verbose)
+ System.out.println("Will inject icon ghosting code in "
+ + igt.getClassName() + "." + igt.getMethodName());
+ }
+
+ String[] pathArr = this.getPaths();
+ IconGhostingAugmenter augmenter = new IconGhostingAugmenter();
+ if (this.m_verbose)
+ augmenter.setVerbose(this.m_verbose);
+
+ for (int i = 0; i < pathArr.length; i++) {
+ augmenter.process(pathArr[0], new File(pathArr[0]),
+ this.m_delegatesToUpdate);
+ }
+ } catch (AugmentException ae) {
+ throw new BuildException(ae);
+ }
+ // process(this.m_classpath, new File(args[0]), p);
+ }
+
+ /**
+ * @param fSet
+ */
+ public void addClassPathSet(FileSet fSet) {
+ this.m_fileSet.add(fSet);
+ }
+
+ /**
+ * Adds information on a single class-method pair for injecting the icon
+ * ghosting code.
+ *
+ * @param iconGhosting
+ * Class-method pair for injecting the icon ghosting code.
+ */
+ public void addIconGhosting(IconGhostingType iconGhosting) {
+ this.m_delegatesToUpdate.add(iconGhosting);
+ }
+
+ /**
+ * Returns the current classpath.
+ *
+ * @return Current classpath.
+ */
+ public Path getClasspath() {
+ return this.m_classpath;
+ }
+
+ /**
+ * Sets the classpath for this task. Multiple calls append the new classpath
+ * to the current one, rather than overwriting it.
+ *
+ * @param classpath
+ * The new classpath as a Path object.
+ */
+ public void setClasspath(Path classpath) {
+ if (this.m_classpath == null) {
+ this.m_classpath = classpath;
+ } else {
+ this.m_classpath.append(classpath);
+ }
+ }
+
+ /**
+ * Creates the classpath for this task and returns it. If the classpath has
+ * already been created, the method just returns that one.
+ *
+ * @return The created classpath.
+ */
+ public Path createClasspath() {
+ if (this.m_classpath == null) {
+ this.m_classpath = new Path(this.getProject());
+ }
+
+ return this.m_classpath;
+ }
+
+ /**
+ * @param bool
+ */
+ public void setVerbose(boolean bool) {
+ this.m_verbose = bool;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentMainTask.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentMainTask.java
new file mode 100644
index 0000000..6904054
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentMainTask.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import java.io.File;
+import java.util.*;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Ant task for augmenting main LAF class. Is based on JiBX ant task
+ * (BSD-licensed).
+ *
+ * @author Kirill Grouchnikov.
+ */
+public class AugmentMainTask extends Task {
+ /**
+ * Verbosty indication.
+ */
+ private boolean m_verbose;
+
+ /**
+ * Classpath.
+ */
+ private Path m_classpath;
+
+ /**
+ * Fileset.
+ */
+ private List<FileSet> m_fileSet;
+
+ /**
+ * Main LAF classname.
+ */
+ private String m_mainLafClassName;
+
+ /**
+ * List of delegates to create.
+ */
+ private List<UiDelegateType> m_delegatesToCreate;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.tools.ant.Task#init()
+ */
+ @Override
+ public void init() throws BuildException {
+ super.init();
+ this.m_fileSet = new ArrayList<FileSet>();
+ this.m_delegatesToCreate = new ArrayList<UiDelegateType>();
+ }
+
+ /**
+ * Returns an array of paths of all the files specified by the
+ * <classpath> or <classpathset> tags. Note that the
+ * <classpath> and <classpathset> tags cannot both be specified.
+ *
+ * @return Array of file paths.
+ */
+ private String[] getPaths() {
+ String[] pathArray = null;
+ if (this.m_classpath != null) {
+ //
+ // If a <classpath> tag has been set, m_classpath will
+ // not be null. In this case, just return the array of
+ // paths directly.
+ //
+ pathArray = this.m_classpath.list();
+ } else {
+ //
+ // Store the directory paths specified by each of the
+ // <classpathset> tags.
+ //
+ pathArray = new String[this.m_fileSet.size()];
+
+ for (int i = 0; i < this.m_fileSet.size(); i++) {
+ FileSet fileSet = this.m_fileSet.get(i);
+ File directory = fileSet.getDir(this.getProject());
+ pathArray[i] = directory.getAbsolutePath();
+ }
+ }
+ return pathArray;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.tools.ant.Task#execute()
+ */
+ @Override
+ public void execute() throws BuildException {
+ try {
+ // System.out.println(this.m_verbose + ", " + this.m_pattern);
+ //
+
+ for (Iterator<UiDelegateType> it = this.m_delegatesToCreate
+ .iterator(); it.hasNext();) {
+ UiDelegateType dtc = it.next();
+ if (this.m_verbose)
+ System.out.println("Will create forwarding delegate for : "
+ + dtc.getName());
+ }
+
+ String[] pathArr = this.getPaths();
+ LafMainClassAugmenter augmenter = new LafMainClassAugmenter();
+ if (this.m_verbose)
+ augmenter.setVerbose(this.m_verbose);
+
+ for (int i = 0; i < pathArr.length; i++) {
+ String[] delegates = new String[this.m_delegatesToCreate.size()];
+ int count = 0;
+ for (Iterator<UiDelegateType> it = this.m_delegatesToCreate.iterator(); it
+ .hasNext();) {
+ UiDelegateType dtc = it.next();
+ delegates[count++] = dtc.getName();
+ }
+ augmenter.setDelegatesToAdd(delegates);
+ augmenter.process(pathArr[0], new File(pathArr[0]),
+ this.m_mainLafClassName);
+ }
+ } catch (AugmentException ae) {
+ throw new BuildException(ae);
+ }
+ // process(this.m_classpath, new File(args[0]), p);
+ }
+
+ /**
+ * @param fSet
+ */
+ public void addClassPathSet(FileSet fSet) {
+ this.m_fileSet.add(fSet);
+ }
+
+ /**
+ * @param delegate
+ */
+ public void addDelegate(UiDelegateType delegate) {
+ this.m_delegatesToCreate.add(delegate);
+ }
+
+ /**
+ * Returns the current classpath.
+ *
+ * @return Current classpath.
+ */
+ public Path getClasspath() {
+ return this.m_classpath;
+ }
+
+ /**
+ * Sets the classpath for this task. Multiple calls append the new classpath
+ * to the current one, rather than overwriting it.
+ *
+ * @param classpath
+ * The new classpath as a Path object.
+ */
+ public void setClasspath(Path classpath) {
+ if (this.m_classpath == null) {
+ this.m_classpath = classpath;
+ } else {
+ this.m_classpath.append(classpath);
+ }
+ }
+
+ /**
+ * Creates the classpath for this task and returns it. If the classpath has
+ * already been created, the method just returns that one.
+ *
+ * @return The created classpath.
+ */
+ public Path createClasspath() {
+ if (this.m_classpath == null) {
+ this.m_classpath = new Path(this.getProject());
+ }
+
+ return this.m_classpath;
+ }
+
+ /**
+ * @param string
+ */
+ public void setMainlafclassname(String string) {
+ this.m_mainLafClassName = string;
+ }
+
+ /**
+ * @param bool
+ */
+ public void setVerbose(boolean bool) {
+ this.m_verbose = bool;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentTask.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentTask.java
new file mode 100644
index 0000000..c5afb8a
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentTask.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Ant task for augmenting UI delegates. Is based on JiBX ant task
+ * (BSD-licensed).
+ *
+ * @author Kirill Grouchnikov.
+ */
+public class AugmentTask extends Task {
+ /**
+ * Pattern for matching the UI delegates.
+ */
+ private String m_pattern;
+
+ /**
+ * Verbosity indication.
+ */
+ private boolean m_verbose;
+
+ /**
+ * Classpath.
+ */
+ private Path m_classpath;
+
+ /**
+ * Fileset.
+ */
+ private List<FileSet> m_fileSet;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.tools.ant.Task#init()
+ */
+ @Override
+ public void init() throws BuildException {
+ super.init();
+ this.m_fileSet = new ArrayList<FileSet>();
+ }
+
+ /**
+ * Returns an array of paths of all the files specified by the
+ * <classpath> or <classpathset> tags. Note that the
+ * <classpath> and <classpathset> tags cannot both be specified.
+ *
+ * @return Array of file paths.
+ */
+ private String[] getPaths() {
+ String[] pathArray = null;
+ if (this.m_classpath != null) {
+ //
+ // If a <classpath> tag has been set, m_classpath will
+ // not be null. In this case, just return the array of
+ // paths directly.
+ //
+ pathArray = this.m_classpath.list();
+ } else {
+ //
+ // Store the directory paths specified by each of the
+ // <classpathset> tags.
+ //
+ pathArray = new String[this.m_fileSet.size()];
+
+ for (int i = 0; i < this.m_fileSet.size(); i++) {
+ FileSet fileSet = this.m_fileSet.get(i);
+ File directory = fileSet.getDir(this.getProject());
+ pathArray[i] = directory.getAbsolutePath();
+ }
+ }
+ return pathArray;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.tools.ant.Task#execute()
+ */
+ @Override
+ public void execute() throws BuildException {
+ try {
+ String[] pathArr = this.getPaths();
+ UiDelegateAugmenter augmenter = new UiDelegateAugmenter();
+ if (this.m_verbose)
+ augmenter.setVerbose(this.m_verbose);
+
+ Pattern p = Pattern.compile(this.m_pattern);
+
+ for (int i = 0; i < pathArr.length; i++) {
+ augmenter.process(pathArr[0], new File(pathArr[0]), p);
+ }
+ } catch (AugmentException ae) {
+ throw new BuildException(ae);
+ }
+ // process(this.m_classpath, new File(args[0]), p);
+ }
+
+ /**
+ * @param fSet
+ */
+ public void addClassPathSet(FileSet fSet) {
+ this.m_fileSet.add(fSet);
+ }
+
+ /**
+ * Returns the current classpath.
+ *
+ * @return The current classpath.
+ */
+ public Path getClasspath() {
+ return this.m_classpath;
+ }
+
+ /**
+ * Sets the classpath for this task. Multiple calls append the new classpath
+ * to the current one, rather than overwriting it.
+ *
+ * @param classpath
+ * The new classpath as a Path object.
+ */
+ public void setClasspath(Path classpath) {
+ if (this.m_classpath == null) {
+ this.m_classpath = classpath;
+ } else {
+ this.m_classpath.append(classpath);
+ }
+ }
+
+ /**
+ * Creates the classpath for this task and returns it. If the classpath has
+ * already been created, the method just returns that one.
+ *
+ * @return The created classpath.
+ */
+ public Path createClasspath() {
+ if (this.m_classpath == null) {
+ this.m_classpath = new Path(this.getProject());
+ }
+
+ return this.m_classpath;
+ }
+
+ /**
+ * @param string
+ */
+ public void setPattern(String string) {
+ this.m_pattern = string;
+ }
+
+ /**
+ * @param bool
+ */
+ public void setVerbose(boolean bool) {
+ this.m_verbose = bool;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentUpdateTask.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentUpdateTask.java
new file mode 100644
index 0000000..2cf31e3
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/AugmentUpdateTask.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * Ant task for augmenting UI delegates. Is based on JiBX ant task
+ * (BSD-licensed).
+ *
+ * @author Kirill Grouchnikov.
+ */
+public class AugmentUpdateTask extends Task {
+ /**
+ * Pattern for matching the UI delegates.
+ */
+ private String m_pattern;
+
+ /**
+ * Verbosity indication.
+ */
+ private boolean m_verbose;
+
+ /**
+ * Classpath.
+ */
+ private Path m_classpath;
+
+ /**
+ * Fileset.
+ */
+ private List<FileSet> m_fileSet;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.tools.ant.Task#init()
+ */
+ @Override
+ public void init() throws BuildException {
+ super.init();
+ this.m_fileSet = new ArrayList<FileSet>();
+ }
+
+ /**
+ * Returns an array of paths of all the files specified by the
+ * <classpath> or <classpathset> tags. Note that the
+ * <classpath> and <classpathset> tags cannot both be specified.
+ *
+ * @return Array of file paths.
+ */
+ private String[] getPaths() {
+ String[] pathArray = null;
+ if (this.m_classpath != null) {
+ //
+ // If a <classpath> tag has been set, m_classpath will
+ // not be null. In this case, just return the array of
+ // paths directly.
+ //
+ pathArray = this.m_classpath.list();
+ } else {
+ //
+ // Store the directory paths specified by each of the
+ // <classpathset> tags.
+ //
+ pathArray = new String[this.m_fileSet.size()];
+
+ for (int i = 0; i < this.m_fileSet.size(); i++) {
+ FileSet fileSet = this.m_fileSet.get(i);
+ File directory = fileSet.getDir(this.getProject());
+ pathArray[i] = directory.getAbsolutePath();
+ }
+ }
+ return pathArray;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.tools.ant.Task#execute()
+ */
+ @Override
+ public void execute() throws BuildException {
+ try {
+ String[] pathArr = this.getPaths();
+ UiDelegateUpdateAugmenter augmenter = new UiDelegateUpdateAugmenter();
+ if (this.m_verbose)
+ augmenter.setVerbose(this.m_verbose);
+
+ Pattern p = Pattern.compile(this.m_pattern);
+
+ for (int i = 0; i < pathArr.length; i++) {
+ augmenter.process(pathArr[0], new File(pathArr[0]), p);
+ }
+ } catch (AugmentException ae) {
+ throw new BuildException(ae);
+ }
+ // process(this.m_classpath, new File(args[0]), p);
+ }
+
+ /**
+ * @param fSet
+ */
+ public void addClassPathSet(FileSet fSet) {
+ this.m_fileSet.add(fSet);
+ }
+
+ /**
+ * Returns the current classpath.
+ *
+ * @return The current classpath.
+ */
+ public Path getClasspath() {
+ return this.m_classpath;
+ }
+
+ /**
+ * Sets the classpath for this task. Multiple calls append the new classpath
+ * to the current one, rather than overwriting it.
+ *
+ * @param classpath
+ * The new classpath as a Path object.
+ */
+ public void setClasspath(Path classpath) {
+ if (this.m_classpath == null) {
+ this.m_classpath = classpath;
+ } else {
+ this.m_classpath.append(classpath);
+ }
+ }
+
+ /**
+ * Creates the classpath for this task and returns it. If the classpath has
+ * already been created, the method just returns that one.
+ *
+ * @return The created classpath.
+ */
+ public Path createClasspath() {
+ if (this.m_classpath == null) {
+ this.m_classpath = new Path(this.getProject());
+ }
+
+ return this.m_classpath;
+ }
+
+ /**
+ * @param string
+ */
+ public void setPattern(String string) {
+ this.m_pattern = string;
+ }
+
+ /**
+ * @param bool
+ */
+ public void setVerbose(boolean bool) {
+ this.m_verbose = bool;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/ContainerGhostingAugmenter.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/ContainerGhostingAugmenter.java
new file mode 100644
index 0000000..e065107
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/ContainerGhostingAugmenter.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import java.io.*;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.*;
+
+import org.objectweb.asm.*;
+
+/**
+ * Augments the UI classes with ghosting painting. Is based on sample adapter
+ * from ASM distribution.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ContainerGhostingAugmenter {
+ /**
+ * Verbosity indication.
+ */
+ private boolean isVerbose;
+
+ /**
+ * Adapter for augmenting a single class.
+ *
+ * @author Kirill Grouchnikov.
+ */
+ protected class AugmentClassAdapter extends ClassAdapter implements Opcodes {
+ /**
+ * Contains all method names.
+ */
+ private Set<String> existingMethods;
+
+ /**
+ * Contains all field names.
+ */
+ private Set<String> existingFields;
+
+ /**
+ * Method to augment.
+ */
+ private Method methodToAugment;
+
+ /**
+ * <code>true</code> if the code needs to be injected after the call to
+ * the original implementation.
+ */
+ private boolean toInjectAfterOriginal;
+
+ /**
+ * Prefix for delegate methods that will be added.
+ */
+ private String prefix;
+
+ /**
+ * Creates a new augmentor.
+ *
+ * @param cv
+ * Class visitor to recreate the non-augmented methods.
+ * @param existingMethods
+ * Contains all method names.
+ * @param existingFields
+ * Contains all field names.
+ * @param methodToAugment
+ * Method to augment.
+ * @param toInjectAfterOriginal
+ * <code>true</code> if the code needs to be injected after
+ * the call to the original implementation.
+ */
+ public AugmentClassAdapter(final ClassVisitor cv,
+ Set<String> existingMethods, Set<String> existingFields,
+ Method methodToAugment, boolean toInjectAfterOriginal) {
+ super(cv);
+ this.existingMethods = existingMethods;
+ this.existingFields = existingFields;
+ this.methodToAugment = methodToAugment;
+ this.toInjectAfterOriginal = toInjectAfterOriginal;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.objectweb.asm.ClassAdapter#visit(int, int, java.lang.String,
+ * java.lang.String, java.lang.String, java.lang.String[])
+ */
+ @Override
+ public void visit(final int version, final int access,
+ final String name, final String signature,
+ final String superName, final String[] interfaces) {
+ this.prefix = "__" + name.replaceAll("/", "__") + "__container__";
+ super
+ .visit(version, access, name, signature, superName,
+ interfaces);
+
+ // Check if need to add the containerGhostingMarker field (boolean)
+ if (!this.existingFields.contains("containerGhostingMarker")) {
+ FieldVisitor fv = this.visitField(Opcodes.ACC_PROTECTED,
+ "containerGhostingMarker", "Z", null, null);
+ fv.visitEnd();
+ }
+
+ // We have three separate cases for the method that we
+ // want to augment:
+ //
+ // 1. The current .class has both function and the __ version -
+ // already has been augmented. Can be ignored.
+ //
+ // 2. The current .class has function but doesn't have the __
+ // version. Than, the original function has already been renamed to
+ // __ (in the visitMethod). We need to create a new version for this
+ // function that performs pre-logic, calls __ and performs the
+ // post-logic.
+ //
+ // 3. The current .class doesn't have neither the function nor
+ // the __ version. In this case we need to create the __ version
+ // that calls super (with the original name) and the function that
+ // performs pre-logic, calls __ and performs the post-logic.
+
+ String methodName = methodToAugment.getName();
+ boolean hasOriginal = this.existingMethods.contains(methodName);
+ boolean hasDelegate = this.existingMethods.contains(this.prefix
+ + methodName);
+
+ String methodSignature = Utils.getMethodDesc(methodToAugment);
+ int paramCount = methodToAugment.getParameterTypes().length;
+ if (ContainerGhostingAugmenter.this.isVerbose)
+ System.out.println("... Augmenting " + methodName + " "
+ + methodSignature + " : original - " + hasOriginal
+ + ", delegate - " + hasDelegate + ", " + paramCount
+ + " params");
+
+ if (!hasDelegate) {
+ if (toInjectAfterOriginal) {
+ this.augmentUpdateMethodAfter(!hasOriginal, name,
+ superName, methodSignature);
+ } else {
+ this.augmentUpdateMethodBefore(!hasOriginal, name,
+ superName, methodSignature);
+ }
+ }
+ }
+
+ /**
+ * Augments the <code>update</code> method that is assumed to always
+ * have two parameters, injecting the ghosting code <b>before</b> the
+ * original implementation.
+ *
+ * @param toSynthOriginal
+ * Indication whether we need to create an empty (only call
+ * to super()) implementation.
+ * @param className
+ * Class name.
+ * @param superClassName
+ * Super class name (relevant for generating empty
+ * implementation).
+ * @param methodDesc
+ * Function signature (using JNI style declaration). Example
+ * for <code>void installUI(JButton button)</code>:
+ * <code>(Ljavax/swing/JButton;)V</code>.
+ */
+ public void augmentUpdateMethodBefore(boolean toSynthOriginal,
+ String className, String superClassName, String methodDesc) {
+ // Some ASM woodoo. The code below was generated by using
+ // ASMifierClassVisitor.
+ if (toSynthOriginal) {
+ MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PUBLIC,
+ this.prefix + "update",
+ "(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V", null,
+ null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName,
+ "update",
+ "(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 3);
+ mv.visitEnd();
+ }
+
+ MethodVisitor mv = this.cv.visitMethod(ACC_PROTECTED, "update",
+ methodDesc, null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitVarInsn(ALOAD, 1);
+ mv
+ .visitMethodInsn(
+ INVOKESTATIC,
+ "org/pushingpixels/lafwidget/animation/effects/GhostPaintingUtils",
+ "paintGhostImages",
+ "(Ljava/awt/Component;Ljava/awt/Graphics;)V");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(INVOKEVIRTUAL, className,
+ this.prefix + "update",
+ "(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 3);
+ mv.visitEnd();
+ }
+
+ /**
+ * Augments the <code>update</code> method that is assumed to always
+ * have two parameters, injecting the ghosting code <b>after</b> the
+ * original implementation.
+ *
+ * @param toSynthOriginal
+ * Indication whether we need to create an empty (only call
+ * to super()) implementation.
+ * @param className
+ * Class name.
+ * @param superClassName
+ * Super class name (relevant for generating empty
+ * implementation).
+ * @param methodDesc
+ * Function signature (using JNI style declaration). Example
+ * for <code>void installUI(JButton button)</code>:
+ * <code>(Ljavax/swing/JButton;)V</code>.
+ */
+ public void augmentUpdateMethodAfter(boolean toSynthOriginal,
+ String className, String superClassName, String methodDesc) {
+ // Some ASM woodoo. The code below was generated by using
+ // ASMifierClassVisitor.
+ if (toSynthOriginal) {
+ MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PUBLIC,
+ this.prefix + "update",
+ "(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V", null,
+ null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName,
+ "update",
+ "(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 3);
+ mv.visitEnd();
+ }
+
+ MethodVisitor mv = this.cv.visitMethod(ACC_PROTECTED, "update",
+ methodDesc, null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(INVOKEVIRTUAL, className,
+ this.prefix + "update",
+ "(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V");
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitVarInsn(ALOAD, 1);
+ mv
+ .visitMethodInsn(
+ INVOKESTATIC,
+ "org/pushingpixels/lafwidget/animation/effects/GhostPaintingUtils",
+ "paintGhostImages",
+ "(Ljava/awt/Component;Ljava/awt/Graphics;)V");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 3);
+ mv.visitEnd();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.objectweb.asm.ClassAdapter#visitMethod(int,
+ * java.lang.String, java.lang.String, java.lang.String,
+ * java.lang.String[])
+ */
+ @Override
+ public MethodVisitor visitMethod(final int access, final String name,
+ final String desc, final String signature,
+ final String[] exceptions) {
+ if (methodToAugment.getName().equals(name)) {
+ // possible candidate for weaving. Check if has __ already
+ if (!this.existingMethods.contains(this.prefix + name)) {
+ // effectively renames the existing method prepending __
+ // to the name
+ if (ContainerGhostingAugmenter.this.isVerbose)
+ System.out.println("... renaming '" + name + "(" + desc
+ + ")' to '" + (this.prefix + name) + "'");
+ return this.cv.visitMethod(access, this.prefix + name,
+ desc, signature, exceptions);
+ }
+ }
+ // preserve the existing method as is
+ return this.cv.visitMethod(access, name, desc, signature,
+ exceptions);
+ }
+ }
+
+ /**
+ * Augments a single class with image ghosting UI behaviour.
+ *
+ * @param dir
+ * Root directory for the library that contains the class.
+ * @param name
+ * Fully-qualified class name.
+ * @param toInjectAfterOriginal
+ * <code>true</code> if the code needs to be injected after the
+ * call to the original implementation.
+ * @throws AugmentException
+ * If the augmentation process failed.
+ */
+ protected synchronized void augmentClass(String dir, final String name,
+ final boolean toInjectAfterOriginal) {
+ if (this.isVerbose)
+ System.out.println("Working on " + name + ".update() ["
+ + (toInjectAfterOriginal ? "after]" : "before]"));
+ // gets an input stream to read the bytecode of the class
+ String resource = dir + File.separator + name.replace('.', '/')
+ + ".class";
+
+ Method methodToAugment = null;
+ try {
+ ClassLoader cl = new URLClassLoader(new URL[] { new File(dir)
+ .toURL() }, ContainerGhostingAugmenter.class
+ .getClassLoader());
+ Class<?> clazz = cl.loadClass(name);
+ // Start iterating over all methods and see what do we
+ // need to augment
+ while (clazz != null) {
+ if (methodToAugment != null)
+ break;
+ Method[] methods = clazz.getDeclaredMethods();
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+ if (!"update".equals(method.getName()))
+ continue;
+ methodToAugment = method;
+ break;
+ }
+ clazz = clazz.getSuperclass();
+ }
+ } catch (Exception e) {
+ throw new AugmentException(name, e);
+ }
+
+ Set<String> existingMethods = null;
+ Set<String> existingFields = null;
+ InputStream is = null;
+ try {
+ is = new FileInputStream(resource);
+ ClassReader cr = new ClassReader(is);
+ InfoClassVisitor infoAdapter = new InfoClassVisitor();
+ cr.accept(infoAdapter, false);
+ existingMethods = infoAdapter.getMethods();
+ existingFields = infoAdapter.getFields();
+ } catch (Exception e) {
+ throw new AugmentException(name, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ }
+ }
+
+ // See if the 'containerGhostingMarker' field is already defined. In
+ // this
+ // case the class is *not* augmented
+ if (existingFields.contains("containerGhostingMarker")) {
+ if (this.isVerbose)
+ System.out
+ .println("Not augmenting resource, field 'containerGhostingMarker' is present");
+ return;
+ }
+
+ // Augment the class (overriding the existing file).
+ byte[] b;
+ try {
+ is = new FileInputStream(resource);
+ ClassReader cr = new ClassReader(is);
+ ClassWriter cw = new ClassWriter(false);
+ ClassVisitor cv = new AugmentClassAdapter(cw, existingMethods,
+ existingFields, methodToAugment, toInjectAfterOriginal);
+ cr.accept(cv, false);
+ b = cw.toByteArray();
+ } catch (Exception e) {
+ throw new AugmentException(name, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ }
+ }
+
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(resource);
+ fos.write(b);
+ if (this.isVerbose)
+ System.out.println("Updated resource " + resource);
+ } catch (Exception e) {
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException ioe) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Processes a single file or a directory, augmenting all relevant classes.
+ *
+ * @param toStrip
+ * The leading prefix to strip from the file names. Is used to
+ * create fully-qualified class name.
+ * @param file
+ * File resource (can point to a single file or to a directory).
+ * @param ids
+ * List of class-method pairs to augment.
+ * @throws AugmentException
+ * If the augmentation process failed.
+ */
+ public void process(String toStrip, File file,
+ List<ContainerGhostingType> ids) throws AugmentException {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ for (int i = 0; i < children.length; i++) {
+ this.process(toStrip, children[i], ids);
+ }
+ } else {
+ String currClassName = file.getAbsolutePath().substring(
+ toStrip.length() + 1);
+ currClassName = currClassName.replace(File.separatorChar, '.');
+ currClassName = currClassName.substring(0,
+ currClassName.length() - 6);
+ for (ContainerGhostingType igt : ids) {
+ // System.out.println(currClassName + ":" + igt.getClassName());
+ if (currClassName.equals(igt.getClassName())) {
+ this.augmentClass(toStrip, igt.getClassName(), igt
+ .isToInjectAfterOriginal());
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the verbosity.
+ *
+ * @param isVerbose
+ * New value for augmentation process verbosity.
+ */
+ public void setVerbose(boolean isVerbose) {
+ this.isVerbose = isVerbose;
+ }
+
+ /**
+ * Test method.
+ *
+ * @param args
+ * @throws AugmentException
+ */
+ public static void main(final String args[]) throws AugmentException {
+ if (args.length == 0) {
+ System.out
+ .println("Usage : java ... IconGhostingDelegateAugmenter [-verbose] [-pattern class_pattern] file_resource");
+ System.out
+ .println("\tIf -verbose option is specified, the augmenter prints out its actions.");
+ System.out
+ .println("\tIf -class option if specified, its value is used as class name to augment.");
+ System.out
+ .println("\tIf -before option if specified, the code is injected before the original code.");
+ System.out
+ .println("\tThe last parameter can point to either a file or a directory. "
+ + "The directory should be the root directory for classes.");
+ return;
+ }
+
+ ContainerGhostingAugmenter uiDelegateAugmenter = new ContainerGhostingAugmenter();
+
+ int argNum = 0;
+ String className = null;
+ boolean isAfter = true;
+ while (true) {
+ String currArg = args[argNum];
+ if ("-verbose".equals(currArg)) {
+ uiDelegateAugmenter.setVerbose(true);
+ argNum++;
+ continue;
+ }
+ if ("-class".equals(currArg)) {
+ argNum++;
+ className = args[argNum];
+ argNum++;
+ continue;
+ }
+ if ("-before".equals(currArg)) {
+ argNum++;
+ isAfter = false;
+ continue;
+ }
+ break;
+ }
+
+ File starter = new File(args[argNum]);
+ ContainerGhostingType igt = new ContainerGhostingType();
+ igt.setClassName(className);
+ igt.setToInjectAfterOriginal(isAfter);
+ uiDelegateAugmenter.process(starter.getAbsolutePath(), starter, Arrays
+ .asList(igt));
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/ContainerGhostingType.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/ContainerGhostingType.java
new file mode 100644
index 0000000..3ad5905
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/ContainerGhostingType.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import org.apache.tools.ant.types.DataType;
+
+/**
+ * Ant type for storing <code>containerGhosting</code> elements of
+ * {@link AugmentContainerGhostingTask} task.
+ *
+ *
+ * <p>
+ * Represents the following build snippet:
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * <containerGhosting className="org.pushingpixels.substance.internal.ui.SubstanceButtonUI"
+ * toInjectAfterOriginal="true" />
+ * </code>
+ * </pre>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ContainerGhostingType extends DataType {
+ /**
+ * UI delegate class name.
+ */
+ private String className;
+
+ /**
+ * Indicates whether the ghosting should be injected before or after the
+ * original code.
+ */
+ private boolean toInjectAfterOriginal;
+
+ /**
+ * Creates new instance.
+ */
+ public ContainerGhostingType() {
+ super();
+ }
+
+ /**
+ * Sets the UI delegate class name.
+ *
+ * @param name
+ * UI delegate class name.
+ */
+ public void setClassName(String name) {
+ this.className = name;
+ }
+
+ /**
+ * Returns the UI delegate class name.
+ *
+ * @return UI delegate class name.
+ */
+ public String getClassName() {
+ return this.className;
+ }
+
+ /**
+ * Returns indication whether the ghosting should be injected before or
+ * after the original code.
+ *
+ * @return <code>true</code> if the ghosting should be injected after the
+ * original code, <code>false</code> if it should be injected before
+ * the original code.
+ */
+ public boolean isToInjectAfterOriginal() {
+ return toInjectAfterOriginal;
+ }
+
+ /**
+ * Returns indication whether the ghosting should be injected before or
+ * after the original code.
+ *
+ * @param toInjectAfterOriginal
+ * <code>true</code> if the ghosting should be injected after the
+ * original code, <code>false</code> if it should be injected
+ * before the original code.
+ */
+ public void setToInjectAfterOriginal(boolean toInjectAfterOriginal) {
+ this.toInjectAfterOriginal = toInjectAfterOriginal;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/IconGhostingAugmenter.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/IconGhostingAugmenter.java
new file mode 100644
index 0000000..5276425
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/IconGhostingAugmenter.java
@@ -0,0 +1,759 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.io.*;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.*;
+
+import javax.swing.JComponent;
+
+import org.objectweb.asm.*;
+
+/**
+ * Augments the button UI classes with icon ghosting painting. Is based on
+ * sample adapter from ASM distribution.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class IconGhostingAugmenter {
+ /**
+ * Verbosity indication.
+ */
+ private boolean isVerbose;
+
+ /**
+ * Adapter for augmenting a single class.
+ *
+ * @author Kirill Grouchnikov.
+ */
+ protected class AugmentClassAdapter extends ClassAdapter implements Opcodes {
+ /**
+ * Contains all method names.
+ */
+ private Set<String> existingMethods;
+
+ /**
+ * Contains all field names.
+ */
+ private Set<String> existingFields;
+
+ /**
+ * Method to augment.
+ */
+ private Method methodToAugment;
+
+ // private Method methodInstallListeners;
+ //
+ // private Method methodUninstallListeners;
+
+ /**
+ * Prefix for delegate methods that will be added.
+ */
+ private String prefix;
+
+ /**
+ * Creates a new augmentor.
+ *
+ * @param cv
+ * Class visitor to recreate the non-augmented methods.
+ * @param existingMethods
+ * Contains all method names.
+ * @param existingFields
+ * Contains all field names.
+ * @param methodToAugment
+ * Method to augment.
+ */
+ public AugmentClassAdapter(final ClassVisitor cv,
+ Set<String> existingMethods, Set<String> existingFields,
+ Method methodToAugment// ,
+ // Method methodInstallListeners, Method methodUninstallListeners
+ ) {
+ super(cv);
+ this.existingMethods = existingMethods;
+ this.existingFields = existingFields;
+ this.methodToAugment = methodToAugment;
+ // this.methodInstallListeners = methodInstallListeners;
+ // this.methodUninstallListeners = methodUninstallListeners;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.objectweb.asm.ClassAdapter#visit(int, int, java.lang.String,
+ * java.lang.String, java.lang.String, java.lang.String[])
+ */
+ @Override
+ public void visit(final int version, final int access,
+ final String name, final String signature,
+ final String superName, final String[] interfaces) {
+ this.prefix = "__" + name.replaceAll("/", "__") + "__icon__";
+ super
+ .visit(version, access, name, signature, superName,
+ interfaces);
+
+ // Check if need to add the iconGhostingMarker field (boolean)
+ if (!this.existingFields.contains("iconGhostingMarker")) {
+ FieldVisitor fv = this.visitField(Opcodes.ACC_PROTECTED,
+ "iconGhostingMarker", "Z", null, null);
+ fv.visitEnd();
+
+ // this.visitInnerClass(name + "$1", null, null, 0);
+ //
+ // fv = this.visitField(ACC_PRIVATE, "ghostModelChangeListener",
+ // "Lorg/pushingpixels/lafwidget/utils/GhostingListener;", null,
+ // null);
+ // fv.visitEnd();
+ // fv = this.visitField(ACC_PROTECTED, "ghostPropertyListener",
+ // "Ljava/beans/PropertyChangeListener;", null, null);
+ // fv.visitEnd();
+ //
+ // MethodVisitor mv = this
+ // .visitMethod(
+ // ACC_STATIC + ACC_SYNTHETIC,
+ // "access$0",
+ // "(L"
+ // + name
+ // + ";)Lorg/pushingpixels/lafwidget/utils/GhostingListener;",
+ // null, null);
+ // mv.visitCode();
+ // mv.visitVarInsn(ALOAD, 0);
+ // mv.visitFieldInsn(GETFIELD, name, "ghostModelChangeListener",
+ // "Lorg/pushingpixels/lafwidget/utils/GhostingListener;");
+ // mv.visitInsn(ARETURN);
+ // mv.visitMaxs(1, 1);
+ // mv.visitEnd();
+ //
+ // mv = this
+ // .visitMethod(
+ // ACC_STATIC + ACC_SYNTHETIC,
+ // "access$1",
+ // "(L"
+ // + name
+ // + ";Lorg/pushingpixels/lafwidget/utils/GhostingListener;)V",
+ // null, null);
+ // mv.visitCode();
+ // mv.visitVarInsn(ALOAD, 0);
+ // mv.visitVarInsn(ALOAD, 1);
+ // mv.visitFieldInsn(PUTFIELD, name, "ghostModelChangeListener",
+ // "Lorg/pushingpixels/lafwidget/utils/GhostingListener;");
+ // mv.visitInsn(RETURN);
+ // mv.visitMaxs(2, 2);
+ // mv.visitEnd();
+ //
+ }
+
+ // We have three separate cases for the method that we
+ // want to augment:
+ //
+ // 1. The current .class has both function and the __ version -
+ // already has been augmented. Can be ignored.
+ //
+ // 2. The current .class has function but doesn't have the __
+ // version. Than, the original function has already been renamed to
+ // __ (in the visitMethod). We need to create a new version for this
+ // function that performs pre-logic, calls __ and performs the
+ // post-logic.
+ //
+ // 3. The current .class doesn't have neither the function nor
+ // the __ version. In this case we need to create the __ version
+ // that calls super (with the original name) and the function that
+ // performs pre-logic, calls __ and performs the post-logic.
+
+ Method[] toAugment = new Method[] { this.methodToAugment };
+ // this.methodInstallListeners, this.methodUninstallListeners };
+ for (Method currMethodToAugment : toAugment) {
+ String methodName = currMethodToAugment.getName();
+ boolean hasOriginal = this.existingMethods.contains(methodName);
+ boolean hasDelegate = this.existingMethods.contains(this.prefix
+ + methodName);
+
+ String methodSignature = Utils
+ .getMethodDesc(currMethodToAugment);
+ int paramCount = currMethodToAugment.getParameterTypes().length;
+ if (IconGhostingAugmenter.this.isVerbose)
+ System.out.println("... Augmenting " + methodName + " "
+ + methodSignature + " : original - " + hasOriginal
+ + ", delegate - " + hasDelegate + ", " + paramCount
+ + " params");
+
+ if (!hasDelegate) {
+ // if (methodName.equals("installListeners")) {
+ // this.augmentInstallListeners(!hasOriginal, name,
+ // superName, methodName, methodSignature);
+ // } else {
+ // if (methodName.equals("uninstallListeners")) {
+ // this.augmentUninstallListeners(!hasOriginal, name,
+ // superName, methodName, methodSignature);
+ // } else {
+ this.augmentPaintIconMethod(!hasOriginal, name, superName,
+ methodName, methodSignature);
+ // }
+ // }
+ }
+ }
+ }
+
+ /**
+ * Augments the <code>paintIcon</code> method that is assumed to always
+ * have three parameters.
+ *
+ * @param toSynthOriginal
+ * Indication whether we need to create an empty (only call
+ * to super()) implementation.
+ * @param className
+ * Class name.
+ * @param superClassName
+ * Super class name (relevant for generating empty
+ * implementation).
+ * @param methodName
+ * Method name.
+ * @param methodDesc
+ * Function signature (using JNI style declaration). Example
+ * for <code>void installUI(JButton button)</code>:
+ * <code>(Ljavax/swing/JButton;)V</code>.
+ */
+ public void augmentPaintIconMethod(boolean toSynthOriginal,
+ String className, String superClassName, String methodName,
+ String methodDesc) {
+ // Some ASM woodoo. The code below was generated by using
+ // ASMifierClassVisitor.
+ if (toSynthOriginal) {
+ MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PUBLIC,
+ this.prefix + methodName, methodDesc, null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName,
+ methodName, methodDesc);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(4, 4);
+ mv.visitEnd();
+ }
+
+ MethodVisitor mv = this.cv.visitMethod(ACC_PROTECTED, methodName,
+ methodDesc, null, null);
+
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitLdcInsn("icon.bounds");
+ mv.visitTypeInsn(NEW, "java/awt/Rectangle");
+ mv.visitInsn(DUP);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/awt/Rectangle", "<init>",
+ "(Ljava/awt/Rectangle;)V");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "javax/swing/JComponent",
+ "putClientProperty",
+ "(Ljava/lang/Object;Ljava/lang/Object;)V");
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/awt/Graphics", "create",
+ "()Ljava/awt/Graphics;");
+ mv.visitTypeInsn(CHECKCAST, "java/awt/Graphics2D");
+ mv.visitVarInsn(ASTORE, 4);
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitTypeInsn(CHECKCAST, "javax/swing/AbstractButton");
+ mv.visitVarInsn(ALOAD, 3);
+ mv
+ .visitMethodInsn(
+ INVOKESTATIC,
+ "org/pushingpixels/lafwidget/animation/effects/GhostPaintingUtils",
+ "paintGhostIcon",
+ "(Ljava/awt/Graphics2D;Ljavax/swing/AbstractButton;Ljava/awt/Rectangle;)V");
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/awt/Graphics2D", "dispose",
+ "()V");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitMethodInsn(INVOKEVIRTUAL, className, this.prefix
+ + methodName, methodDesc);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(5, 5);
+ mv.visitEnd();
+ }
+
+ /**
+ * Augments the <code>installListeners</code> method.
+ *
+ * @param toSynthOriginal
+ * Indication whether we need to create an empty (only call
+ * to super()) implementation.
+ * @param className
+ * Class name.
+ * @param superClassName
+ * Super class name (relevant for generating empty
+ * implementation).
+ * @param methodName
+ * Method name.
+ * @param functionDesc
+ * Function signature (using JNI style declaration). Example
+ * for <code>void installUI(JButton button)</code>:
+ * <code>(Ljavax/swing/JButton;)V</code>.
+ */
+ public void augmentInstallListeners(boolean toSynthOriginal,
+ String className, String superClassName, String methodName,
+ String functionDesc) {
+ // Some ASM woodoo. The code below was generated by using
+ // ASMifierClassVisitor.
+ if (toSynthOriginal) {
+ if (isVerbose) {
+ System.out.println("... Creating empty '" + methodName
+ + functionDesc + "' forwarding to super '"
+ + superClassName + "'");
+ }
+ MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PUBLIC,
+ this.prefix + methodName, functionDesc, null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKESPECIAL, superClassName,
+ "installListeners", "(Ljavax/swing/AbstractButton;)V");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+
+ if (isVerbose) {
+ System.out.println("... Augmenting '" + methodName
+ + functionDesc + "'");
+ }
+ MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PUBLIC,
+ methodName, functionDesc, null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, className, prefix
+ + "installListeners", functionDesc);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitTypeInsn(NEW, className + "$1");
+ mv.visitInsn(DUP);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKESPECIAL, className + "$1", "<init>", "(L"
+ + className + ";Ljavax/swing/AbstractButton;)V");
+ mv.visitFieldInsn(PUTFIELD, className, "ghostPropertyListener",
+ "Ljava/beans/PropertyChangeListener;");
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, className, "ghostPropertyListener",
+ "Ljava/beans/PropertyChangeListener;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "javax/swing/AbstractButton",
+ "addPropertyChangeListener",
+ "(Ljava/beans/PropertyChangeListener;)V");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitTypeInsn(NEW,
+ "org/pushingpixels/lafwidget/utils/GhostingListener");
+ mv.visitInsn(DUP);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "javax/swing/AbstractButton",
+ "getModel", "()Ljavax/swing/ButtonModel;");
+ mv.visitMethodInsn(INVOKESPECIAL,
+ "org/pushingpixels/lafwidget/utils/GhostingListener",
+ "<init>",
+ "(Ljava/awt/Component;Ljavax/swing/ButtonModel;)V");
+ mv.visitFieldInsn(PUTFIELD, className, "ghostModelChangeListener",
+ "Lorg/pushingpixels/lafwidget/utils/GhostingListener;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, className, "ghostModelChangeListener",
+ "Lorg/pushingpixels/lafwidget/utils/GhostingListener;");
+ mv.visitMethodInsn(INVOKEVIRTUAL,
+ "org/pushingpixels/lafwidget/utils/GhostingListener",
+ "registerListeners", "()V");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(5, 2);
+ mv.visitEnd();
+ }
+
+ /**
+ * Augments the <code>uninstallListeners</code> method.
+ *
+ * @param toSynthOriginal
+ * Indication whether we need to create an empty (only call
+ * to super()) implementation.
+ * @param className
+ * Class name.
+ * @param superClassName
+ * Super class name (relevant for generating empty
+ * implementation).
+ * @param methodName
+ * Method name.
+ * @param functionDesc
+ * Function signature (using JNI style declaration). Example
+ * for <code>void installUI(JButton button)</code>:
+ * <code>(Ljavax/swing/JButton;)V</code>.
+ */
+ public void augmentUninstallListeners(boolean toSynthOriginal,
+ String className, String superClassName, String methodName,
+ String functionDesc) {
+ // Some ASM woodoo. The code below was generated by using
+ // ASMifierClassVisitor.
+ if (toSynthOriginal) {
+ if (isVerbose) {
+ System.out.println("... Creating empty '" + methodName
+ + functionDesc + "' forwarding to super '"
+ + superClassName + "'");
+ }
+ MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PUBLIC,
+ this.prefix + methodName, functionDesc, null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv
+ .visitMethodInsn(INVOKESPECIAL, superClassName,
+ "uninstallListeners",
+ "(Ljavax/swing/AbstractButton;)V");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+
+ }
+
+ if (isVerbose) {
+ System.out.println("... Augmenting '" + methodName
+ + functionDesc + "'");
+ }
+ MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PUBLIC,
+ methodName, functionDesc, null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, className, "ghostPropertyListener",
+ "Ljava/beans/PropertyChangeListener;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "javax/swing/AbstractButton",
+ "removePropertyChangeListener",
+ "(Ljava/beans/PropertyChangeListener;)V");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitFieldInsn(PUTFIELD, className, "ghostPropertyListener",
+ "Ljava/beans/PropertyChangeListener;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, className, "ghostModelChangeListener",
+ "Lorg/pushingpixels/lafwidget/utils/GhostingListener;");
+ mv.visitMethodInsn(INVOKEVIRTUAL,
+ "org/pushingpixels/lafwidget/utils/GhostingListener",
+ "unregisterListeners", "()V");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitFieldInsn(PUTFIELD, className, "ghostModelChangeListener",
+ "Lorg/pushingpixels/lafwidget/utils/GhostingListener;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, className, prefix
+ + "uninstallListeners", "(Ljavax/swing/AbstractButton;)V");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.objectweb.asm.ClassAdapter#visitMethod(int,
+ * java.lang.String, java.lang.String, java.lang.String,
+ * java.lang.String[])
+ */
+ @Override
+ public MethodVisitor visitMethod(final int access, final String name,
+ final String desc, final String signature,
+ final String[] exceptions) {
+
+ Set<String> toAugment = new HashSet<String>();
+ toAugment.add(this.methodToAugment.getName());
+ // toAugment.add(this.methodInstallListeners.getName());
+ // toAugment.add(this.methodUninstallListeners.getName());
+ if (toAugment.contains(name)) {
+ // possible candidate for weaving. Check if has __ already
+ if (!this.existingMethods.contains(this.prefix + name)) {
+ // effectively renames the existing method prepending __
+ // to the name
+ if (IconGhostingAugmenter.this.isVerbose)
+ System.out.println("... renaming '" + name + "(" + desc
+ + ")' to '" + (this.prefix + name) + "'");
+ return this.cv.visitMethod(access, this.prefix + name,
+ desc, signature, exceptions);
+ }
+ }
+ // preserve the existing method as is
+ return this.cv.visitMethod(access, name, desc, signature,
+ exceptions);
+ }
+ }
+
+ /**
+ * Augments a single class with additional UI behaviour.
+ *
+ * @param dir
+ * Root directory for the library that contains the class.
+ * @param name
+ * Fully-qualified class name.
+ * @param paintIconMethodName
+ * Method name.
+ * @throws AugmentException
+ * If the augmentation process failed.
+ */
+ protected synchronized void augmentClass(String dir, final String name,
+ final String paintIconMethodName) {
+ if (this.isVerbose)
+ System.out
+ .println("Working on " + name + "." + paintIconMethodName);
+ // gets an input stream to read the bytecode of the class
+ String resource = dir + File.separator + name.replace('.', '/')
+ + ".class";
+
+ Method methodToAugment = null;
+ Method methodInstallListeners = null;
+ Method methodUninstallListeners = null;
+ try {
+ ClassLoader cl = new URLClassLoader(new URL[] { new File(dir)
+ .toURL() }, IconGhostingAugmenter.class.getClassLoader());
+ Class<?> clazz = cl.loadClass(name);
+ // Start iterating over all methods and see what do we
+ // need to augment
+ while (clazz != null) {
+ Method[] methods = clazz.getDeclaredMethods();
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+ if ((methodInstallListeners == null)
+ && (method.getName().equals("installListeners"))) {
+ methodInstallListeners = method;
+ continue;
+ }
+ if ((methodUninstallListeners == null)
+ && (method.getName().equals("uninstallListeners"))) {
+ methodUninstallListeners = method;
+ continue;
+ }
+ if (!paintIconMethodName.equals(method.getName()))
+ continue;
+ Class<?>[] params = method.getParameterTypes();
+ boolean paramsOk = (params.length == 3);
+ if (paramsOk) {
+ paramsOk = paramsOk && (params[0] == Graphics.class);
+ paramsOk = paramsOk
+ && (JComponent.class
+ .isAssignableFrom(params[1]));
+ paramsOk = paramsOk && (params[2] == Rectangle.class);
+ if (isVerbose) {
+ System.out.println("Method params are "
+ + params[0].getName() + ":"
+ + params[1].getName() + ":"
+ + params[2].getName() + " - " + paramsOk);
+ }
+ }
+ if (!paramsOk)
+ continue;
+ if (methodToAugment == null) {
+ methodToAugment = method;
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+ } catch (Exception e) {
+ throw new AugmentException(name, e);
+ }
+
+ Set<String> existingMethods = null;
+ Set<String> existingFields = null;
+ InputStream is = null;
+ try {
+ is = new FileInputStream(resource);
+ ClassReader cr = new ClassReader(is);
+ InfoClassVisitor infoAdapter = new InfoClassVisitor();
+ cr.accept(infoAdapter, false);
+ existingMethods = infoAdapter.getMethods();
+ existingFields = infoAdapter.getFields();
+ } catch (Exception e) {
+ throw new AugmentException(name, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ }
+ }
+
+ // See if the 'iconGhostingMarker' field is already defined. In this
+ // case the class is *not* augmented
+ if (existingFields.contains("iconGhostingMarker")) {
+ if (this.isVerbose)
+ System.out
+ .println("Not augmenting resource, field 'iconGhostingMarker' is present");
+ return;
+ }
+
+ // Augment the class (overriding the existing file).
+ byte[] b;
+ try {
+ is = new FileInputStream(resource);
+ ClassReader cr = new ClassReader(is);
+ ClassWriter cw = new ClassWriter(false);
+ ClassVisitor cv = new AugmentClassAdapter(cw, existingMethods,
+ existingFields, methodToAugment// , methodInstallListeners,
+ // methodUninstallListeners);
+ );
+ cr.accept(cv, false);
+ b = cw.toByteArray();
+ } catch (Exception e) {
+ throw new AugmentException(name, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ }
+ }
+
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(resource);
+ fos.write(b);
+ if (this.isVerbose)
+ System.out.println("Updated resource " + resource);
+ } catch (Exception e) {
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException ioe) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Processes a single file or a directory, augmenting all relevant classes.
+ *
+ * @param toStrip
+ * The leading prefix to strip from the file names. Is used to
+ * create fully-qualified class name.
+ * @param file
+ * File resource (can point to a single file or to a directory).
+ * @param ids
+ * List of class-method pairs to augment.
+ * @throws AugmentException
+ * If the augmentation process failed.
+ */
+ public void process(String toStrip, File file, List<IconGhostingType> ids)
+ throws AugmentException {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ for (int i = 0; i < children.length; i++) {
+ this.process(toStrip, children[i], ids);
+ }
+ } else {
+ String currClassName = file.getAbsolutePath().substring(
+ toStrip.length() + 1);
+ currClassName = currClassName.replace(File.separatorChar, '.');
+ currClassName = currClassName.substring(0,
+ currClassName.length() - 6);
+ for (IconGhostingType igt : ids) {
+ // System.out.println(currClassName + ":" + igt.getClassName());
+ if (currClassName.equals(igt.getClassName())) {
+ this.augmentClass(toStrip, igt.getClassName(), igt
+ .getMethodName());
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the verbosity.
+ *
+ * @param isVerbose
+ * New value for augmentation process verbosity.
+ */
+ public void setVerbose(boolean isVerbose) {
+ this.isVerbose = isVerbose;
+ }
+
+ /**
+ * Test method.
+ *
+ * @param args
+ * @throws AugmentException
+ */
+ public static void main(final String args[]) throws AugmentException {
+ if (args.length == 0) {
+ System.out
+ .println("Usage : java ... IconGhostingDelegateAugmenter [-verbose] [-pattern class_pattern] file_resource");
+ System.out
+ .println("\tIf -verbose option is specified, the augmenter prints out its actions.");
+ System.out
+ .println("\tIf -class option is specified, its value is used as class name to augment.");
+ System.out
+ .println("\tIf -method option is specified, its value is used as method name to augment.");
+ System.out
+ .println("\tThe last parameter can point to either a file or a directory. "
+ + "The directory should be the root directory for classes.");
+ return;
+ }
+
+ IconGhostingAugmenter uiDelegateAugmenter = new IconGhostingAugmenter();
+
+ int argNum = 0;
+ String className = null;
+ String methodName = null;
+ while (true) {
+ String currArg = args[argNum];
+ if ("-verbose".equals(currArg)) {
+ uiDelegateAugmenter.setVerbose(true);
+ argNum++;
+ continue;
+ }
+ if ("-class".equals(currArg)) {
+ argNum++;
+ className = args[argNum];
+ argNum++;
+ continue;
+ }
+ if ("-method".equals(currArg)) {
+ argNum++;
+ methodName = args[argNum];
+ argNum++;
+ continue;
+ }
+ break;
+ }
+
+ File starter = new File(args[argNum]);
+ IconGhostingType igt = new IconGhostingType();
+ igt.setClassName(className);
+ igt.setMethodName(methodName);
+ uiDelegateAugmenter.process(starter.getAbsolutePath(), starter, Arrays
+ .asList(igt));
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/IconGhostingType.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/IconGhostingType.java
new file mode 100644
index 0000000..4cf7264
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/IconGhostingType.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import org.apache.tools.ant.types.DataType;
+
+/**
+ * Ant type for storing <code>iconGhosting</code> elements of
+ * {@link AugmentIconGhostingTask} task.
+ *
+ *
+ * <p>
+ * Represents the following build snippet:
+ * </p>
+ *
+ * <pre>
+ * <code>
+ * <iconGhosting className="org.pushingpixels.substance.internal.ui.SubstanceButtonUI"
+ * methodName="paintIcon" />
+ * </code>
+ * </pre>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class IconGhostingType extends DataType {
+ /**
+ * UI delegate class name.
+ */
+ private String className;
+
+ /**
+ * Method name to augment.
+ */
+ private String methodName;
+
+ /**
+ * Creates new instance.
+ */
+ public IconGhostingType() {
+ super();
+ }
+
+ /**
+ * Sets the UI delegate class name.
+ *
+ * @param name
+ * UI delegate class name.
+ */
+ public void setClassName(String name) {
+ this.className = name;
+ }
+
+ /**
+ * Returns the UI delegate class name.
+ *
+ * @return UI delegate class name.
+ */
+ public String getClassName() {
+ return this.className;
+ }
+
+ /**
+ * Returns the method name to augment.
+ *
+ * @return Method name to augment.
+ */
+ public String getMethodName() {
+ return methodName;
+ }
+
+ /**
+ * Sets the method name to augment.
+ *
+ * @param methodName
+ * Method name to augment.
+ */
+ public void setMethodName(String methodName) {
+ this.methodName = methodName;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/InfoClassVisitor.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/InfoClassVisitor.java
new file mode 100644
index 0000000..a879844
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/InfoClassVisitor.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import java.util.*;
+
+import org.objectweb.asm.*;
+import org.objectweb.asm.commons.EmptyVisitor;
+
+/**
+ * Gathers information on all methods and fields of some class.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class InfoClassVisitor extends EmptyVisitor implements Opcodes {
+ /**
+ * All method names.
+ */
+ protected Set<String> methods;
+
+ /**
+ * All field names.
+ */
+ protected Set<String> fields;
+
+ /**
+ * Creates a new visitor.
+ */
+ public InfoClassVisitor() {
+ this.methods = new HashSet<String>();
+ this.fields = new HashSet<String>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.objectweb.asm.commons.EmptyVisitor#visitMethod(int,
+ * java.lang.String, java.lang.String, java.lang.String,
+ * java.lang.String[])
+ */
+ @Override
+ public MethodVisitor visitMethod(final int access, final String name,
+ final String desc, final String signature, final String[] exceptions) {
+ this.methods.add(name);
+ return this;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.objectweb.asm.commons.EmptyVisitor#visitField(int,
+ * java.lang.String, java.lang.String, java.lang.String,
+ * java.lang.Object)
+ */
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ this.fields.add(name);
+ return this;
+ }
+
+ /**
+ * Returns all methods of the visited class.
+ *
+ * @return Unmodifiable set of all methods of the visited class.
+ */
+ public Set<String> getMethods() {
+ return Collections.unmodifiableSet(this.methods);
+ }
+
+ /**
+ * Returns all fields of the visited class.
+ *
+ * @return Unmodifiable set of all fields of the visited class.
+ */
+ public Set<String> getFields() {
+ return Collections.unmodifiableSet(this.fields);
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/LafMainClassAugmenter.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/LafMainClassAugmenter.java
new file mode 100644
index 0000000..8256e80
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/LafMainClassAugmenter.java
@@ -0,0 +1,543 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Set;
+
+import javax.swing.UIDefaults;
+
+import org.objectweb.asm.*;
+
+/**
+ * Augments the main LAF classes with laf-widget behaviour. Is based on sample
+ * adapter from ASM distribution.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LafMainClassAugmenter {
+ /**
+ * Method name to augment.
+ */
+ protected static String METHOD_NAME = "initClassDefaults";
+
+ /**
+ * Verbosity indication.
+ */
+ private boolean isVerbose;
+
+ /**
+ * Names of delegates to add.
+ */
+ private String[] delegatesToAdd;
+
+ /**
+ * Class adapter that augments the UI functionality.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class AugmentClassAdapter extends ClassAdapter implements Opcodes {
+ /**
+ * Contains all method names.
+ */
+ private Set<String> existingMethods;
+
+ /**
+ * Prefix for delegate methods that will be added.
+ */
+ private String prefix;
+
+ /**
+ * The original method.
+ */
+ // private Method originalMethod;
+ /**
+ * Class name of the super class.
+ */
+ private String superClassName;
+
+ /**
+ * Creates a new augmentor.
+ *
+ * @param cv
+ * Class visitor to recreate the non-augmented methods.
+ * @param existingMethods
+ * Existing methods.
+ * @param originalMethod
+ * The original method.
+ */
+ public AugmentClassAdapter(ClassVisitor cv,
+ Set<String> existingMethods, Method originalMethod) {
+ super(cv);
+ this.existingMethods = existingMethods;
+ // this.originalMethod = originalMethod;
+ }
+
+ /**
+ * Returns the superclass name.
+ *
+ * @return The superclass name.
+ */
+ public String getSuperClassName() {
+ return this.superClassName;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.objectweb.asm.ClassAdapter#visit(int, int, java.lang.String,
+ * java.lang.String, java.lang.String, java.lang.String[])
+ */
+ @Override
+ public void visit(final int version, final int access,
+ final String name, final String signature,
+ final String superName, final String[] interfaces) {
+ this.superClassName = superName;
+ this.prefix = "__" + name.replaceAll("/", "__") + "__";
+ super
+ .visit(version, access, name, signature, superName,
+ interfaces);
+
+ // We have two separate cases for the "initClassDefaults" function
+ // that we want to augment:
+ //
+ // 1. The current .class has both function and the __ version -
+ // already has been augmented. Can be ignored.
+ //
+ // 2. The current .class has function but doesn't have the __
+ // version. Than, the original function has already been renamed to
+ // __ (in the visitMethod). We need to create a new version for this
+ // function that performs pre-logic, calls __ and performs the
+ // post-logic.
+ boolean hasOriginal = this.existingMethods
+ .contains(LafMainClassAugmenter.METHOD_NAME);
+ boolean hasDelegate = this.existingMethods.contains(this.prefix
+ + LafMainClassAugmenter.METHOD_NAME);
+
+ // String methodSignature =
+ // Utils.getMethodDesc(this.originalMethod);
+ // int paramCount = this.originalMethod.getParameterTypes().length;
+ if (LafMainClassAugmenter.this.isVerbose)
+ System.out.println("..." + LafMainClassAugmenter.METHOD_NAME
+ + " " + "(Ljavax/swing/UIDefaults;)V"
+ + " : delegate - " + hasDelegate + ", 1 params");
+
+ if (!hasDelegate) {
+ this.augmentInitClassDefaultsMethod(!hasOriginal, name,
+ superName);
+ }
+ }
+
+ /**
+ * Augments the <code>initClassDefaults</code> method.
+ *
+ * @param toSynthOriginal
+ * if <code>true</code>, a forwarding method will be
+ * generated.
+ * @param className
+ * Class name.
+ * @param superClassName
+ * Super class name (relevant for generating empty
+ * implementation).
+ */
+ public void augmentInitClassDefaultsMethod(boolean toSynthOriginal,
+ String className, String superClassName) {
+
+ if (toSynthOriginal) {
+ if (LafMainClassAugmenter.this.isVerbose) {
+ System.out
+ .println("... Creating empty 'initClassDefaults' forwarding to super '"
+ + superClassName + "'");
+ }
+ MethodVisitor mv = this.cv.visitMethod(ACC_PROTECTED,
+ this.prefix + "initClassDefaults",
+ "(Ljavax/swing/UIDefaults;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKESPECIAL, superClassName,
+ "initClassDefaults", "(Ljavax/swing/UIDefaults;)V");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+
+ MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PROTECTED,
+ "initClassDefaults", "(Ljavax/swing/UIDefaults;)V", null,
+ null);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, this.prefix
+ + "initClassDefaults", "(Ljavax/swing/UIDefaults;)V");
+
+ String packageName = className.replace('/', '.');
+ int lastDotIndex = packageName.lastIndexOf('.');
+ if (lastDotIndex >= 0) {
+ packageName = packageName.substring(0, lastDotIndex);
+ } else {
+ packageName = "";
+ }
+
+ for (int i = 0; i < LafMainClassAugmenter.this.delegatesToAdd.length; i++) {
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ String delegateKey = LafMainClassAugmenter.this.delegatesToAdd[i];
+ String delegateValue = packageName + ".__Forwarding__"
+ + delegateKey;
+
+ if (LafMainClassAugmenter.this.isVerbose) {
+ System.out.println("...Putting '" + delegateKey + "' -> '"
+ + delegateValue + "'");
+ }
+
+ mv.visitLdcInsn(delegateKey);
+ mv.visitLdcInsn(delegateValue);
+ mv
+ .visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ "javax/swing/UIDefaults", "put",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+ mv.visitInsn(Opcodes.POP);
+ }
+
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(3, 2);
+ mv.visitEnd();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.objectweb.asm.ClassAdapter#visitMethod(int,
+ * java.lang.String, java.lang.String, java.lang.String,
+ * java.lang.String[])
+ */
+ @Override
+ public MethodVisitor visitMethod(final int access, final String name,
+ final String desc, final String signature,
+ final String[] exceptions) {
+ // System.out.println("Visiting " + name + ":" + desc + ":"
+ // + signature);
+ if (LafMainClassAugmenter.METHOD_NAME.equals(name)) {
+ // possible candidate for weaving. Check if has __ already
+ if (!this.existingMethods.contains(this.prefix + name)) {
+ // effectively renames the existing method prepending __
+ // to the name
+ return this.cv.visitMethod(access, this.prefix + name,
+ desc, signature, exceptions);
+ }
+ }
+ // preserve the existing method as is
+ return this.cv.visitMethod(access, name, desc, signature,
+ exceptions);
+ }
+ }
+
+ /**
+ * Augments a single class with additional UI behaviour.
+ *
+ * @param dir
+ * Root directory for the library that contains the class.
+ * @param name
+ * Fully-qualified class name.
+ * @throws AugmentException
+ * If the augmentation process failed.
+ * @return The superclass name.
+ */
+ protected synchronized String augmentClass(String dir, final String name) {
+ if (this.isVerbose)
+ System.out.println("Working on LAF main class " + name);
+
+ // gets an input stream to read the bytecode of the class
+ String resource = dir + File.separator + name.replace('.', '/')
+ + ".class";
+
+ Method origMethod = null;
+ try {
+ ClassLoader cl = new URLClassLoader(new URL[] { new File(dir)
+ .toURL() }, LafMainClassAugmenter.class.getClassLoader());
+ Class<?> clazz = cl.loadClass(name);
+ origMethod = clazz.getDeclaredMethod(
+ LafMainClassAugmenter.METHOD_NAME,
+ new Class[] { UIDefaults.class });
+ } catch (NoSuchMethodException nsme) {
+ origMethod = null;
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new AugmentException(name, e);
+ }
+
+ Set<String> existingMethods = null;
+ InputStream is = null;
+ try {
+ is = new FileInputStream(resource);
+ ClassReader cr = new ClassReader(is);
+ // ClassWriter cw = new ClassWriter(false);
+ InfoClassVisitor infoAdapter = new InfoClassVisitor();
+ cr.accept(infoAdapter, false);
+ existingMethods = infoAdapter.getMethods();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new AugmentException(name, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ }
+ }
+
+ // Augment the class (overriding the existing file).
+ byte[] b;
+ String superClassName = null;
+ try {
+ is = new FileInputStream(resource);
+ ClassReader cr = new ClassReader(is);
+ ClassWriter cw = new ClassWriter(false);
+ AugmentClassAdapter cv = new AugmentClassAdapter(cw,
+ existingMethods, origMethod);
+ cr.accept(cv, false);
+ b = cw.toByteArray();
+ superClassName = cv.getSuperClassName();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new AugmentException(name, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ }
+ }
+
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(resource);
+ fos.write(b);
+ if (this.isVerbose)
+ System.out.println("Updated resource " + resource);
+ } catch (Exception e) {
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException ioe) {
+ }
+ }
+ }
+
+ return superClassName;
+ }
+
+ /**
+ * Creates a new augmenter.
+ */
+ public LafMainClassAugmenter() {
+ super();
+ }
+
+ /**
+ * Processes a single file or a directory, augmenting all main LAF classes.
+ *
+ * @param toStrip
+ * The leading prefix to strip from the file names. Is used to
+ * create fully-qualified class name.
+ * @param file
+ * File resource (can point to a single file or to a directory).
+ * @param mainClassName
+ * The class name of the main LAF class.
+ * @throws AugmentException
+ * If the augmentation process failed.
+ */
+ public void process(String toStrip, File file, String mainClassName) {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ for (int i = 0; i < children.length; i++) {
+ this.process(toStrip, children[i], mainClassName);
+ }
+ } else {
+ String className = file.getAbsolutePath().substring(
+ toStrip.length() + 1);
+ className = className.replace(File.separatorChar, '.');
+ className = className.substring(0, className.length() - 6);
+ if (mainClassName.equals(className)) {
+ String superClassName = this.augmentClass(toStrip, className);
+ // Create forwarding delegates
+ for (int i = 0; i < this.delegatesToAdd.length; i++) {
+ String uiKey = this.delegatesToAdd[i];
+ String uiSuperClassName = Utils.getUtils().getUIDelegate(
+ uiKey, superClassName);
+
+ Class<?>[] uiSuperCtrParams = null;
+ try {
+ Class<?> uiSuperClazz = Class.forName(uiSuperClassName);
+ Constructor<?>[] uiSuperCtrs = uiSuperClazz
+ .getDeclaredConstructors();
+ if (uiSuperCtrs.length != 1)
+ throw new AugmentException(
+ "Unsupported base UI class "
+ + uiSuperClassName
+ + " - not exactly one ctr");
+ Constructor<?> uiSuperCtr = uiSuperCtrs[0];
+ uiSuperCtrParams = uiSuperCtr.getParameterTypes();
+ if (uiSuperCtrParams.length > 1)
+ throw new AugmentException(
+ "Unsupported base UI class "
+ + uiSuperClassName + " - "
+ + uiSuperCtrParams.length
+ + " parameters");
+ } catch (ClassNotFoundException cnfe) {
+ throw new AugmentException(
+ "Failed locating base UI class", cnfe);
+ }
+
+ int lastDotIndex = className.lastIndexOf('.');
+ String packageName = (lastDotIndex >= 0) ? className
+ .substring(0, lastDotIndex) : "";
+
+ String uiClassName = "__Forwarding__" + uiKey;
+
+ String resource = toStrip
+ + File.separator
+ + (packageName + File.separator + uiClassName)
+ .replace('.', File.separatorChar)
+ + ".class";
+
+ System.out.println("...Creating forwarding delegate");
+ System.out.println("...... at '" + resource + "'");
+ System.out.println("...... with class name '" + uiClassName
+ + "'");
+ System.out.println("...... package '" + packageName + "'");
+ System.out.println("...... super impl '" + uiSuperClassName
+ + "'");
+
+ byte[] b = null;
+ if (uiSuperCtrParams.length == 0) {
+ b = UiDelegateWriterEmptyCtr.createClass(packageName,
+ uiClassName, uiSuperClassName);
+ } else {
+ b = UiDelegateWriterOneParamCtr.createClass(
+ packageName, uiClassName, uiSuperClassName,
+ Utils.getTypeDesc(uiSuperCtrParams[0]));
+ }
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(resource);
+ fos.write(b);
+ if (this.isVerbose)
+ System.out.println("...Created resource "
+ + resource);
+ } catch (Exception e) {
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException ioe) {
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the verbosity.
+ *
+ * @param isVerbose
+ * New value for augmentation process verbosity.
+ */
+ public void setVerbose(boolean isVerbose) {
+ this.isVerbose = isVerbose;
+ }
+
+ /**
+ * Sets the list of delegates that need to be added.
+ *
+ * @param delegatesToAdd
+ * The list of delegates that need to be added.
+ */
+ public void setDelegatesToAdd(String[] delegatesToAdd) {
+ this.delegatesToAdd = delegatesToAdd;
+ }
+
+ /**
+ * Test methods.
+ *
+ * @param args
+ * @throws AugmentException
+ */
+ public static void main(final String args[]) throws AugmentException {
+ if (args.length == 0) {
+ System.out
+ .println("Usage : java ... LafMainClassAugmenter [-verbose]");
+ System.out
+ .println("\t -main main_class_name -dir class_directory ");
+ System.out.println("\t -delegates delegate_ui_ids");
+ return;
+ }
+
+ LafMainClassAugmenter augmenter = new LafMainClassAugmenter();
+ int argNum = 0;
+ String mainLafClassName = null;
+ String startDir = null;
+ while (argNum < args.length) {
+ String currArg = args[argNum];
+ if ("-verbose".equals(currArg)) {
+ augmenter.setVerbose(true);
+ argNum++;
+ continue;
+ }
+ if ("-main".equals(currArg)) {
+ argNum++;
+ mainLafClassName = args[argNum];
+ argNum++;
+ continue;
+ }
+ if ("-dir".equals(currArg)) {
+ argNum++;
+ startDir = args[argNum];
+ argNum++;
+ continue;
+ }
+ if ("-delegates".equals(currArg)) {
+ argNum++;
+ augmenter.setDelegatesToAdd(args[argNum].split(";"));
+ argNum++;
+ continue;
+ }
+ break;
+ }
+
+ File starter = new File(startDir);
+ augmenter.process(starter.getAbsolutePath(), starter, mainLafClassName);
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateAugmenter.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateAugmenter.java
new file mode 100644
index 0000000..61b8bf0
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateAugmenter.java
@@ -0,0 +1,717 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Augments the UI classes with laf-widget behaviour. Is based on sample adapter
+ * from ASM distribution.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class UiDelegateAugmenter {
+ /**
+ * Set of methods to change (augment).
+ */
+ private Set<String> methodsToChange;
+
+ /**
+ * Verbosity indication.
+ */
+ private boolean isVerbose;
+
+ /**
+ * Class adapter that augments the UI functionality.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class AugmentClassAdapter extends ClassAdapter implements Opcodes {
+ /**
+ * Contains all method names.
+ */
+ private Set<String> existingMethods;
+
+ /**
+ * Contains all field names.
+ */
+ private Set<String> existingFields;
+
+ /**
+ * Contains all methods that will be augmented. Key is {@link String},
+ * value is {@link Method}.
+ */
+ private Map<String, Method> methodsToAugment;
+
+ /**
+ * Prefix for delegate methods that will be added.
+ */
+ private String prefix;
+
+ /**
+ * Creates a new augmentor.
+ *
+ * @param cv
+ * Class visitor to recreate the non-augmented methods.
+ * @param existingMethods
+ * Contains all method names.
+ * @param existingFields
+ * Contains all field names.
+ * @param methodsToAugment
+ * Contains all methods that will be augmented. Key is
+ * {@link String}, value is {@link Method}.
+ */
+ public AugmentClassAdapter(final ClassVisitor cv,
+ Set<String> existingMethods, Set<String> existingFields,
+ Map<String, Method> methodsToAugment) {
+ super(cv);
+ this.existingMethods = existingMethods;
+ this.existingFields = existingFields;
+ this.methodsToAugment = methodsToAugment;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.objectweb.asm.ClassAdapter#visit(int, int, java.lang.String,
+ * java.lang.String, java.lang.String, java.lang.String[])
+ */
+ @Override
+ public void visit(final int version, final int access,
+ final String name, final String signature,
+ final String superName, final String[] interfaces) {
+ this.prefix = "__" + name.replaceAll("/", "__") + "__";
+ super
+ .visit(version, access, name, signature, superName,
+ interfaces);
+
+ // Check if need to add the lafWidgets field
+ if (!this.existingFields.contains("lafWidgets")) {
+ FieldVisitor fv = this.visitField(Opcodes.ACC_PROTECTED,
+ "lafWidgets", "Ljava/util/Set;", null, null);
+ fv.visitEnd();
+ }
+
+ // We have three separate cases for each function that we
+ // want to augment:
+ //
+ // 1. The current .class has both function and the __ version -
+ // already has been augmented. Can be ignored.
+ //
+ // 2. The current .class has function but doesn't have the __
+ // version. Than, the original function has already been renamed to
+ // __ (in the visitMethod). We need to create a new version for this
+ // function that performs pre-logic, calls __ and performs the
+ // post-logic.
+ //
+ // 3. The current .class doesn't have neither the function nor
+ // the __ version. In this case we need to create the __ version
+ // that calls super (with the original name) and the function that
+ // performs pre-logic, calls __ and performs the post-logic.
+ for (Iterator<String> it = UiDelegateAugmenter.this.methodsToChange
+ .iterator(); it.hasNext();) {
+ String methodName = it.next();
+
+ if (!this.methodsToAugment.containsKey(methodName))
+ continue;
+
+ boolean hasOriginal = this.existingMethods.contains(methodName);
+ boolean hasDelegate = this.existingMethods.contains(this.prefix
+ + methodName);
+
+ Method method = this.methodsToAugment.get(methodName);
+ String methodSignature = Utils.getMethodDesc(method);
+ int paramCount = method.getParameterTypes().length;
+ if (UiDelegateAugmenter.this.isVerbose)
+ System.out.println("... Augmenting " + methodName + " "
+ + methodSignature + " : original - " + hasOriginal
+ + ", delegate - " + hasDelegate + ", " + paramCount
+ + " params");
+
+ if (!hasDelegate) {
+ if (methodName.equals("installUI")) {
+ this.augmentInstallUIMethod(!hasOriginal, name,
+ superName, methodSignature);
+ } else {
+ if (method.getParameterTypes().length == 0) {
+ this.augmentVoidMethod(!hasOriginal, name,
+ superName, methodName, method
+ .getModifiers());
+ } else {
+ this.augmentSingleParameterMethod(!hasOriginal,
+ name, superName, methodName, method
+ .getModifiers(), methodSignature);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Augments void UI method (w/o parameters).
+ *
+ * @param toSynthOriginal
+ * Indication whether we need to create an empty (only call
+ * to super()) implementation.
+ * @param className
+ * Class name.
+ * @param superClassName
+ * Super class name (relevant for generating empty
+ * implementation).
+ * @param methodName
+ * Method name.
+ */
+ public void augmentVoidMethod(boolean toSynthOriginal,
+ String className, String superClassName, String methodName,
+ int methodModifiers) {
+ int modifierOpcode = Opcodes.ACC_PUBLIC;
+ if (Modifier.isProtected(methodModifiers))
+ modifierOpcode = Opcodes.ACC_PROTECTED;
+
+ // Some ASM woodoo. The code below was generated by using
+ // ASMifierClassVisitor.
+ if (toSynthOriginal) {
+ MethodVisitor mv = this.cv.visitMethod(modifierOpcode,
+ this.prefix + methodName, "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName,
+ methodName, "()V");
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+
+ MethodVisitor mv = this.cv.visitMethod(modifierOpcode, methodName,
+ "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, this.prefix
+ + methodName, "()V");
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitFieldInsn(Opcodes.GETFIELD, className, "lafWidgets",
+ "Ljava/util/Set;");
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Set",
+ "iterator", "()Ljava/util/Iterator;");
+ mv.visitVarInsn(Opcodes.ASTORE, 1);
+ Label l0 = new Label();
+ mv.visitJumpInsn(Opcodes.GOTO, l0);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Iterator",
+ "next", "()Ljava/lang/Object;");
+ mv.visitTypeInsn(Opcodes.CHECKCAST,
+ "org/pushingpixels/lafwidget/LafWidget");
+ mv.visitVarInsn(Opcodes.ASTORE, 2);
+ mv.visitVarInsn(Opcodes.ALOAD, 2);
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE,
+ "org/pushingpixels/lafwidget/LafWidget", methodName, "()V");
+ mv.visitLabel(l0);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Iterator",
+ "hasNext", "()Z");
+ mv.visitJumpInsn(Opcodes.IFNE, l1);
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(1, 3);
+ mv.visitEnd();
+ }
+
+ /**
+ * Augments single-parameter UI method.
+ *
+ * @param toSynthOriginal
+ * Indication whether we need to create an empty (only call
+ * to super()) implementation.
+ * @param className
+ * Class name.
+ * @param superClassName
+ * Super class name (relevant for generating empty
+ * implementation).
+ * @param methodName
+ * Method name.
+ * @param functionDesc
+ * Function signature (using JNI style declaration). Example
+ * for <code>void installUI(JButton button)</code>:
+ * <code>(Ljavax/swing/JButton;)V</code>.
+ */
+ public void augmentSingleParameterMethod(boolean toSynthOriginal,
+ String className, String superClassName, String methodName,
+ int methodModifiers, String functionDesc) {
+
+ int modifierOpcode = Opcodes.ACC_PUBLIC;
+ if (Modifier.isProtected(methodModifiers))
+ modifierOpcode = Opcodes.ACC_PROTECTED;
+
+ // Some ASM woodoo. The code below was generated by using
+ // ASMifierClassVisitor.
+ if (toSynthOriginal) {
+ if (UiDelegateAugmenter.this.isVerbose) {
+ System.out.println("... Creating empty '" + methodName
+ + functionDesc + "' forwarding to super '"
+ + superClassName + "'");
+ }
+ MethodVisitor mv = this.cv.visitMethod(modifierOpcode,
+ this.prefix + methodName, functionDesc, null, null);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName,
+ methodName, functionDesc);
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+
+ if (UiDelegateAugmenter.this.isVerbose) {
+ System.out.println("... Augmenting '" + methodName
+ + functionDesc + "'");
+ }
+ MethodVisitor mv = this.cv.visitMethod(modifierOpcode, methodName,
+ functionDesc, null, null);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, this.prefix
+ + methodName, functionDesc);
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitFieldInsn(Opcodes.GETFIELD, className, "lafWidgets",
+ "Ljava/util/Set;");
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Set",
+ "iterator", "()Ljava/util/Iterator;");
+ mv.visitVarInsn(Opcodes.ASTORE, 2);
+ Label l0 = new Label();
+ mv.visitJumpInsn(Opcodes.GOTO, l0);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitVarInsn(Opcodes.ALOAD, 2);
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Iterator",
+ "next", "()Ljava/lang/Object;");
+ mv.visitTypeInsn(Opcodes.CHECKCAST,
+ "org/pushingpixels/lafwidget/LafWidget");
+ mv.visitVarInsn(Opcodes.ASTORE, 3);
+ mv.visitVarInsn(Opcodes.ALOAD, 3);
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE,
+ "org/pushingpixels/lafwidget/LafWidget", methodName, "()V");
+ mv.visitLabel(l0);
+ mv.visitVarInsn(Opcodes.ALOAD, 2);
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Iterator",
+ "hasNext", "()Z");
+ mv.visitJumpInsn(Opcodes.IFNE, l1);
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(2, 4);
+ mv.visitEnd();
+ }
+
+ /**
+ * Augments the <code>installUI</code> method that is assumed to always
+ * have a single parameter.
+ *
+ * @param toSynthOriginal
+ * Indication whether we need to create an empty (only call
+ * to super()) implementation.
+ * @param className
+ * Class name.
+ * @param superClassName
+ * Super class name (relevant for generating empty
+ * implementation).
+ * @param functionDesc
+ * Function signature (using JNI style declaration). Example
+ * for <code>void installUI(JButton button)</code>:
+ * <code>(Ljavax/swing/JButton;)V</code>.
+ */
+ public void augmentInstallUIMethod(boolean toSynthOriginal,
+ String className, String superClassName, String functionDesc) {
+ // Some ASM woodoo. The code below was generated by using
+ // ASMifierClassVisitor.
+ if (toSynthOriginal) {
+ MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PUBLIC,
+ this.prefix + "installUI",
+ "(Ljavax/swing/JComponent;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName,
+ "installUI", "(Ljavax/swing/JComponent;)V");
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+
+ MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PUBLIC,
+ "installUI", "(Ljavax/swing/JComponent;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "org/pushingpixels/lafwidget/LafWidgetRepository",
+ "getRepository",
+ "()Lorg/pushingpixels/lafwidget/LafWidgetRepository;");
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+ "org/pushingpixels/lafwidget/LafWidgetRepository",
+ "getMatchingWidgets",
+ "(Ljavax/swing/JComponent;)Ljava/util/Set;");
+ mv.visitFieldInsn(Opcodes.PUTFIELD, className, "lafWidgets",
+ "Ljava/util/Set;");
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, this.prefix
+ + "installUI", "(Ljavax/swing/JComponent;)V");
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitFieldInsn(Opcodes.GETFIELD, className, "lafWidgets",
+ "Ljava/util/Set;");
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Set",
+ "iterator", "()Ljava/util/Iterator;");
+ mv.visitVarInsn(Opcodes.ASTORE, 2);
+ Label l0 = new Label();
+ mv.visitJumpInsn(Opcodes.GOTO, l0);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ mv.visitVarInsn(Opcodes.ALOAD, 2);
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Iterator",
+ "next", "()Ljava/lang/Object;");
+ mv.visitTypeInsn(Opcodes.CHECKCAST,
+ "org/pushingpixels/lafwidget/LafWidget");
+ mv.visitVarInsn(Opcodes.ASTORE, 3);
+ mv.visitVarInsn(Opcodes.ALOAD, 3);
+ mv
+ .visitMethodInsn(Opcodes.INVOKEINTERFACE,
+ "org/pushingpixels/lafwidget/LafWidget",
+ "installUI", "()V");
+ mv.visitLabel(l0);
+ mv.visitVarInsn(Opcodes.ALOAD, 2);
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Iterator",
+ "hasNext", "()Z");
+ mv.visitJumpInsn(Opcodes.IFNE, l1);
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(3, 4);
+ mv.visitEnd();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.objectweb.asm.ClassAdapter#visitMethod(int,
+ * java.lang.String, java.lang.String, java.lang.String,
+ * java.lang.String[])
+ */
+ @Override
+ public MethodVisitor visitMethod(final int access, final String name,
+ final String desc, final String signature,
+ final String[] exceptions) {
+ if (UiDelegateAugmenter.this.methodsToChange.contains(name)) {
+ // possible candidate for weaving. Check if has __ already
+ if (!this.existingMethods.contains(this.prefix + name)) {
+ // effectively renames the existing method prepending __
+ // to the name
+ if (UiDelegateAugmenter.this.isVerbose)
+ System.out.println("... renaming '" + name + "(" + desc
+ + ")' to '" + (this.prefix + name) + "'");
+ return this.cv.visitMethod(access, this.prefix + name,
+ desc, signature, exceptions);
+ }
+ }
+ // preserve the existing method as is
+ return this.cv.visitMethod(access, name, desc, signature,
+ exceptions);
+ }
+ }
+
+ /**
+ * Augments a single class with additional UI behaviour.
+ *
+ * @param dir
+ * Root directory for the library that contains the class.
+ * @param name
+ * Fully-qualified class name.
+ * @throws AugmentException
+ * If the augmentation process failed.
+ */
+ protected synchronized void augmentClass(String dir, final String name) {
+ if (this.isVerbose)
+ System.out.println("Working on " + name);
+ // gets an input stream to read the bytecode of the class
+ String resource = dir + File.separator + name.replace('.', '/')
+ + ".class";
+
+ Map<String, Method> methodsToAugment = new HashMap<String, Method>();
+ try {
+ ClassLoader cl = new URLClassLoader(new URL[] { new File(dir)
+ .toURL() }, UiDelegateAugmenter.class.getClassLoader());
+ Class<?> clazz = cl.loadClass(name);
+ if (!ComponentUI.class.isAssignableFrom(clazz)) {
+ if (this.isVerbose)
+ System.out
+ .println("Not augmenting resource, doesn't extend ComponentUI");
+ return;
+ }
+ // Start iterating over all methods and see what do we
+ // need to augment
+ while (clazz != null) {
+ Method[] methods = clazz.getDeclaredMethods();
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+
+ // this check is very important - store only the
+ // first occurence (in the closest ancestor).
+ if (methodsToAugment.containsKey(method.getName()))
+ continue;
+
+ if (this.methodsToChange.contains(method.getName())) {
+ // check if it can be supported
+ boolean isSupportedRetType = (void.class == method
+ .getReturnType());
+ Class<?>[] paramTypes = method.getParameterTypes();
+ boolean isSupportedParamList = (paramTypes.length == 0)
+ || ((paramTypes.length == 1) && (JComponent.class
+ .isAssignableFrom(paramTypes[0])));
+ if (isSupportedRetType && isSupportedParamList)
+ if (Modifier.isProtected(method.getModifiers())
+ || Modifier.isPublic(method.getModifiers())) {
+ methodsToAugment.put(method.getName(), method);
+ } else {
+ if (isVerbose) {
+ System.out
+ .println("Not augmenting private "
+ + name + "."
+ + method.getName());
+ }
+ }
+ else
+ throw new AugmentException("Method '"
+ + method.getName() + "' in class '" + name
+ + "' has unsupported signature");
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+ } catch (Exception e) {
+ throw new AugmentException(name, e);
+ }
+
+ Set<String> existingMethods = null;
+ Set<String> existingFields = null;
+ InputStream is = null;
+ try {
+ is = new FileInputStream(resource);
+ ClassReader cr = new ClassReader(is);
+ InfoClassVisitor infoAdapter = new InfoClassVisitor();
+ cr.accept(infoAdapter, false);
+ existingMethods = infoAdapter.getMethods();
+ existingFields = infoAdapter.getFields();
+ } catch (Exception e) {
+ throw new AugmentException(name, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ }
+ }
+
+ // See if the' lafWidgets' field is already defined. In this
+ // case the class is *not* augmented
+ if (existingFields.contains("lafWidgets")) {
+ if (this.isVerbose)
+ System.out
+ .println("Not augmenting resource, field 'lafWidgets' is present");
+ return;
+ }
+
+ // Augment the class (overriding the existing file).
+ byte[] b;
+ try {
+ is = new FileInputStream(resource);
+ ClassReader cr = new ClassReader(is);
+ ClassWriter cw = new ClassWriter(false);
+ ClassVisitor cv = new AugmentClassAdapter(cw, existingMethods,
+ existingFields, methodsToAugment);
+ cr.accept(cv, false);
+ b = cw.toByteArray();
+ } catch (Exception e) {
+ throw new AugmentException(name, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ }
+ }
+
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(resource);
+ fos.write(b);
+ if (this.isVerbose)
+ System.out.println("Updated resource " + resource);
+ } catch (Exception e) {
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException ioe) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates the new augmenter.
+ */
+ public UiDelegateAugmenter() {
+ super();
+
+ this.methodsToChange = new HashSet<String>();
+ this.methodsToChange.add("installUI");
+ this.methodsToChange.add("installDefaults");
+ this.methodsToChange.add("installComponents");
+ this.methodsToChange.add("installListeners");
+ this.methodsToChange.add("uninstallUI");
+ this.methodsToChange.add("uninstallDefaults");
+ this.methodsToChange.add("uninstallComponents");
+ this.methodsToChange.add("uninstallListeners");
+ }
+
+ /**
+ * Processes a single file or a directory, augmenting all relevant classes.
+ *
+ * @param toStrip
+ * The leading prefix to strip from the file names. Is used to
+ * create fully-qualified class name.
+ * @param file
+ * File resource (can point to a single file or to a directory).
+ * @param pattern
+ * Pattern to apply to the file name (of the single file). If the
+ * file name matches the pattern, the relevant class is
+ * augmented.
+ * @throws AugmentException
+ * If the augmentation process failed.
+ */
+ public void process(String toStrip, File file, Pattern pattern)
+ throws AugmentException {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ for (int i = 0; i < children.length; i++) {
+ this.process(toStrip, children[i], pattern);
+ }
+ } else {
+ Matcher m = pattern.matcher(file.getName());
+ if (m.matches()) {
+ String className = file.getAbsolutePath().substring(
+ toStrip.length() + 1);
+ className = className.replace(File.separatorChar, '.');
+ this.augmentClass(toStrip, className.substring(0, className
+ .length() - 6));
+ }
+ }
+ }
+
+ /**
+ * Sets the verbosity.
+ *
+ * @param isVerbose
+ * New value for augmentation process verbosity.
+ */
+ public void setVerbose(boolean isVerbose) {
+ this.isVerbose = isVerbose;
+ }
+
+ /**
+ * Test method.
+ *
+ * @param args
+ * @throws AugmentException
+ */
+ public static void main(final String args[]) throws AugmentException {
+ if (args.length == 0) {
+ System.out
+ .println("Usage : java ... UiDelegateAugmenter [-verbose] [-pattern class_pattern] file_resource");
+ System.out
+ .println("\tIf -verbose option is specified, the augmenter prints out its actions.");
+ System.out
+ .println("\tIf -pattern option is specified, its value is used as a wildcard "
+ + "for matching the classes for augmentation.");
+ System.out
+ .println("\tThe last parameter can point to either a file or a directory. "
+ + "The directory should be the root directory for classes.");
+ return;
+ }
+
+ UiDelegateAugmenter uiDelegateAugmenter = new UiDelegateAugmenter();
+
+ int argNum = 0;
+ String pattern = ".*UI\u002Eclass";
+ while (true) {
+ String currArg = args[argNum];
+ if ("-verbose".equals(currArg)) {
+ uiDelegateAugmenter.setVerbose(true);
+ argNum++;
+ continue;
+ }
+ if ("-pattern".equals(currArg)) {
+ argNum++;
+ pattern = args[argNum];
+ argNum++;
+ continue;
+ }
+ break;
+ }
+
+ Pattern p = Pattern.compile(pattern);
+
+ File starter = new File(args[argNum]);
+ uiDelegateAugmenter.process(starter.getAbsolutePath(), starter, p);
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateType.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateType.java
new file mode 100644
index 0000000..e40cbe3
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateType.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import org.apache.tools.ant.types.DataType;
+
+/**
+ * Ant type for storing <code>delegate</code> elements of
+ * {@link AugmentMainTask} task.
+ *
+ * <p>
+ * Represents the following build snippet:
+ * </p>
+ *
+ * <pre><code>
+ * <delegate name="ToolTipUI" />
+ * </code></pre>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class UiDelegateType extends DataType {
+ /**
+ * Delegate name.
+ */
+ private String name;
+
+ /**
+ * Creates new instance.
+ */
+ public UiDelegateType() {
+ super();
+ }
+
+ /**
+ * Sets the delegate name.
+ *
+ * @param name
+ * Delegate name.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the delegate name.
+ *
+ * @return Delegate name.
+ */
+ public String getName() {
+ return this.name;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateUpdateAugmenter.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateUpdateAugmenter.java
new file mode 100644
index 0000000..a6ee8ae
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateUpdateAugmenter.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import java.awt.Graphics;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Augments the UI classes with laf-widget behaviour. Is based on sample adapter
+ * from ASM distribution.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class UiDelegateUpdateAugmenter {
+ /**
+ * Verbosity indication.
+ */
+ private boolean isVerbose;
+
+ /**
+ * Class adapter that augments the UI functionality.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class AugmentClassAdapter extends ClassAdapter implements Opcodes {
+ /**
+ * Contains all method names.
+ */
+ private Set<String> existingMethods;
+
+ /**
+ * The <code>update</code> method to augment.
+ */
+ private Method updateMethod;
+
+ /**
+ * Prefix for delegate methods that will be added.
+ */
+ private String prefix;
+
+ /**
+ * Creates a new augmentor.
+ *
+ * @param cv
+ * Class visitor to recreate the non-augmented methods.
+ * @param existingMethods
+ * Contains all method names.
+ * @param updateMethod
+ * The <code>update</code> method to augment.
+ */
+ public AugmentClassAdapter(final ClassVisitor cv,
+ Set<String> existingMethods, Method updateMethod) {
+ super(cv);
+ this.existingMethods = existingMethods;
+ this.updateMethod = updateMethod;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.objectweb.asm.ClassAdapter#visit(int, int, java.lang.String,
+ * java.lang.String, java.lang.String, java.lang.String[])
+ */
+ @Override
+ public void visit(final int version, final int access,
+ final String name, final String signature,
+ final String superName, final String[] interfaces) {
+ this.prefix = "__" + name.replaceAll("/", "__") + "__";
+ super
+ .visit(version, access, name, signature, superName,
+ interfaces);
+ // We have three separate cases for each function that we
+ // want to augment:
+ //
+ // 1. The current .class has both function and the __ version -
+ // already has been augmented. Can be ignored.
+ //
+ // 2. The current .class has function but doesn't have the __
+ // version. Than, the original function has already been renamed to
+ // __ (in the visitMethod). We need to create a new version for this
+ // function that performs pre-logic, calls __ and performs the
+ // post-logic.
+ //
+ // 3. The current .class doesn't have neither the function nor
+ // the __ version. In this case we need to create the __ version
+ // that calls super (with the original name) and the function that
+ // performs pre-logic, calls __ and performs the post-logic.
+
+ boolean hasOriginal = this.existingMethods.contains("update");
+ boolean hasDelegate = this.existingMethods.contains(this.prefix
+ + "update");
+
+ String methodSignature = Utils.getMethodDesc(this.updateMethod);
+ int paramCount = this.updateMethod.getParameterTypes().length;
+ if (UiDelegateUpdateAugmenter.this.isVerbose)
+ System.out.println("... Augmenting update " + methodSignature
+ + " : original - " + hasOriginal + ", delegate - "
+ + hasDelegate + ", " + paramCount + " params");
+
+ if (!hasDelegate) {
+ this.augmentUpdateMethod(!hasOriginal, name, superName,
+ methodSignature);
+ }
+ }
+
+ /**
+ * Augments the <code>update</code> method that is assumed to always
+ * have two parameters.
+ *
+ * @param toSynthOriginal
+ * Indication whether we need to create an empty (only call
+ * to super()) implementation.
+ * @param className
+ * Class name.
+ * @param superClassName
+ * Super class name (relevant for generating empty
+ * implementation).
+ * @param functionDesc
+ * Function signature (using JNI style declaration). Example
+ * for <code>void installUI(JButton button)</code>:
+ * <code>(Ljavax/swing/JButton;)V</code>.
+ */
+ public void augmentUpdateMethod(boolean toSynthOriginal,
+ String className, String superClassName, String functionDesc) {
+ // Some ASM woodoo. The code below was generated by using
+ // ASMifierClassVisitor.
+ if (toSynthOriginal) {
+ MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PUBLIC,
+ this.prefix + "update",
+ "(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V", null,
+ null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName,
+ "update",
+ "(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 3);
+ mv.visitEnd();
+ }
+
+ MethodVisitor mv = this.cv.visitMethod(ACC_PUBLIC, "update",
+ "(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V", null,
+ null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/awt/Graphics", "create",
+ "()Ljava/awt/Graphics;");
+ mv.visitTypeInsn(CHECKCAST, "java/awt/Graphics2D");
+ mv.visitVarInsn(ASTORE, 3);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(INVOKESTATIC,
+ "org/pushingpixels/lafwidget/utils/RenderingUtils",
+ "installDesktopHints",
+ "(Ljava/awt/Graphics2D;Ljava/awt/Component;)V");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(INVOKEVIRTUAL, className,
+ this.prefix + "update",
+ "(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V");
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/awt/Graphics2D", "dispose",
+ "()V");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 4);
+ mv.visitEnd();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.objectweb.asm.ClassAdapter#visitMethod(int,
+ * java.lang.String, java.lang.String, java.lang.String,
+ * java.lang.String[])
+ */
+ @Override
+ public MethodVisitor visitMethod(final int access, final String name,
+ final String desc, final String signature,
+ final String[] exceptions) {
+ if ("update".equals(name)) {
+ // possible candidate for weaving. Check if has __ already
+ if (!this.existingMethods.contains(this.prefix + name)) {
+ // effectively renames the existing method prepending __
+ // to the name
+ if (UiDelegateUpdateAugmenter.this.isVerbose)
+ System.out.println("... renaming '" + name + "(" + desc
+ + ")' to '" + (this.prefix + name) + "'");
+ return this.cv.visitMethod(access, this.prefix + name,
+ desc, signature, exceptions);
+ }
+ }
+ // preserve the existing method as is
+ return this.cv.visitMethod(access, name, desc, signature,
+ exceptions);
+ }
+ }
+
+ /**
+ * Augments a single class with additional UI behaviour.
+ *
+ * @param dir
+ * Root directory for the library that contains the class.
+ * @param name
+ * Fully-qualified class name.
+ * @throws AugmentException
+ * If the augmentation process failed.
+ */
+ protected synchronized void augmentClass(String dir, final String name) {
+ if (this.isVerbose)
+ System.out.println("Working on " + name);
+ // gets an input stream to read the bytecode of the class
+ String resource = dir + File.separator + name.replace('.', '/')
+ + ".class";
+
+ Method updateMethod = null;
+ try {
+ ClassLoader cl = new URLClassLoader(new URL[] { new File(dir)
+ .toURL() }, UiDelegateUpdateAugmenter.class
+ .getClassLoader());
+ Class<?> clazz = cl.loadClass(name);
+ if (!ComponentUI.class.isAssignableFrom(clazz)) {
+ if (this.isVerbose)
+ System.out
+ .println("Not augmenting resource, doesn't extend ComponentUI");
+ return;
+ }
+ // Start iterating over all methods and see what do w
+ // need to augment
+ while (clazz != null) {
+ try {
+ updateMethod = clazz.getDeclaredMethod("update",
+ Graphics.class, JComponent.class);
+ } catch (NoSuchMethodException nsme) {
+ }
+ if (updateMethod != null)
+ break;
+ clazz = clazz.getSuperclass();
+ }
+ } catch (Exception e) {
+ throw new AugmentException(name, e);
+ }
+
+ Set<String> existingMethods = null;
+ InputStream is = null;
+ try {
+ is = new FileInputStream(resource);
+ ClassReader cr = new ClassReader(is);
+ InfoClassVisitor infoAdapter = new InfoClassVisitor();
+ cr.accept(infoAdapter, false);
+ existingMethods = infoAdapter.getMethods();
+ } catch (Exception e) {
+ throw new AugmentException(name, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ }
+ }
+
+ // Augment the class (overriding the existing file).
+ byte[] b;
+ try {
+ is = new FileInputStream(resource);
+ ClassReader cr = new ClassReader(is);
+ ClassWriter cw = new ClassWriter(false);
+ ClassVisitor cv = new AugmentClassAdapter(cw, existingMethods,
+ updateMethod);
+ cr.accept(cv, false);
+ b = cw.toByteArray();
+ } catch (Exception e) {
+ throw new AugmentException(name, e);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ }
+ }
+
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(resource);
+ fos.write(b);
+ if (this.isVerbose)
+ System.out.println("Updated resource " + resource);
+ } catch (Exception e) {
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException ioe) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates the new augmenter.
+ */
+ public UiDelegateUpdateAugmenter() {
+ super();
+ }
+
+ /**
+ * Processes a single file or a directory, augmenting all relevant classes.
+ *
+ * @param toStrip
+ * The leading prefix to strip from the file names. Is used to
+ * create fully-qualified class name.
+ * @param file
+ * File resource (can point to a single file or to a directory).
+ * @param pattern
+ * Pattern to apply to the file name (of the single file). If the
+ * file name matches the pattern, the relevant class is
+ * augmented.
+ * @throws AugmentException
+ * If the augmentation process failed.
+ */
+ public void process(String toStrip, File file, Pattern pattern)
+ throws AugmentException {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ for (int i = 0; i < children.length; i++) {
+ this.process(toStrip, children[i], pattern);
+ }
+ } else {
+ Matcher m = pattern.matcher(file.getName());
+ if (m.matches()) {
+ String className = file.getAbsolutePath().substring(
+ toStrip.length() + 1);
+ className = className.replace(File.separatorChar, '.');
+ this.augmentClass(toStrip, className.substring(0, className
+ .length() - 6));
+ }
+ }
+ }
+
+ /**
+ * Sets the verbosity.
+ *
+ * @param isVerbose
+ * New value for augmentation process verbosity.
+ */
+ public void setVerbose(boolean isVerbose) {
+ this.isVerbose = isVerbose;
+ }
+
+ /**
+ * Test method.
+ *
+ * @param args
+ * @throws AugmentException
+ */
+ public static void main(final String args[]) throws AugmentException {
+ if (args.length == 0) {
+ System.out
+ .println("Usage : java ... UiDelegateUpdateAugmenter [-verbose] [-pattern class_pattern] file_resource");
+ System.out
+ .println("\tIf -verbose option is specified, the augmenter prints out its actions.");
+ System.out
+ .println("\tIf -pattern option is specified, its value is used as a wildcard "
+ + "for matching the classes for augmentation.");
+ System.out
+ .println("\tThe last parameter can point to either a file or a directory. "
+ + "The directory should be the root directory for classes.");
+ return;
+ }
+
+ UiDelegateUpdateAugmenter uiDelegateAugmenter = new UiDelegateUpdateAugmenter();
+
+ int argNum = 0;
+ String pattern = ".*UI\u002Eclass";
+ while (true) {
+ String currArg = args[argNum];
+ if ("-verbose".equals(currArg)) {
+ uiDelegateAugmenter.setVerbose(true);
+ argNum++;
+ continue;
+ }
+ if ("-pattern".equals(currArg)) {
+ argNum++;
+ pattern = args[argNum];
+ argNum++;
+ continue;
+ }
+ break;
+ }
+
+ Pattern p = Pattern.compile(pattern);
+
+ File starter = new File(args[argNum]);
+ uiDelegateAugmenter.process(starter.getAbsolutePath(), starter, p);
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateWriterEmptyCtr.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateWriterEmptyCtr.java
new file mode 100644
index 0000000..83d80f7
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateWriterEmptyCtr.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import org.objectweb.asm.*;
+
+/**
+ * Bytecode writer for a forwarding UI delegate class with a single constructor
+ * that gets no parameters.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class UiDelegateWriterEmptyCtr extends ClassWriter implements Opcodes {
+ /**
+ * Creates a new bytecode writer.
+ */
+ public UiDelegateWriterEmptyCtr() {
+ super(false);
+ }
+
+ /**
+ * Creates a new class.
+ *
+ * @param packageName
+ * Package name.
+ * @param className
+ * Class name.
+ * @param superClassName
+ * Superclass name.
+ * @return Class bytecode contents.
+ */
+ public static byte[] createClass(String packageName, String className,
+ String superClassName) {
+
+ packageName = packageName.replace('.', '/');
+ superClassName = superClassName.replace('.', '/');
+
+ UiDelegateWriterEmptyCtr cw = new UiDelegateWriterEmptyCtr();
+
+ MethodVisitor mv;
+
+ cw.visit(Opcodes.V1_2, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER,
+ packageName + "/" + className, null, superClassName, null);
+
+ cw.visitSource(className + ".java", null);
+
+ mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
+ "createUI",
+ "(Ljavax/swing/JComponent;)Ljavax/swing/plaf/ComponentUI;",
+ null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(Opcodes.NEW, packageName + "/" + className);
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
+ packageName + "/" + className, "<init>", "()V");
+ mv.visitInsn(Opcodes.ARETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+
+ mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName, "<init>",
+ "()V");
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateWriterOneParamCtr.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateWriterOneParamCtr.java
new file mode 100644
index 0000000..068a9be
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/UiDelegateWriterOneParamCtr.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import org.objectweb.asm.*;
+
+/**
+ * Bytecode writer for a forwarding UI delegate class with a single constructor
+ * that gets one parameter.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class UiDelegateWriterOneParamCtr extends ClassWriter implements Opcodes {
+ /**
+ * Creates a new bytecode writer.
+ */
+ public UiDelegateWriterOneParamCtr() {
+ super(false);
+ }
+
+ /**
+ * Creates a new class.
+ *
+ * @param packageName
+ * Package name.
+ * @param className
+ * Class name.
+ * @param superClassName
+ * Superclass name.
+ * @param paramClassDesc
+ * Description of the parameter classes.
+ * @return Class bytecode contents.
+ */
+ public static byte[] createClass(String packageName, String className,
+ String superClassName, String paramClassDesc) {
+
+ packageName = packageName.replace('.', '/');
+ superClassName = superClassName.replace('.', '/');
+
+ UiDelegateWriterOneParamCtr cw = new UiDelegateWriterOneParamCtr();
+
+ MethodVisitor mv;
+
+ cw.visit(Opcodes.V1_2, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER,
+ packageName + "/" + className, null, superClassName, null);
+
+ cw.visitSource(className + ".java", null);
+
+ mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
+ "createUI",
+ "(Ljavax/swing/JComponent;)Ljavax/swing/plaf/ComponentUI;",
+ null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(Opcodes.NEW, packageName + "/" + className);
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitTypeInsn(Opcodes.CHECKCAST, paramClassDesc.substring(1,
+ paramClassDesc.length() - 1));
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
+ packageName + "/" + className, "<init>", "(" + paramClassDesc
+ + ")V");
+ mv.visitInsn(Opcodes.ARETURN);
+ mv.visitMaxs(3, 1);
+ mv.visitEnd();
+
+ mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(" + paramClassDesc
+ + ")V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName, "<init>", "("
+ + paramClassDesc + ")V");
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/Utils.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/Utils.java
new file mode 100644
index 0000000..cd14f77
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/ant/Utils.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.ant;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.plaf.basic.BasicLookAndFeel;
+import javax.swing.plaf.metal.MetalLookAndFeel;
+
+/**
+ * Utility functions.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Utils {
+ /**
+ * Maps the LAF names.
+ */
+ protected Map<String, String> lafMap;
+
+ /**
+ * Singleton.
+ */
+ protected static Utils instance = new Utils();
+
+ /**
+ * IDs of all UI delegates.
+ */
+ public static final String[] UI_IDS = new String[] { "ButtonUI",
+ "CheckBoxUI", "CheckBoxMenuItemUI", "ColorChooserUI", "ComboBoxUI",
+ "DesktopIconUI", "DesktopPaneUI", "EditorPaneUI",
+ "FormattedTextFieldUI", "InternalFrameUI", "LabelUI", "ListUI",
+ "MenuUI", "MenuBarUI", "MenuItemUI", "OptionPaneUI", "PanelUI",
+ "PasswordFieldUI", "PopupMenuUI", "PopupMenuSeparatorUI",
+ "ProgressBarUI", "RadioButtonUI", "RadioButtonMenuItemUI",
+ "RootPaneUI", "ScrollBarUI", "ScrollPaneUI", "SplitPaneUI",
+ "SliderUI", "SeparatorUI", "SpinnerUI", "ToolBarSeparatorUI",
+ "TabbedPaneUI", "TableUI", "TableHeaderUI", "TextAreaUI",
+ "TextFieldUI", "TextPaneUI", "ToggleButtonUI", "ToolBarUI",
+ "ToolTipUI", "TreeUI", "ViewportUI" };
+
+ /**
+ * Constructor.
+ */
+ private Utils() {
+ this.lafMap = new HashMap<String, String>();
+ this.lafMap.put(BasicLookAndFeel.class.getName(),
+ "javax.swing.plaf.basic.Basic");
+ this.lafMap.put(MetalLookAndFeel.class.getName(),
+ "javax.swing.plaf.metal.Metal");
+ }
+
+ /**
+ * Returns instance.
+ *
+ * @return Instance.
+ */
+ public static Utils getUtils() {
+ return Utils.instance;
+ }
+
+ /**
+ * Returns fully-qualified class name for the UI delegate based on the
+ * specified parameters.
+ *
+ * @param uiKey
+ * UI key.
+ * @param lafClassName
+ * Class name of the LAF.
+ * @return Fully-qualified class name for the UI delegate. The LAF hierarchy
+ * is searched starting from the specified class name and up. For
+ * example, if the second parameter points to
+ * {@link MetalLookAndFeel}, the metal delegate classname is
+ * returned if exists; otherwise the basic delegate classname is
+ * returned.
+ */
+ public String getUIDelegate(String uiKey, String lafClassName) {
+ try {
+ lafClassName = lafClassName.replace('/', '.');
+ return this.getUIDelegate(uiKey, Class.forName(lafClassName));
+ } catch (ClassNotFoundException cnfe) {
+ throw new AugmentException(
+ "Class '" + lafClassName + "' not found", cnfe);
+ }
+ }
+
+ /**
+ * Returns fully-qualified class name for the UI delegate based on the
+ * specified parameters.
+ *
+ * @param uiKey
+ * UI key.
+ * @param origLafClazz
+ * LAF class.
+ * @return Fully-qualified class name for the UI delegate. The LAF hierarchy
+ * is searched starting from the specified class and up. For
+ * example, if the second parameter points to
+ * {@link MetalLookAndFeel}, the metal delegate classname is
+ * returned if exists; otherwise the basic delegate classname is
+ * returned.
+ */
+ public String getUIDelegate(String uiKey, Class<?> origLafClazz) {
+ Class<?> lafClazz = origLafClazz;
+ while (lafClazz != null) {
+ String prefix = (String) this.lafMap.get(lafClazz.getName());
+ if (prefix != null) {
+ String fullClassName = prefix + uiKey;
+ Class<?> delegateClazz = null;
+ try {
+ delegateClazz = Class.forName(fullClassName);
+ } catch (ClassNotFoundException cnfe) {
+ }
+ if (delegateClazz != null)
+ return fullClassName;
+ }
+ lafClazz = lafClazz.getSuperclass();
+ }
+ throw new AugmentException("No match for '" + uiKey + "' in '"
+ + origLafClazz.getName() + "' hierarchy");
+ }
+
+ /**
+ * Returns JNI-compliant description of the specified class (type). For
+ * example, for <code>JButton[]</code> this function will return
+ * <code>[Ljavax/swing/JButton;</code>.
+ *
+ * @param clazz
+ * Class.
+ * @return JNI-compliant class (type) description.
+ */
+ public static String getTypeDesc(Class<?> clazz) {
+ if (clazz.isArray())
+ return "[" + Utils.getTypeDesc(clazz.getComponentType());
+ if (clazz == void.class)
+ return "V";
+ if (clazz == boolean.class)
+ return "Z";
+ if (clazz == byte.class)
+ return "B";
+ if (clazz == char.class)
+ return "C";
+ if (clazz == short.class)
+ return "S";
+ if (clazz == int.class)
+ return "I";
+ if (clazz == long.class)
+ return "J";
+ if (clazz == float.class)
+ return "F";
+ if (clazz == double.class)
+ return "D";
+ return "L" + clazz.getName().replace('.', '/') + ";";
+ }
+
+ /**
+ * Returns JNI-compliant description of the specified method. For example,
+ * for <code>void installUI(JButton button)</code> this function will
+ * return <code>(Ljavax/swing/JButton;)V</code>.
+ *
+ * @param method
+ * Method.
+ * @return JNI-compliant method description.
+ */
+ public static String getMethodDesc(Method method) {
+ StringBuffer result = new StringBuffer();
+ result.append("(");
+ Class<?>[] paramClasses = method.getParameterTypes();
+ for (int i = 0; i < paramClasses.length; i++) {
+ Class<?> paramClass = paramClasses[i];
+ result.append(Utils.getTypeDesc(paramClass));
+ }
+ result.append(")");
+ result.append(Utils.getTypeDesc(method.getReturnType()));
+ return result.toString();
+ }
+
+ /**
+ * Test app.
+ *
+ * @param args
+ */
+ public static void main(String[] args) {
+ for (Map.Entry<String, String> entry : Utils.instance.lafMap.entrySet()) {
+ //Map.Entry entry = (Map.Entry) it.next();
+ String lafClassName = entry.getKey();
+ System.out.println(lafClassName);
+ String prefix = entry.getValue();
+ for (int i = 0; i < Utils.UI_IDS.length; i++) {
+ String uiClassName = prefix + Utils.UI_IDS[i];
+ try {
+ Class<?> uiClazz = Class.forName(uiClassName);
+ System.out.println("\t" + Utils.UI_IDS[i]);
+ Constructor<?>[] ctrs = uiClazz.getDeclaredConstructors();
+ for (int j = 0; j < ctrs.length; j++) {
+ Constructor<?> ctr = ctrs[j];
+ Class<?>[] ctrArgs = ctr.getParameterTypes();
+ System.out.print("\t\t" + ctrArgs.length + " args : ");
+ for (int k = 0; k < ctrArgs.length; k++)
+ System.out.print(ctrArgs[k].getName() + ",");
+ System.out.println();
+ }
+ } catch (ClassNotFoundException cnfe) {
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/SwingBugUtilities.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/SwingBugUtilities.java
new file mode 100644
index 0000000..48b1a65
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/SwingBugUtilities.java
@@ -0,0 +1,52 @@
+/*
+ * SwingBugUtilities.java
+ *
+ * Created on March 30, 2007, 12:27 AM
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.swing;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.Timer;
+
+/**
+ * Contains some utility methods applicable to any swing application.
+ *
+ * @author nigel
+ */
+public class SwingBugUtilities {
+
+ /** Creates a new instance of SwingBugUtilities */
+ private SwingBugUtilities() {
+ }
+
+ /**
+ * Runs the supplied class after a certain period of time, the thread
+ * will be executed in the EDT.
+ *
+ * @param execute The runnable object whose method will be called after the
+ * specified delay
+ * @param after The delay in ms before the event will be called
+ */
+ public static void invokeAfter(final Runnable execute, int after){
+ Timer timer = new Timer(after,new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ execute.run();
+ }
+ });
+
+ timer.setRepeats(false);
+ timer.start();
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/borders/AbstractImageBorder.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/borders/AbstractImageBorder.java
new file mode 100644
index 0000000..7a1c234
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/borders/AbstractImageBorder.java
@@ -0,0 +1,155 @@
+/*
+ * AbstractImageBorder.java
+ *
+ * Created on March 27, 2007, 9:19 AM
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.swing.borders;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.net.URL;
+
+import org.pushingpixels.lafwidget.contrib.blogofbug.utility.ImageUtilities;
+
+
+/**
+ *
+ * @author nigel
+ */
+public class AbstractImageBorder {
+
+ /**
+ * Creates a new ImageBofder loading the image from the supplied URL
+ * @param imageURL The location of the image to use
+ * @param imageInsets The insets around the edge of the image that allow the cookie-cut-and-stretch of the image
+ * around the edge of the border
+ */
+ public AbstractImageBorder(URL imageURL, Insets imageInsets){
+ this.imageInsets = imageInsets;
+ borderImage = ImageUtilities.loadCompatibleImage(imageURL.toString());
+ }
+
+ /**
+ * Creates a new ImageBorder using the supplied image and the insets
+ *
+ * @param borderImage The image to be used as the border
+ * @param imageInsets The insets around the edge of the image that allow the cookie-cut-and-stretch of the image
+ * around the edge of the border
+ */
+ public AbstractImageBorder(BufferedImage borderImage, Insets imageInsets) {
+ this.borderImage = borderImage;
+ this.imageInsets = imageInsets;
+ }
+
+
+ protected BufferedImage borderImage;
+
+
+ protected Insets imageInsets;
+
+
+ /**
+ * Paints the border around the specified component
+ *
+ * @param compWidth width of the target component
+ * @param compHeight height of the target component
+ * @param g The graphics context
+ * @param x The x offset
+ * @param y The y offset
+ * @param width The width
+ * @param height The height
+ */
+ public void paintBorder(int compWidth, int compHeight, Graphics g, int x, int y, int width, int height) {
+
+ Graphics2D g2 = (Graphics2D) g;
+ int imageWidth = borderImage.getWidth();
+ int imageHeight = borderImage.getHeight();
+
+ //Top-left corner
+ drawSlice(g2,0,0,imageInsets.left,imageInsets.top,0,0);
+ //Top-right corner
+ drawSlice(g2,imageWidth-imageInsets.right,0,imageInsets.right,imageInsets.bottom,
+ compWidth-imageInsets.right,0);
+ //Bottom-left corner
+ drawSlice(g2,0,imageHeight-imageInsets.bottom,imageInsets.left,imageInsets.bottom,0,compHeight-imageInsets.bottom);
+
+ //Bottom-right corner
+ drawSlice(g2,imageWidth-imageInsets.right,imageHeight-imageInsets.bottom,imageInsets.left,imageInsets.bottom,compWidth-imageInsets.right,compHeight-imageInsets.bottom);
+
+ //Draw left side
+ g2.drawImage(borderImage,0,imageInsets.top,imageInsets.left,compHeight-imageInsets.bottom,
+ 0,imageInsets.top,imageInsets.left,imageHeight-imageInsets.bottom,null);
+
+ //Draw right side
+ g2.drawImage(borderImage,compWidth-imageInsets.right ,imageInsets.top+6,compWidth,compHeight-imageInsets.bottom,
+ imageWidth-imageInsets.right,imageInsets.top,imageWidth,imageHeight-imageInsets.bottom,null);
+
+ //Draw top side
+ g2.drawImage(borderImage,imageInsets.left ,0,compWidth-imageInsets.left,imageInsets.top,
+ imageInsets.left,0,imageWidth-imageInsets.right,imageInsets.top,null);
+
+ //Draw bottom side
+ g2.drawImage(borderImage,imageInsets.left ,compHeight-imageInsets.bottom,compWidth-imageInsets.left,compHeight,
+ imageInsets.left,imageHeight-imageInsets.bottom,imageWidth-imageInsets.right,imageHeight,null);
+
+ }
+
+
+ /**
+ * Sets the insets around the edge of the image to be used to cookie cut the image into a border
+ *
+ * @param insets The edges of the image
+ */
+ public void setInsets(Insets insets){
+ this.imageInsets = insets;
+ }
+
+ public Insets getImageInsets(){
+ return (Insets) imageInsets.clone();
+ }
+
+ /**
+ * Paints a stretched version of the center of the image (as the border is drawn
+ * first, then the component paints itself) so that the component can use it in
+ * its own paint if the border lends itself to having a centre area over-painted
+ *
+ * @param g2 The graphics context
+ * @param compWidth width of the target component
+ * @param compHeight height of the target component
+ */
+ public void paintCenter(Graphics2D g2, int compWidth,int compHeight){
+ int imageWidth = borderImage.getWidth();
+ int imageHeight = borderImage.getHeight();
+
+ //draw center
+ g2.drawImage(borderImage, imageInsets.left,imageInsets.top,compWidth-imageInsets.right,compHeight-imageInsets.bottom,
+ imageInsets.left,imageInsets.top,imageWidth-imageInsets.right,imageHeight-imageInsets.bottom,null);
+ }
+
+ /**
+ * Draws a slicde from the specified image onto the graphics area
+ *
+ * @param g2 The graphics context to draw into
+ * @param sliceX The x-cordinate of the slice
+ * @param sliceY The y-cordinate of the slice
+ * @param sliceWidth The width of the slice
+ * @param sliceHeight The height of the slice
+ * @param destX The target location of the drawn slice
+ * @param destY The target location of the drawn slice
+ */
+ private void drawSlice(Graphics2D g2,int sliceX, int sliceY, int sliceWidth, int sliceHeight, int destX, int destY){
+ g2.drawImage(borderImage,destX,destY,destX+sliceWidth,destY+sliceHeight,
+ sliceX,sliceY,sliceX+sliceWidth,sliceY+sliceHeight,null);
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/borders/ImageBorder.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/borders/ImageBorder.java
new file mode 100644
index 0000000..313bdf0
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/borders/ImageBorder.java
@@ -0,0 +1,119 @@
+/*
+ * ImageBorder.java
+ *
+ * Created on January 15, 2007, 6:54 AM
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.swing.borders;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.net.URL;
+
+import javax.swing.border.AbstractBorder;
+
+/**
+ * ImageBorder takes an image and breaks out the corners and the top, left, right, and bottom
+ * borders stretching them to fill the space around which the border is drawn
+ * @author nigel
+ */
+public class ImageBorder extends AbstractBorder{
+ protected AbstractImageBorder borderRenderer;
+ boolean paintBorder = true;
+
+ /**
+ * Creates a new ImageBorder using the supplied image and the insets
+ *
+ * @param borderImage The image to be used as the border
+ * @param imageInsets The insets around the edge of the image that allow the cookie-cut-and-stretch of the image
+ * around the edge of the border
+ */
+ public ImageBorder(BufferedImage borderImage, Insets imageInsets) {
+ borderRenderer= new AbstractImageBorder(borderImage, imageInsets);
+ }
+
+
+ /**
+ * Creates a new ImageBofder loading the image from the supplied URL
+ * @param imageURL The location of the image to use
+ * @param imageInsets The insets around the edge of the image that allow the cookie-cut-and-stretch of the image
+ * around the edge of the border
+ */
+ public ImageBorder(URL imageURL, Insets imageInsets){
+ borderRenderer = new AbstractImageBorder(imageURL,imageInsets);
+ }
+
+ /**
+ * Paints the border around the specified component
+ *
+ * @param c The component to paint the border on
+ * @param g The graphics context
+ * @param x The x offset
+ * @param y The y offset
+ * @param width The width
+ * @param height The height
+ */
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+ if (!paintBorder){
+ return;
+ }
+ borderRenderer.paintBorder(c.getWidth(),c.getHeight(),g,x,y,width,height);
+ }
+
+ /**
+ * Controls wether or not the border is actually painted or not.
+ *
+ * @param paintBorder If false then will not draw the border. Useful if the border is being used to show a selected item
+ */
+ public void setPaintBorder(boolean paintBorder){
+ this.paintBorder = paintBorder;
+ }
+
+ /**
+ * Gets the insets of the image back (subtracting from the component size would give you the renderable
+ * area
+ *
+ * @param c The component to which the border will be applied
+ * @return The insets of the border
+ */
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return borderRenderer.getImageInsets();
+ }
+
+ /**
+ * Gets the insets of the image and returns in the in the supplied Insets instance
+ *
+ * @param c The component to which the border will be applied
+ * @param i A pre-created insets object
+ * @return The insets of the border
+ */
+ @Override
+ public Insets getBorderInsets(Component c, Insets i){
+ Insets imageInsets = borderRenderer.getImageInsets();
+ i.top = imageInsets.top;
+ i.bottom = imageInsets.bottom;
+ i.left = imageInsets.left;
+ i.right = imageInsets.right;
+ return i;
+ }
+
+ public void paintCenter(Graphics2D g2, Component c) {
+ if (paintBorder){
+ borderRenderer.paintCenter(g2,c.getWidth(),c.getHeight());
+ }
+ }
+
+
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/AbstractCarouselMenuAction.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/AbstractCarouselMenuAction.java
new file mode 100644
index 0000000..c958e06
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/AbstractCarouselMenuAction.java
@@ -0,0 +1,43 @@
+/*
+ * AbstractCarouselMenuAction.java
+ *
+ * Created on January 14, 2007, 11:49 AM
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.swing.components;
+
+import java.net.URL;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+
+/**
+ * An action for the CarouselMenuAction. May be later refactored into a more useful set of fields or removed completely.
+ * @author nigel
+ */
+public abstract class AbstractCarouselMenuAction extends AbstractAction{
+ /**
+ * A very high resolution (recommend at leat 128x128) image to associate with the action
+ */
+ public static final String ACTION_IMAGE_URL = "actionImageURL";
+
+ /**
+ * Creates a new instance of AbstractCarouselMenuAction
+ * @param image The image
+ * @param label The text
+ */
+ public AbstractCarouselMenuAction(URL image, String label) {
+ super();
+ this.putValue(ACTION_IMAGE_URL,image);
+ this.putValue(Action.SHORT_DESCRIPTION,label);
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/GradientPanel.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/GradientPanel.java
new file mode 100644
index 0000000..f826788
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/GradientPanel.java
@@ -0,0 +1,155 @@
+/*
+ * GradientPanel.java
+ *
+ * Created on November 22, 2006, 10:11 AM
+ *
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.swing.components;
+
+import java.awt.Color;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.TexturePaint;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import javax.swing.JPanel;
+
+/**
+ * Container that draws (in an optimized way) a gradient in the background
+ * @author bug
+ *
+ * Really trivial panel to draw the nice graduated background.
+ */
+public class GradientPanel extends JPanel implements ComponentListener{
+ /**
+ * Gradient start colour
+ */
+ protected Color start;
+ /**
+ * Gradient end color
+ */
+ protected Color end;
+ /**
+ * Gradient painter
+ */
+ protected GradientPaint gp = null;
+ /**
+ * A pre-rendered gradient in an image
+ */
+ protected BufferedImage cache = null;
+
+
+ /**
+ * Set the background to a single color
+ * @param color The color for a solid background
+ */
+ @Override
+ public void setBackground(Color color){
+ this.start = color;
+ this.end = color;
+ super.setBackground(color);
+ }
+
+ /**
+ * Sets two background colors for a gradient
+ * @param start Top (first) color
+ * @param end Bottom (final) color
+ */
+ public void setBackground(Color start, Color end){
+ this.start = start;
+ this.end = end;
+ makeGradient();
+ }
+
+ /**
+ * paints the gradient.
+ * @param graphics The graphics context
+ */
+ @Override
+ public void paintComponent(Graphics graphics) {
+ if (start == end){
+ super.paintComponent(graphics);
+ return;
+ }
+
+ Graphics2D g2 = (Graphics2D) graphics;
+
+ /*
+ //Thanks Romain Guy
+ if (cache == null || cache.getHeight() != getHeight()) {
+ cache = new BufferedImage(2, getHeight(),
+ BufferedImage.TYPE_INT_RGB);
+ Graphics2D g2d = cache.createGraphics();
+
+ GradientPaint paint = new GradientPaint(0, 0, start,
+ 0, getHeight(), end);
+ g2d.setPaint(paint);
+ g2d.fillRect(0, 0, 2, getHeight());
+ g2d.dispose();
+ }
+ g2.setPaint(new TexturePaint(cache, new Rectangle(0, 0, 1, getHeight())));
+ g2.fillRect(0, 0, getWidth(), getHeight());
+
+ //g2.drawImage(cache, 0, 0, getWidth(), getHeight(), null);
+ */
+ gp = new GradientPaint((float)(getWidth()/2), (float) getY(), start, (float) (getWidth()/2), (float) getHeight(), end, false);
+
+ g2.setPaint(gp);
+ g2.fillRect(0,0,getWidth(),getHeight());
+ super.paintChildren(graphics);
+ }
+
+ /**
+ * Pre-renders the gradient
+ */
+ private void makeGradient(){
+ gp = new GradientPaint((float)(getWidth()/2), (float) getY(), start, (float) (getWidth()/2), (float) getHeight(), end, false);
+ }
+
+ /**
+ * Recalculates the gradient when it's resized
+ * @param componentEvent The event object
+ */
+ @Override
+ public void componentResized(ComponentEvent componentEvent) {
+ makeGradient();
+ }
+
+ /**
+ * Ignored
+ * @param componentEvent The component event
+ */
+ @Override
+ public void componentShown(ComponentEvent componentEvent) {
+ makeGradient();
+ }
+
+ /**
+ * Not used *
+ * @param componentEvent The event
+ */
+ @Override
+ public void componentMoved(ComponentEvent componentEvent){}
+
+ /**
+ * Not used *
+ * @param componentEvent The event
+ */
+ @Override
+ public void componentHidden(ComponentEvent componentEvent){}
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/ImageLabel.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/ImageLabel.java
new file mode 100644
index 0000000..d43a9a4
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/ImageLabel.java
@@ -0,0 +1,74 @@
+/*
+ * ImageLabel.java
+ *
+ * Created on November 22, 2006, 9:53 AM
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.swing.components;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.RenderingHints;
+import java.awt.image.ImageObserver;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+
+/**
+ * A simple component that scales an image to fit inside the size of the component.
+ * @author nigel
+ */
+public class ImageLabel extends JLabel{
+ /**
+ * The image that ends up getting scaled
+ */
+ protected ImageIcon imageIcon = null;
+
+ /**
+ * Creates a new instance of ImageLabel. The prefered width and height will
+ * be set to the dimensions of the image
+ *
+ * @param icon The image to display
+ */
+ public ImageLabel(ImageIcon icon) {
+ super(icon);
+ this.imageIcon = icon;
+ setPreferredSize(new Dimension(icon.getIconWidth(), icon.getIconHeight()));
+ }
+
+ /**
+ * Creates a new instance of ImageLabel, setting the preferred rendering size to
+ * the supplied dimensions
+ *
+ * @param icon The image to place on the label
+ * @param width The prefered width
+ * @param height The prefered height
+ */
+ public ImageLabel(ImageIcon icon, int width, int height){
+ this(icon);
+ setPreferredSize(new Dimension(width,height));
+ }
+
+ /**
+ * Paints the label scaling the image to the appropriate size
+ * @param graphics The graphics context
+ */
+ @Override
+ public void paintComponent(Graphics graphics) {
+ Image image = this.imageIcon.getImage();
+ ImageObserver observer = imageIcon.getImageObserver();
+ ((Graphics2D)graphics).setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
+ ((Graphics2D)graphics).setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ graphics.drawImage(image, 0,0,getWidth(),getHeight(),0,0,image.getWidth(observer),image.getHeight(observer),observer);
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/JCarosel.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/JCarosel.java
new file mode 100644
index 0000000..c38d4e7
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/JCarosel.java
@@ -0,0 +1,451 @@
+/*
+ * JCarosel.java
+ *
+ * Created on November 22, 2006, 7:27 PM
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.swing.components;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import org.pushingpixels.lafwidget.contrib.blogofbug.swing.SwingBugUtilities;
+import org.pushingpixels.lafwidget.contrib.blogofbug.swing.layout.CaroselLayout;
+import org.pushingpixels.lafwidget.contrib.blogofbug.utility.ImageUtilities;
+
+
+/**
+ * A carousel component which lays out components around a carousel, moving each
+ * to the front as it is clicked on. Double clicking will fire the action
+ * associated with the compnent if it has one, or give it the focus if it does
+ * not.
+ *
+ * Note this will be changed to only allow RichComponents in the future.
+ *
+ * @author bug
+ */
+public class JCarosel extends GradientPanel implements MouseListener,
+ MouseWheelListener {
+ /**
+ * Set to true if the performance should be measured. Debugging purposes
+ * only
+ */
+ private static final boolean MEASURE_PERFORMANCE = true;
+ /**
+ * The property that is set when a new component comes to the front. To use
+ * it add a property change listener for it, useful for keeping animations
+ * synchronized.
+ */
+ public static final String FRONT_COMPONENT_CHANGE = "frontComponentChanged";
+ /**
+ * The layout being used for the carousel
+ */
+ protected CaroselLayout layout;
+ /**
+ * The last component the wheel selected. Used to ensure we don't move too
+ * far around why they are wheeling and they turn back on themselves
+ * creating a nasty oscilation in the animation (everything works fine, it's
+ * just not purty)
+ */
+ protected Component lastWheeledTo = null;
+
+ /**
+ * The prefered width of the components in the container, everything will be
+ * scaled to this width for neutral scaling (1.0)
+ */
+ protected int DEFAULT_CONTENT_WIDTH = 64;
+
+ /**
+ * Delay in milliseconds from the first click to the start of the spin, this
+ * gives implementors using a double click anywhere to fire an event a
+ * chance for their users to get the double click message before the
+ * component spins around.
+ */
+ protected int spinStartDelay = 200;
+
+ /**
+ * Creates a new instance of JCarosel
+ */
+ public JCarosel() {
+ layout = new CaroselLayout(this);
+ this.setLayout(layout);
+ this.addMouseWheelListener(this);
+ setContentWidth(DEFAULT_CONTENT_WIDTH);
+ }
+
+ /**
+ * Creates a new Carousel specifying the prefered width up front
+ *
+ * @param contentWidth
+ * The prefered width of component at neutral scale (3 or 9
+ * o'clock)
+ */
+ public JCarosel(int contentWidth) {
+ this();
+ setContentWidth(contentWidth);
+ }
+
+ /**
+ * Sets the prefered width of the components inside the carousel, this is
+ * the neutral width that will change as the component is scaled, but at 9
+ * and 6 o'clock where the scale is one they will be this width.
+ *
+ * @param contentWidth
+ * The prefered width.
+ */
+ public void setContentWidth(int contentWidth) {
+ layout.setNeutralContentWidth(contentWidth);
+ }
+
+ /**
+ * If set to true the carousel will fade out components as they move away
+ * from the front of the carousel (6 o'clock)
+ *
+ * @param useDepthBased
+ * True to fade components as they move to the back, false to not
+ * do it
+ */
+ public void setDepthBasedAlpha(boolean useDepthBased) {
+ layout.setDepthBasedAlpha(useDepthBased);
+ }
+
+ /**
+ * Specifies which type of CarouselLayout to be used to lay the component
+ * out around the carousel Any looping layout can be used. Mobius strip
+ * layout anyone?
+ *
+ * @param layout
+ * The carousel layout to use
+ */
+ public void setLayout(CaroselLayout layout) {
+ this.layout = layout;
+ super.setLayout(layout);
+ }
+
+ /**
+ * Adds a component to the carousel
+ *
+ * @param component
+ * The component to add to the carousel
+ * @return The component
+ */
+ @Override
+ public Component add(Component component) {
+ add("", component);
+ component.setForeground(Color.WHITE);
+ component.setBackground(Color.BLACK);
+ bringToFront(getComponent(0));
+ validate();
+ return component;
+ }
+
+ /**
+ * Adds an image to the carousel
+ *
+ * @param image
+ * The image to add
+ * @param text
+ * The text label
+ * @return The component created, normally a reflected image label
+ */
+ public Component add(Image image, String text) {
+ ReflectedImageLabel component = new ReflectedImageLabel(image, text);
+ component.addMouseListener(this);
+ return add(component);
+ }
+
+ /**
+ * Removes the component from the carousel
+ *
+ * @param component
+ * The component to remove
+ */
+ @Override
+ public void remove(Component component) {
+ super.remove(component);
+ if (getComponentCount() > 0) {
+ bringToFront(getComponent(0));
+ }
+ invalidate();
+ validate();
+ }
+
+ /**
+ * The image to add and it's width and height
+ *
+ * @param imageURL
+ * The URL
+ * @param width
+ * The desired rendering width
+ * @param height
+ * The desired rendering height
+ * @return The component created
+ * @deprecated This function will be removed, use add(String imageURL)
+ * instead.
+ */
+ public Component add(String imageURL, int width, int height) {
+ ReflectedImageLabel component = new ReflectedImageLabel(imageURL,
+ width, height);
+ component.addMouseListener(this);
+ return add(component);
+ }
+
+ /**
+ * Add the image, and it's label to the carousel
+ *
+ * @param imageURL
+ * The image URL
+ * @param text
+ * The text label
+ * @param width
+ * The width
+ * @param height
+ * The height
+ * @return The component created to hold the image
+ * @deprecated This function will be removed, use setNeutralWidth() on
+ * JCarousel instead.
+ */
+ public Component add(String imageURL, String text, int width, int height) {
+ ReflectedImageLabel component = new ReflectedImageLabel(imageURL, text,
+ width, height);
+ component.addMouseListener(this);
+ return add(component);
+ }
+
+ /**
+ * Brings the specified component to the front of the carousel
+ *
+ * @param component
+ * The component to bring to the front
+ */
+ public void bringToFront(Component component) {
+ firePropertyChange(FRONT_COMPONENT_CHANGE, getComponent(0), component);
+ layout.setFrontMostComponent(component);
+ }
+
+ /**
+ * Which component is at the front
+ *
+ * @return The component at the front (by default 6 o'clock)
+ */
+ public Component getFrontmost() {
+ return getComponent(0);
+ }
+
+ /**
+ * Bring the "clicked" component to the front. Delays by 200ms to allow for
+ * a double click
+ *
+ * @param mouseEvent
+ * Brings the component clicked on by the mouse to the front
+ */
+ @Override
+ public void mouseClicked(final MouseEvent mouseEvent) {
+ if (mouseEvent.getClickCount() == 1) {
+ SwingBugUtilities.invokeAfter(new Runnable() {
+ @Override
+ public void run() {
+ bringToFront((Component) mouseEvent.getSource());
+ }
+ }, spinStartDelay);
+ }
+ }
+
+ /**
+ * Sets the delay between clicking on a component in the carousel, and the
+ * spin starting to move that component to the front. The longer it is, the
+ * easier it is to double click on a non-front component
+ *
+ * @param spinStartDelay
+ */
+ public void setSpinStartDelay(int spinStartDelay) {
+ this.spinStartDelay = spinStartDelay;
+ }
+
+ /**
+ * Returns the spin start delay
+ *
+ * @return The delay in ms between the click and the spin
+ */
+ public int getSpinStartDelay() {
+ return spinStartDelay;
+ }
+
+ /**
+ * Inserts a component at the specified index
+ *
+ * @param i
+ * The index
+ * @param comp
+ * The component
+ */
+ public void insertComponentAt(int i, Component comp) {
+ add(comp);
+ layout.moveComponentTo(i, comp);
+ }
+
+ /**
+ * Inserts a new object at a specific location
+ *
+ * @param i
+ * The position on the carousel
+ * @param imageURL
+ * The URL of the image
+ * @param width
+ * The width of the image
+ * @param height
+ * The height of the image
+ * @return The component created to show the image (usually a
+ * ReflectedImageLabel but this may change)
+ */
+ public Component insertAt(int i, String imageURL, int width, int height) {
+ Component comp = add(imageURL, width, height);
+ layout.moveComponentTo(i, comp);
+ return comp;
+ }
+
+ /**
+ * Inserts a new object at a specific location
+ *
+ * @param i
+ * The position on the carousel
+ * @param imageURL
+ * The URL of the image
+ * @param text
+ * The text label
+ * @param width
+ * The prefered width of the image
+ * @param height
+ * The prefered height of the image
+ * @return The component created to represent the image, currently reflected
+ * image label but may change
+ */
+ public Component insertAt(int i, String imageURL, String text, int width,
+ int height) {
+ Component comp = add(imageURL, text, width, height);
+ layout.moveComponentTo(i, comp);
+ return comp;
+ }
+
+ /**
+ * Moves everything to their final positions
+ *
+ */
+ public void finalizeLayoutImmediately() {
+ layout.layoutContainer(this);
+ layout.finalizeLayoutImmediately();
+ repaint();
+ }
+
+ /**
+ * Not interested
+ *
+ * @param mouseEvent
+ * Ignored
+ */
+ @Override
+ public void mousePressed(MouseEvent mouseEvent) {
+ }
+
+ /**
+ * Not interested
+ *
+ * @param mouseEvent
+ * Ignored
+ */
+ @Override
+ public void mouseReleased(MouseEvent mouseEvent) {
+ }
+
+ /**
+ * Not interested
+ *
+ * @param mouseEvent
+ * Ignored
+ */
+ @Override
+ public void mouseEntered(MouseEvent mouseEvent) {
+ }
+
+ /**
+ * Not interested
+ *
+ * @param mouseEvent
+ * Ignored
+ */
+ @Override
+ public void mouseExited(MouseEvent mouseEvent) {
+ }
+
+ /**
+ * When event received will spin the carousel to select the next object.
+ * Because the wheel can be spun quicker than the carousel animates it keeps
+ * track of the target (so the user may have selected something three
+ * components away, althought the animation has not yet finished moving past
+ * the first comopnent)
+ *
+ * @param mouseWheelEvent
+ * The event object
+ */
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent mouseWheelEvent) {
+
+ if (mouseWheelEvent.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
+ int amount = mouseWheelEvent.getWheelRotation();
+ if (lastWheeledTo == null) {
+ lastWheeledTo = getFrontmost();
+ }
+ int lastPosition = layout.getComponentIndex(lastWheeledTo);
+ int frontMostPosition = layout.getComponentIndex(getComponent(0));
+ // Don't over spin
+ if (Math.abs(lastPosition - frontMostPosition) > layout
+ .getComponentCount() / 4) {
+ return;
+ }
+ if (amount > 0) {
+ lastWheeledTo = layout.getPreviousComponent(lastWheeledTo);
+ } else {
+ lastWheeledTo = layout.getNextComponent(lastWheeledTo);
+ }
+ bringToFront(lastWheeledTo);
+ }
+ }
+
+ /**
+ * Adds a new image to the carousel
+ *
+ * @param imageURL
+ * The image
+ * @return The component created
+ */
+ public Component add(String imageURL) {
+ ReflectedImageLabel component = new ReflectedImageLabel(imageURL);
+ component.addMouseListener(this);
+ return add(component);
+ }
+
+ /**
+ * Adds a new image to the carousel
+ *
+ * @param imageURL
+ * The image
+ * @param textLabel
+ * The label
+ * @return The component created
+ */
+ public Component add(String imageURL, String textLabel) {
+ ReflectedImageLabel component = new ReflectedImageLabel(ImageUtilities
+ .loadCompatibleImage(imageURL), textLabel);
+ component.addMouseListener(this);
+ return add(component);
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/JCarouselMenu.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/JCarouselMenu.java
new file mode 100644
index 0000000..9926033
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/JCarouselMenu.java
@@ -0,0 +1,788 @@
+/*
+ * JCarouselMenu.java
+ *
+ * Created on January 13, 2007, 12:42 PM
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.swing.components;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.net.URL;
+import java.security.InvalidParameterException;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.event.*;
+
+import org.pushingpixels.lafwidget.contrib.blogofbug.swing.borders.ImageBorder;
+import org.pushingpixels.lafwidget.contrib.blogofbug.swing.layout.OffsetCaroselLayout;
+
+
+/**
+ * Shows a carousel offset to the left with a menu of actions on the right.
+ * @author nigel
+ */
+public class JCarouselMenu extends GradientPanel implements ListSelectionListener,MouseListener,KeyListener, ChangeListener, MouseWheelListener{
+ /**
+ * The carousel used and drawn on the left.
+ */
+ private JCarosel carousel;
+ /**
+ * A JList with the menu items in
+ */
+ private JList menu;
+ /**
+ * The scroll pane the menu is in
+ */
+ private JScrollPane menuScroll;
+ /**
+ * The model for the action menu
+ */
+ private DefaultListModel menuModel=new DefaultListModel();
+ /**
+ * Linked list of the items in the menu
+ */
+ private LinkedList<MenuItem> menuItems=new LinkedList<MenuItem>();
+ /**
+ * A hashtable connecting the actions to the components in the carousel
+ */
+ private Map<Component, MenuItem> menuMap = new HashMap<Component, MenuItem>();
+ /**
+ * The last item selected
+ */
+ private int lastSelection = -1;
+
+ /**
+ * The button that is drawn when it is possible to scroll up
+ */
+ private UpDownButton upButton = new UpDownButton("Up");
+ /**
+ * The button shown when you can scroll down
+ */
+ private UpDownButton downButton = new UpDownButton("Down");
+
+ /**
+ * Creates a new instance of JCarouselMenu
+ * @param border The border to use to draw items in the menu
+ */
+ public JCarouselMenu(ImageBorder border) {
+ carousel = new JCarosel();
+ carousel.setLayout(new OffsetCaroselLayout(carousel));
+ carousel.setBackground(null);
+ carousel.setOpaque(false);
+ carousel.setContentWidth(256);
+
+ super.setLayout(new GridLayout(1,2));
+ super.add(carousel);
+
+ upButton.setForeground(Color.WHITE);
+ downButton.setForeground(Color.WHITE);
+
+ JPanel menuPanel = new JPanel();
+ menuPanel.setBackground(null);
+ menuPanel.setOpaque(false);
+ menuPanel.setLayout(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ menu = new JList();
+ menuScroll = new JScrollPane(menu, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ menuScroll.getViewport().setOpaque(false);
+ menuScroll.setBorder(null);
+ menuScroll.getViewport().addChangeListener(this);
+ menu.setModel(menuModel);
+ menu.setCellRenderer(new CarouselListCellRenderer(border));
+ menu.setBackground(null);
+ menu.setOpaque(false);
+ menu.addListSelectionListener(this);
+ menuScroll.setOpaque(true);
+ menuScroll.setBackground(Color.BLACK);
+ menuScroll.setBorder(BorderFactory.createEmptyBorder());
+
+ gbc.weightx=0.0;
+ gbc.weighty=0.0;
+ gbc.gridy=0;
+ gbc.fill=GridBagConstraints.HORIZONTAL;
+ menuPanel.add(upButton,gbc);
+ gbc.weighty=1.0;
+ gbc.weightx=1.0;
+ gbc.gridy++;
+ gbc.fill=GridBagConstraints.BOTH;
+ menuPanel.add(menuScroll,gbc);
+ gbc.weighty=0.0;
+ gbc.weightx=0.0;
+ gbc.gridy++;
+ gbc.fill=GridBagConstraints.HORIZONTAL;
+ menuPanel.add(downButton,gbc);
+ menu.addMouseListener(this);
+ menu.addKeyListener(this);
+
+ //Don't want it to listen to itself...
+ carousel.removeMouseWheelListener(carousel);
+ carousel.addMouseWheelListener(this);
+ menu.addMouseWheelListener(this);
+ menuScroll.addMouseWheelListener(this);
+ menuPanel.addMouseWheelListener(this);
+
+ super.add(menuPanel);
+ }
+
+ /**
+ * Creates a new instance
+ */
+ public JCarouselMenu(){
+ this(new ImageBorder(JCarouselMenu.class.getResource("/com/blogofbug/swing/borders/images/menu_highlight.png"),new Insets(10,12,16,12)));
+ }
+
+ /**
+ * Sets the color the up and down buttons are drawn
+ * @param color The desired color
+ */
+ public void setUpDownColor(Color color){
+ upButton.setForeground(color);
+ downButton.setForeground(color);
+ }
+
+ /**
+ * Returns the list part of the carousel menu
+ *
+ * @return The JList object
+ */
+ public JList getList(){
+ return this.menu;
+ }
+
+ /**
+ * Sets the selected item in the menu
+ * @param i The index of the item to select
+ */
+ public void setSelectedIndex(int i){
+ menu.setSelectedIndex(i);
+ }
+
+ /**
+ * Adds a component to the carousel menu that will be brought into view when the user clicks
+ * on the associated item
+ * @param component The component
+ * @param label The text to appear in the menu
+ * @return The created component
+ */
+ public Component add(Component component,String label){
+ carousel.add(label,component);
+ MenuItem item = new MenuItem(component,label,null);
+ menuItems.addLast(item);
+ menuModel.addElement(item);
+ menuMap.put(component, item);
+ component.removeMouseListener(carousel);
+ return component;
+ }
+
+ /**
+ * Removes a component from the menu
+ * @param component The component to remove
+ */
+ @Override
+ public void remove(Component component) {
+ carousel.remove(component);
+ MenuItem menuItem = menuMap.remove(component);
+ if (menuItem != null) {
+ menuItems.remove(menuItem);
+ menuModel.removeElement(menuItem);
+ }
+ }
+
+
+ /**
+ * Adds an image to the menu.
+ * @deprecated Use add(Image, String) instead
+ * @param image The image
+ * @param label The text
+ * @param width Prefered width
+ * @param height Prefered height
+ * @return The created component
+ */
+ public Component add(Image image, String label, int width, int height) {
+ Component comp = carousel.add(image, null);
+ MenuItem item = new MenuItem(comp, label, null);
+ menuItems.addLast(item);
+ menuModel.addElement(item);
+ comp.removeMouseListener(carousel);
+ menuMap.put(comp, item);
+ return comp;
+ }
+
+
+ /**
+ * Adds an image (with a label) and returns the component created to represent them
+ * @param image The image to display
+ * @param label The label to show
+ * @return The component created
+ */
+ public Component add(Image image, String label) {
+ Component comp = carousel.add(image, null);
+ MenuItem item = new MenuItem(comp, label, null);
+ menuItems.addLast(item);
+ menuModel.addElement(item);
+ comp.removeMouseListener(carousel);
+ menuMap.put(comp, item);
+ return comp;
+ }
+
+ /**
+ * Adds an action to the menu
+ * @deprecated Use add(imageURL) instead
+ * @param action The action to add
+ * @param width The width
+ * @param height The height
+ * @return The created component
+ */
+ public Component add(Action action, int width, int height){
+ URL url = (URL) action.getValue(AbstractCarouselMenuAction.ACTION_IMAGE_URL);
+ if (url==null){
+ throw new InvalidParameterException("Supplied action does not have Image URL key (AbstractCarouselMenuAction.ACTION_IMAGE_URL)"
+ );
+ }
+ Component comp = carousel.add(url.toString());
+ MenuItem item = new MenuItem(comp,(String) action.getValue(Action.SHORT_DESCRIPTION),action);
+ menuItems.addLast(item);
+ menuMap.put(comp, item);
+ menuModel.addElement(item);
+ comp.removeMouseListener(carousel);
+ return comp;
+ }
+
+ /**
+ * Adds an action to the list, creating a menu item and a carousel entry
+ * @param action The action to add
+ * @return The resultant component
+ */
+ public Component add(Action action){
+ URL url = (URL) action.getValue(AbstractCarouselMenuAction.ACTION_IMAGE_URL);
+ if (url==null){
+ throw new InvalidParameterException("Supplied action does not have Image URL key (AbstractCarouselMenuAction.ACTION_IMAGE_URL)"
+ );
+ }
+ Component comp = carousel.add(url.toString());
+ MenuItem item = new MenuItem(comp,(String) action.getValue(Action.SHORT_DESCRIPTION),action);
+ menuItems.addLast(item);
+ menuMap.put(comp, item);
+ menuModel.addElement(item);
+ comp.removeMouseListener(carousel);
+ return comp;
+ }
+
+ /**
+ * Adds an image (through a URL) to the menu
+ * @deprecated Use add(imageURL, label) instead
+ * @param imageURL URL of the image
+ * @param label Text message
+ * @param width width
+ * @param height height
+ * @return The created component
+ */
+ public Component add(String imageURL, String label, int width, int height){
+ Component comp = carousel.add(imageURL);
+ MenuItem item = new MenuItem(comp,label,null);
+ menuMap.put(comp, item);
+ menuItems.addLast(item);
+ menuModel.addElement(item);
+ comp.removeMouseListener(carousel);
+ return comp;
+ }
+
+ /**
+ * Adds an image based on the imageURL and a text label, returning the component that is created as a result
+ * @param imageURL The URL of the image
+ * @param label Text label to be shown in the menu
+ * @return The created component
+ */
+ public Component add(String imageURL, String label){
+ Component comp = carousel.add(imageURL);
+ MenuItem item = new MenuItem(comp,label,null);
+ menuMap.put(comp, item);
+ menuItems.addLast(item);
+ menuModel.addElement(item);
+ comp.removeMouseListener(carousel);
+ return comp;
+ }
+
+ /**
+ * Return the preferred size of the component
+ * @return The prefered dimensions of the component
+ */
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size = super.getPreferredSize();
+ size.width /= 2;
+ return size;
+ }
+
+ /**
+ * Detect when the list selection changes, and respond by updating the state
+ * of the two "arrow" buttons. Contributed by Sebastian Charpentier.
+ * @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent)
+ * @param e The state changed event
+ */
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ // Check if the scroll bar is at the top or at the bottom
+ // Note: It's a trick, I don't know if this is the best/correct way to handle that
+ // We show the "go up" arrow if were not at the beginning
+ JViewport viewport = menuScroll.getViewport();
+ int yPos = (int)viewport.getViewPosition().getY();
+ upButton.setDoPaint(yPos > 0);
+ // We show the "go down" arrow if were not at the end (having the view as down as we could)
+ downButton.setDoPaint((yPos + viewport.getExtentSize().getHeight()) != menu.getHeight());
+ }
+
+
+ /**
+ * Detect when the list selection changes, and respond by rotating the carousel to show
+ * that item
+ * @param listSelectionEvent The list selection change event
+ */
+ @Override
+ public void valueChanged(ListSelectionEvent listSelectionEvent) {
+ MenuItem item = (MenuItem) menu.getSelectedValue();
+ if (item==null){
+ return;
+ }
+
+ carousel.bringToFront(item.carouselComponent);
+ }
+
+ /**
+ * Launch the action associated with the currently selected list item
+ *
+ */
+ protected void processAction(){
+ MenuItem item = (MenuItem) menu.getSelectedValue();
+ if (item==null){
+ return;
+ }
+ if (item.action==null){
+ return;
+ }
+ item.action.actionPerformed(new ActionEvent(this,ActionEvent.ACTION_PERFORMED,item.label));
+ }
+
+ /**
+ * Look to see if an item in the list is double clicked, and launch the action if it is
+ * @param mouseEvent The mouse event
+ */
+ @Override
+ public void mouseClicked(MouseEvent mouseEvent) {
+ if (mouseEvent.getClickCount()==2){
+ processAction();
+ }
+ }
+
+ /**
+ * Don't Care *
+ * @param mouseEvent The mouse event
+ */
+ @Override
+ public void mousePressed(MouseEvent mouseEvent) { }
+
+ /**
+ * Don't Care *
+ * @param mouseEvent The mouse event
+ */
+ @Override
+ public void mouseReleased(MouseEvent mouseEvent) {}
+
+ /**
+ * Don't Care *
+ * @param mouseEvent The mouse event
+ */
+ @Override
+ public void mouseEntered(MouseEvent mouseEvent) {}
+
+ /**
+ * Don't Care *
+ * @param mouseEvent The mouse event
+ */
+ @Override
+ public void mouseExited(MouseEvent mouseEvent) {}
+
+ /**
+ * Don't Care *
+ * @param keyEvent The key event
+ */
+ @Override
+ public void keyTyped(KeyEvent keyEvent) { }
+
+ /**
+ * Listen for key events, when we see one that looks like it should wrap, set up the lastSelection variable to
+ * trigger a change on release of the key
+ * @param keyEvent The key event
+ */
+ @Override
+ public void keyPressed(KeyEvent keyEvent) {
+ switch (keyEvent.getKeyCode()){
+ case KeyEvent.VK_ENTER:
+ processAction();
+ break;
+ case KeyEvent.VK_UP:
+ if (menu.getSelectedIndex()==0){
+ this.lastSelection = menuModel.size()-1;
+ } else {
+ this.lastSelection = -1;
+ }
+ break;
+ case KeyEvent.VK_DOWN:
+ if (menu.getSelectedIndex()==menuModel.size()-1){
+ this.lastSelection = 0;
+ } else {
+ this.lastSelection = -1;
+ }
+ break;
+
+ }
+ }
+
+ /**
+ * Sets the image border used to draw around the items in the menu
+ * @param imageBorder The desired image border
+ */
+ public void setCellImageBorder(ImageBorder imageBorder){
+ CarouselListCellRenderer renderer = (CarouselListCellRenderer) menu.getCellRenderer();
+
+ renderer.setImageBorder(imageBorder);
+ }
+
+ /**
+ * Specifies the list cell renderer used to draw the items in the menu
+ * @param cellRenderer The list cell renderer
+ */
+ public void setCellRenderer(ListCellRenderer cellRenderer){
+ menu.setCellRenderer(cellRenderer);
+ }
+
+ /**
+ * If the wrap-around has detected the need to wrap, sets the selection to the value
+ * calculated when the key was first pressed.
+ * @param keyEvent The key event
+ */
+ @Override
+ public void keyReleased(KeyEvent keyEvent) {
+ if (lastSelection!=-1){
+ menu.setSelectedIndex(lastSelection);
+ menu.ensureIndexIsVisible(lastSelection);
+ lastSelection=-1;
+ }
+ }
+
+ /**
+ * Moves the selected menu up or down when the mouse wheel scrolls
+ * @param mouseWheelEvent The mouse wheel event
+ */
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent mouseWheelEvent) {
+ if (mouseWheelEvent.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
+ int amount = mouseWheelEvent.getWheelRotation();
+
+ int lastSelection;
+ if (amount < 0) {
+ if (menu.getSelectedIndex()==0){
+ lastSelection = menuModel.size()-1;
+ } else {
+ lastSelection = menu.getSelectedIndex()-1;
+ }
+ } else {
+ if (menu.getSelectedIndex()==menuModel.size()-1){
+ lastSelection = 0;
+ } else {
+ lastSelection = menu.getSelectedIndex()+1;
+ }
+ }
+
+ final int indexToSelect = lastSelection;
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ menu.setSelectedIndex(indexToSelect);
+ menu.ensureIndexIsVisible(indexToSelect);
+ menu.repaint();
+ }
+ });
+ }
+ }
+
+ /**
+ * Sets icons to use to show the up and down buttons
+ * @param upIcon The icon to use for up
+ * @param downIcon The icon to use for down
+ */
+ public void setUpDownIcons(Icon upIcon, Icon downIcon) {
+ upButton.setIcon(upIcon);
+ downButton.setIcon(downIcon);
+ }
+
+ /**
+ * Allows the background color to the menu (left side) to be set
+ * @param color Sets the background color to the menu
+ */
+ public void setMenuScrollColor(Color color) {
+ this.menuScroll.setBackground(color);
+ }
+
+ /**
+ * ListCellRenderer for the Carousel uses an image border to draw a nice border around the menu item when it is selected
+ *
+ */
+ protected class CarouselListCellRenderer extends JLabel implements ListCellRenderer{
+ ImageBorder imageBorder;
+ /**
+ * Creates a new list cell renderer for the menu with the specified image border
+ * @param border The border to use
+ */
+ public CarouselListCellRenderer(ImageBorder border){
+ imageBorder = border;
+ setBorder(imageBorder);
+ }
+
+ /**
+ * Allows the setting of the image border
+ * @param border The border to use
+ */
+ public void setImageBorder(ImageBorder border){
+ imageBorder = border;
+ setBorder(imageBorder);
+ }
+
+ /**
+ * Sets up the component for stamping
+ * @param jList The list
+ * @param object The object being drawn
+ * @param i The index of the object
+ * @param isSelected If the object is selected
+ * @param cellHasFocus Does the cell have the focus
+ * @return The object to use to stamp the list item
+ */
+ @Override
+ public Component getListCellRendererComponent(JList jList, Object object, int i, boolean isSelected, boolean cellHasFocus) {
+ MenuItem item = (MenuItem) object;
+ setText(item.label);
+ if (!isSelected){
+ setBackground(null);
+ imageBorder.setPaintBorder(false);
+ setOpaque(false);
+ } else {
+ imageBorder.setPaintBorder(false);
+ setOpaque(false);
+ }
+ setForeground(Color.WHITE);
+
+ return this;
+ }
+
+ /**
+ * Our image border can paint a center as well as a surround. Call paint center if we want it to do this.
+ * @param g The graphcis context
+ */
+ @Override
+ public void paintComponent(Graphics g){
+ imageBorder.paintCenter((Graphics2D)g,this);
+ super.paintComponent(g);
+ }
+
+ /**
+ * I want it to be wider than it needs to be
+ * @return The desired width of the cell
+ */
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension d = super.getPreferredSize();
+ d.width+=20;
+ return d;
+ }
+ }
+
+ /**
+ * A menu item inside the carousel
+ */
+ public class MenuItem{
+ /**
+ * The component inside the caroulse
+ */
+ protected Component carouselComponent;
+ /**
+ * The text label
+ */
+ protected String label;
+ /**
+ * An associated action
+ */
+ protected Action action;
+
+ /**
+ * Creates a new instance of the menu item
+ * @param component The component to use
+ * @param label The text label
+ * @param action The associated action
+ */
+ public MenuItem(Component component, String label,Action action){
+ this.label = label;
+ carouselComponent = component;
+ this.action = action;
+ }
+
+ /**
+ * Retreives the label associated with the entry
+ * @return The label
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * Gets the action associated with the entry
+ * @return The action associated with the entry
+ */
+ public Action getAction() {
+ return action;
+ }
+
+ /**
+ * Gets the component in the carousel associated with the entry
+ * @return The component
+ */
+ public Component getCarouselComponent() {
+ return carouselComponent;
+ }
+
+ }
+
+ /**
+ * This class represents the up and down buttons that allow the scrolling through the menu when it is too big to fit in the avaiable space
+ */
+ private class UpDownButton extends JLabel implements MouseListener{
+ /**
+ * True if they should be painted
+ */
+ private boolean doPaint = true;
+
+ /**
+ * Creates the up down button
+ * @param text Test, ignored
+ */
+ public UpDownButton(String text){
+ super(text);
+ addMouseListener(this);
+ setBorder(BorderFactory.createEmptyBorder(4,4,4,4));
+ }
+
+ /**
+ * Controls if the button should paint itself or not
+ * @param shouldPaint True if it should, false if it shouldn't
+ */
+ public void setDoPaint(boolean shouldPaint){
+ doPaint = shouldPaint;
+ repaint();
+ }
+
+ /**
+ * Paint the component
+ * @param g The graphics context
+ */
+ @Override
+ public void paintComponent(Graphics g){
+ if (doPaint) {
+ Icon icon = this.getIcon();
+ if (icon != null) {
+ int centerX = getWidth()
+ - (getInsets().left + getInsets().right);
+ centerX = getInsets().left + centerX / 2;
+ int centerY = getHeight()
+ - (getInsets().top + getInsets().bottom);
+ centerY = getInsets().top + centerY / 2;
+ icon.paintIcon(this, g, centerX - icon.getIconWidth() / 2,
+ centerY - icon.getIconHeight() / 2);
+ } else {
+ Graphics2D g2 = (Graphics2D) g;
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setColor(getForeground());
+ int centerX = getWidth()-(getInsets().left+getInsets().right);
+ centerX = getInsets().left + centerX/2;
+ int height = getHeight()-(getInsets().top+getInsets().bottom);
+ int width = height*2;
+ if ("Up".equals(getText())){
+ g.fillPolygon(new int[]{centerX-width,centerX,centerX+width},new int[]{height,getInsets().top,height},3);
+ } else {
+ g.fillPolygon(new int[]{centerX-width,centerX,centerX+width},new int[]{getInsets().top,height,getInsets().top},3);
+ }
+
+ }
+ }
+ }
+
+
+ /**
+ * Listens for a mouse click and scroll up or down in the menu when it gets one
+ * @param mouseEvent The mouse event
+ */
+ @Override
+ public void mouseClicked(MouseEvent mouseEvent) {
+ if (!doPaint){
+ return;
+ }
+ if (mouseEvent.getClickCount()==1){
+ int height = menu.getCellBounds(menu.getSelectedIndex(),menu.getSelectedIndex()).height;
+ if (getText().equals("Up")) {
+ setSelectedIndex(menu.getSelectedIndex()-1);
+ Point pos = menuScroll.getViewport().getViewPosition();
+ pos.y-=height;
+ menuScroll.getViewport().setViewPosition(pos);
+ } else if (getText().equals("Down")) {
+ setSelectedIndex(menu.getSelectedIndex()+1);
+ Point pos = menuScroll.getViewport().getViewPosition();
+ pos.y+=height;
+ menuScroll.getViewport().setViewPosition(pos);
+ }
+ }
+ }
+
+ /**
+ * Don't care
+ * @param mouseEvent The mouse event
+ */
+ @Override
+ public void mousePressed(MouseEvent mouseEvent) {
+ }
+
+ /**
+ * Don't care
+ * @param mouseEvent The mouse event
+ */
+ @Override
+ public void mouseReleased(MouseEvent mouseEvent) {
+ }
+
+ /**
+ * Don't care
+ * @param mouseEvent The mouse event
+ */
+ @Override
+ public void mouseEntered(MouseEvent mouseEvent) {
+ }
+
+ /**
+ * Don't care
+ * @param mouseEvent The mouse event
+ */
+ @Override
+ public void mouseExited(MouseEvent mouseEvent) {
+ }
+
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/ReflectedImageLabel.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/ReflectedImageLabel.java
new file mode 100644
index 0000000..4f5a7ed
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/ReflectedImageLabel.java
@@ -0,0 +1,366 @@
+/*
+ * ReflectedImageLabel.java
+ *
+ * Created on November 22, 2006, 11:34 PM
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.swing.components;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.imageio.ImageIO;
+import javax.swing.JLabel;
+
+import org.pushingpixels.lafwidget.contrib.blogofbug.utility.ImageUtilities;
+
+
+/**
+ * A RichComponent which takes the supplied image, adds on 50% to the height of the image
+ * and draws a graduated alpha-blended reflection below the top aligned original image. The
+ * text (set by setRichText()) is drawn dynamically over the reflection, below the original image.
+ * @author nigel
+ */
+public class ReflectedImageLabel extends JLabel implements RichComponent{
+ /**
+ * The richtext associated with this component
+ */
+ private String text = "";
+ /**
+ * The image with reflection
+ */
+ private BufferedImage bufferedImage = null;
+
+ /**
+ * A font used for reference purposes when evaluating the size of the rendered component
+ */
+ private static final Font reference = new Font("Arial",Font.BOLD,14);
+ /**
+ * The desired alpha composite
+ */
+ private AlphaComposite alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER,1.0f);
+
+ /**
+ * Creates a new instance from the supplied image object
+ * @param image The Image object
+ * @param width The prefered width of the image when rendered by a rich container
+ * @param height The prefered heightof the image when rendered by a rich container
+ * @deprecated This function will be removed, use setNeutralWidth() on JCarousel instead.
+ */
+ public ReflectedImageLabel(Image image, int width, int height) {
+ setRichImage(image);
+ }
+
+ /**
+ * Creates a new instance of a reflected label for the supplied image, also setting the text
+ * to be associated with the image.
+ * @param image The image
+ * @param text The text label
+ * @param width The prefered width of the image when rendered by a rich container
+ * @param height The prefered height of the image when rendered by a rich container
+ * @deprecated This function will be removed, use setNeutralWidth() on JCarousel instead.
+ */
+ public ReflectedImageLabel(Image image, String text, int width, int height) {
+ setRichImage(image);
+ setRichText(text);
+ }
+
+ /**
+ * Creates a new instance of a reflected label using the supplied image and text
+ * @param image The image to be used
+ * @param text The text to be displayed
+ */
+ public ReflectedImageLabel(Image image, String text) {
+ this(image, text, image.getWidth(null), image.getHeight(null));
+ }
+
+ /**
+ * See constructor for image label, this version of the constructor takes an image URL instead
+ * of the image object (the URL can be in string format).
+ * @param imageURL A URL (in string form) of the image.
+ */
+ public ReflectedImageLabel(String imageURL){
+ try {
+ setRichImage(new URL(imageURL));
+ } catch (MalformedURLException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Creates a new instance, setting the width and the height that may be used by the RichContainer
+ * @param imageURL The URL of the image (String form)
+ * @param width The prefered width of the image when rendered by a rich container
+ * @param height The prefered height of the image when rendered by a rich container
+ * @deprecated This function will be removed, use setNeutralWidth() on JCarousel instead.
+ */
+ public ReflectedImageLabel(String imageURL, int width, int height){
+ try {
+ setRichImage(new URL(imageURL));
+ } catch (MalformedURLException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Creates a new instance, using the image specified in the URL string, the prefered dimensions and sets the Rich text as well
+ * @param imageURL The URL of the image in text form
+ * @param text The RichText to be displayed
+ * @param width The prefered width of the image when rendered by a rich container
+ * @param height The prefered height of the image when rendered by a rich container
+ * @deprecated This function will be removed, use setNeutralWidth() on JCarousel instead.
+ */
+ public ReflectedImageLabel(String imageURL, String text, int width, int height){
+ this(imageURL,width,height);
+ this.text=text;
+ }
+
+ /**
+ * Depricated.
+ * @deprecated Use setRichText() instead
+ * @param text The rich text
+ */
+ public void setLabel(String text){
+ this.text = text;
+ }
+
+
+
+ /**
+ * Will accept a string URL for loading the image before calling the normal setupImage function after loading it.
+ * @param imageURL The URL of the image (in a string)
+ */
+ private void setupImage(String imageURL) {
+ Image image = null;
+ try {
+ image = ImageIO.read(new URL(imageURL));
+ } catch (MalformedURLException ex) {
+ ex.printStackTrace();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ this.setupImage(image);
+ }
+
+ /**
+ * Adds a reflection to the supplied image
+ * @param image The image object to use to pre-render the reflection
+ */
+ protected void setupImage(Image image){
+ if (image == null) {
+ return;
+ }
+
+ //Create a buffered image which is the right (translucent) format for the current graphics device, this
+ //should ensure the fastest possible performance. Adding on some extra height to make room for the reflection
+ BufferedImage originalImage = ImageUtilities.createCompatibleImage(image.getWidth(null),(int)((double)image.getHeight(null)*1.5));
+
+ //Blit the loaded image onto the optimized surface by creating a graphics context for the new image
+ Graphics2D g = originalImage.createGraphics();
+ //Draw the original image
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+ BufferedImage reflection = ImageUtilities.createCompatibleImage(image.getWidth(null),image.getHeight(null));
+ g = reflection.createGraphics();
+ int drawHeight = image.getHeight(null);
+ AffineTransform tranform = AffineTransform.getScaleInstance(1.0, -1.0);
+ tranform.translate(0, -drawHeight);
+
+ // draw the flipped image
+ AffineTransform oldTransform = g.getTransform();
+ g.setTransform(tranform);
+ g.drawImage(image, 0,0,image.getWidth(null),drawHeight,0,0,image.getWidth(null),image.getHeight(null),null);
+ g.setTransform(oldTransform);
+
+ GradientPaint painter = new GradientPaint(0.0f, 0.0f,
+ new Color(0.0f, 0.0f, 0.0f, 0.5f),
+ 0.0f, drawHeight / 2.0f,
+ new Color(0.0f, 0.0f, 0.0f, 1.0f));
+
+ // this use : Ar = Ad*(1-As) and Cr = Cd*(1-As)
+ g.setComposite(AlphaComposite.DstOut);
+ g.setPaint(painter);
+ // this will make our image transluent ...
+ g.fill(new Rectangle2D.Double(0, 0, image.getWidth(null), drawHeight));
+ g.dispose();
+ //Now blit back the reflection...
+ g = originalImage.createGraphics();
+ g.drawImage(reflection,0,drawHeight,null);
+ g.dispose();
+
+ //First hack, just sets the bufferedIMage to the one loaded, don't cache any rendering
+ bufferedImage = originalImage;
+ setPreferredSize(new Dimension(image.getWidth(null), image.getHeight(null)));
+ }
+
+ /**
+ * Sets the transparency of the component
+ * @param alphaLevel The alpha level of the object
+ * @see RichComponent
+ */
+ @Override
+ public void setAlpha(float alphaLevel){
+ alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER,alphaLevel);
+ }
+
+ /**
+ * Deprecated
+ * @param image An image object to use (reflection will be added)
+ * @see RichComponent
+ * @deprecated Please use setRichImage() instead from the RichComponent interface
+ */
+ public void setImage(Image image) {
+ this.setupImage(image);
+ }
+
+
+ /**
+ * Overrides the default getPreferedSize() which has been controlled by the created image and adds 50% onto the height to allow for the reflection.
+ * @return The prefered dimensions of the component
+ */
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension d = super.getPreferredSize();
+ d.height = (int) ((double) d.height * 1.5);
+
+ return d;
+ }
+
+ /**
+ * Paints the component
+ *
+ * @param graphics The graphics context
+ */
+ @Override
+ public void paintComponent(Graphics graphics) {
+ // Don't paint if I'm off screen
+ if ((getX() + getWidth() < 0) && (getY() + getHeight() < 0)) {
+ return;
+ }
+
+ Graphics2D g = (Graphics2D) graphics;
+ Image image = bufferedImage;
+
+ int drawHeight = (int) ((double) getHeight() / 1.5);
+
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+
+ // SiAlphaCompositemple scale only
+ Composite oldAc = g.getComposite();
+ g.setComposite(alphaComposite);
+ g.drawImage(image, 0, 0, getWidth(), getHeight(), 0, 0, image
+ .getWidth(null), image.getHeight(null), null);
+ // Draw text if there is any...
+ if ((text != null) && (text.length() > 0)) {
+ Graphics2D g2d = (Graphics2D) graphics;
+ Rectangle2D bounds = reference.getStringBounds(text, g2d
+ .getFontRenderContext());
+ double scaleFactor = (double) getWidth() / image.getWidth(null);
+ double scaleFactor2 = (double) getWidth() / bounds.getWidth();
+ int fontSize = (int) Math.min(25.0 * scaleFactor,
+ 14.0 * scaleFactor2);
+ Font font = new Font("Arial", Font.BOLD, fontSize);
+ g2d.setFont(font);
+ int dx = (getWidth() - (int) font.getStringBounds(text,
+ g2d.getFontRenderContext()).getWidth()) / 2;
+ int dy = drawHeight + 2 * (int) (bounds.getHeight() * scaleFactor);
+ Color background = this.getBackground();
+ int red = background.getRed();
+ int green = background.getRed();
+ int blue = background.getRed();
+ graphics.setColor(new Color(red,green,blue,96));
+ FontMetrics fm = g2d.getFontMetrics();
+ Rectangle2D rect = fm.getStringBounds(text,graphics);
+
+ graphics.fillRoundRect(dx-(int)rect.getHeight()/2, dy - (int) g2d.getFontMetrics().getAscent(),
+ (int)rect.getWidth()+((int)rect.getHeight()), fm.getAscent() + fm.getDescent(),(int)rect.getHeight(),(int)rect.getHeight());
+ graphics.setColor(this.getForeground());
+ graphics.drawString(text, dx, dy);
+ }
+ g.setComposite(oldAc);
+
+ }
+
+
+ /**
+ * Assigns an image to the component, the width and height taken from the supplied image
+ *
+ * @param image The URL of the image
+ */
+ @Override
+ public void setRichImage(URL image) {
+ setRichImage(ImageUtilities.loadCompatibleImage(image.toString()));
+ }
+
+ /**
+ * See interface definition
+ * @param image See interface definition
+ * @see RichComponent
+ */
+ @Override
+ public void setRichImage(File image) {
+ try {
+ setRichImage(image.toURL());
+ } catch (MalformedURLException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * See interface definition
+ * @param image See interface definition
+ * @see RichComponent
+ */
+ @Override
+ public void setRichImage(Image image) {
+ setImage(image);
+ }
+
+ /**
+ * See interface definition
+ * @see RichComponent
+ */
+ @Override
+ public void prePaintImage() {
+ //I do all my pre-rendering earlier...
+ }
+
+ /**
+ * See interface definition
+ * @param text See interface definition
+ * @see RichComponent
+ */
+ @Override
+ public void setRichText(String text) {
+ setLabel(text);
+ }
+
+ /**
+ * See interface definition
+ * @return See interface definition
+ * @see RichComponent
+ */
+ @Override
+ public String getRichText() {
+ return this.text;
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/RichComponent.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/RichComponent.java
new file mode 100644
index 0000000..5912ab2
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/components/RichComponent.java
@@ -0,0 +1,81 @@
+/*
+ * RichComponent.java
+ *
+ * Created on March 16, 2007, 3:58 PM
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.swing.components;
+
+import java.awt.Image;
+import java.io.File;
+import java.net.URL;
+
+/**
+ * A component that can be part of the a rich user interface
+ * @author nigel
+ */
+public interface RichComponent {
+
+ /**
+ * Assigns an image to the component, the width and height taken from the supplied image
+ *
+ * @param image The URL of the image
+ */
+ public void setRichImage(URL image);
+
+ /**
+ * Assigns an image to the component, the width and height taken from the supplied image
+ *
+ * @param image A file referencing the image
+ */
+ public void setRichImage(File image);
+
+ /**
+ * Assisgns an image to the component, the width and height taken from the supplied image
+ *
+ * @param image An image object containing the image
+ */
+ public void setRichImage(Image image);
+
+ /**
+ * Allows a RichContainer to request that the component pre-renders anything
+ * that might provide peak performance. It is expected that most implementations will
+ * do this automatically after the image has been specified
+ */
+ public void prePaintImage();
+
+ /**
+ * Specifies text that should be displayed by the rich component when
+ * getRichText() is called.
+ * @param text The text to be displayed. This may be different to that of the component
+ * being enriched.
+ */
+ public void setRichText(String text);
+
+ /**
+ * Gets the text to be displayed by the RichContainer for the component. May
+ * be extracted from elsewhere
+ *
+ * @return The text to be displayed
+ */
+ public String getRichText();
+
+ /**
+ * Assisgns a uniform alpha to the component
+ *
+ * @param alpha A value from 0.0 to 1.0 where 1.0 is fully visible, and 0.0
+ * is completely invisible.
+ */
+ public void setAlpha(float alpha);
+
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/layout/CaroselLayout.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/layout/CaroselLayout.java
new file mode 100644
index 0000000..350c0b5
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/layout/CaroselLayout.java
@@ -0,0 +1,685 @@
+/*
+ * CaroselLayout.java
+ *
+ * Created on November 21, 2006, 11:48 AM
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.swing.layout;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.*;
+
+import javax.swing.Timer;
+
+import org.pushingpixels.lafwidget.contrib.blogofbug.swing.components.RichComponent;
+
+
+/**
+ * Layout engine for JCarousel components (although would work for any container).
+ * It does have a closer than usual relationship with the container, sometimes causing the container to repaint.
+ * @author bug
+ */
+public class CaroselLayout implements LayoutManager,ActionListener{
+ /**
+ * Number of items in the carousel (that are visible)
+ */
+ protected int numberOfItems = 0;
+
+ /**
+ * List of components being laid out
+ */
+ protected LinkedList<Component> components = new LinkedList<Component>();
+
+ /**
+ * List of additional information held on components in the carousel
+ */
+ protected Hashtable additionalData = new Hashtable();
+
+ /**
+ * The current degree of rotation of the carousel
+ */
+ protected double rotationalOffset = 0.0;
+
+ /**
+ * The desired rotational offset, which will be moved to by a timer animating the carousel
+ */
+ protected double targetOffset = 0.0;
+
+ /**
+ * Time for driving animations
+ */
+ private Timer animationTimer = null;
+
+ /**
+ * The container the layout is... laying out
+ */
+ private Container container = null;
+
+ /**
+ * Should items furtehr away from the observer be faded out?
+ */
+ private boolean depthBasedAlpha = true;
+
+ /**
+ * The width of a component when the scale is 1.0
+ */
+ private int neutralContentWidth = 64;
+
+ /**
+ * Creates a new instance of the layout engine, tied to the specified container.
+ * @param forContainer The container the layout will layout
+ */
+ public CaroselLayout(Container forContainer){
+ animationTimer = new Timer(0,this);
+ container = forContainer;
+ }
+
+ /**
+ * Specify the neutral content width of any laid out component.
+ * @param neutralContentWidth The neutral width of components
+ */
+ public void setNeutralContentWidth(int neutralContentWidth) {
+ this.neutralContentWidth = neutralContentWidth;
+ }
+
+
+
+ /**
+ * Moves a layout component at a particular location in the
+ * carousel
+ *
+ * @param i The location at which to insert
+ * @param comp The component to insert
+ *
+ */
+ public void moveComponentTo(int i, Component comp){
+ components.remove(comp);
+ components.add(i,comp);
+ recalculateCarosel();
+ }
+
+ /**
+ * Name is ignored
+ * @param name The name of the component, ignored.
+ * @param comp The component being added
+ */
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ components.addLast(comp);
+ recalculateCarosel();
+ }
+
+ /**
+ * Remove the component
+ * @param comp The component being removed
+ */
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ components.remove(comp);
+ recalculateCarosel();
+ }
+
+ /**
+ * Gets the additional data stored by the layout manager for a given component
+ *
+ * @param comp The component you wish retreive the data for
+ * @return A position, which is added if it does not already exist. Never null unless
+ * you run out of memory!
+ */
+ protected CaroselPosition getPosition(Component comp){
+ CaroselPosition cpos = (CaroselPosition) additionalData.get(comp);
+
+ if (cpos==null){
+ cpos = new CaroselPosition(comp);
+ additionalData.put(comp,cpos);
+ }
+
+ return cpos;
+ }
+
+ /**
+ * Determines how many of the items being laid out are currently visible.
+ * @return How many of the items in the carousel are currently visible.
+ */
+ protected int recalculateVisibleItems(){
+ int visibleItems=0;
+ try{
+ for (Component comp : components){
+ if (comp.isVisible()){
+ visibleItems++;
+ }
+ }
+ } catch (ConcurrentModificationException ex){
+ return recalculateVisibleItems();
+ }
+ return visibleItems;
+ }
+
+ /**
+ * Updates all of the positions of the carousel. Does not do a repaint, just does the math ready for the next one.
+ */
+ protected void recalculateCarosel(){
+ //Need to count visible, not just how many in the list
+ //Again dealing with out-of-EDT modification
+ numberOfItems = recalculateVisibleItems();
+
+ //Trap and re-calc on concurrent modification (might as well be up-to-date)
+
+ try{
+ boolean animate=false;
+ double itemCount = 0;
+ for (Component comp : components){
+ CaroselPosition position = getPosition(comp);
+ if (comp.isVisible()){
+ double localAngle = itemCount * (Math.PI * 2.0 / (double) numberOfItems);
+ position.setAngle(localAngle);
+ }
+ if (position.isAnimating()){
+ animate=true;
+ }
+ itemCount+=1.0;
+ }
+
+ //If we do need to animate, get it started
+ if (animate){
+ animationTimer.start();
+ }
+ } catch (ConcurrentModificationException ex){
+ recalculateCarosel();
+ return;
+ }
+
+ }
+
+ /**
+ * Cheats and bases it's size on the prefered sizes of each component
+ * @param parent The container interested in the layout size
+ * @return The minimum size of the layout. See above.
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return preferredLayoutSize(parent);
+ }
+
+ /**
+ * Determine the widest and tallest dimensions, then return the height as 1.5 * the highest, and 3 * the widest
+ * @param parent The container for the layout
+ * @return The prefered size of the layout
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ Dimension dim = new Dimension(0, 0);
+ // get widest preferred width for left && right
+ // get highest preferred height for left && right
+ // add preferred width of middle
+ int widestWidth = 0;
+ int highestHeight = 0;
+
+ Iterator i = components.iterator();
+ while (i.hasNext()){
+ Component comp = (Component) i.next();
+
+ if (comp.isVisible()){
+ widestWidth = Math.max(widestWidth, comp.getPreferredSize().width);
+ highestHeight = Math.max(highestHeight, comp.getPreferredSize().height);
+ }
+ }
+
+ dim.width = widestWidth * 3;
+ dim.height = highestHeight * 2;
+
+ Insets insets = parent.getInsets();
+ dim.width += insets.left + insets.right;
+ dim.height += insets.top + insets.bottom;
+
+ return dim;
+ }
+
+ /**
+ * Determines the center of the carousel
+ * @param insets The insets of the container
+ * @param width The width of the container
+ *
+ * @param height The height of the container
+ * @param widest The widest component
+ * @return A point at the center of the carousel
+ */
+ protected Point calculateCenter(Insets insets, int width, int height, int widest){
+ return new Point((insets.left+widest/2) + width/2, insets.top + height/2);
+ }
+
+ /**
+ * Controls if items should fade as they move to the back of the carousel
+ * @param depthBasedAlpha True if they should fade, false if they shouldn't
+ */
+ public void setDepthBasedAlpha(boolean depthBasedAlpha) {
+ this.depthBasedAlpha = depthBasedAlpha;
+ }
+
+ /**
+ * Can be over-ridden to restrict the range of angles where the child component
+ * is shown
+ * @return false if the component should not be shown
+ * @param comp Controls if components are hidden or not, in the case of this layout it always returns false
+ * @param angle The angle of the component under consideration
+ * @param s The scale of the component under consideration
+ */
+ protected boolean shouldHide(Component comp, double angle, double s){
+ if (depthBasedAlpha){
+ if (comp instanceof RichComponent){
+ s = Math.min(1.0f,Math.max(0.0f,s/1.2f));
+ ((RichComponent) comp).setAlpha((float)s);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determines the correct size of the carousel for the container
+ * @param target The target container
+ * @param insets Insets into the target container
+ * @param width Width of the target container
+ * @param height Height of the target container
+ * @param widestComponent The widest component in the container
+ * @return The
+ */
+ protected Dimension getCarouselRadius(Container target, Insets insets, int width, int height, int widestComponent){
+ return null;
+ }
+
+ /**
+ * Determines the scale to be applied to the component. The default calculation
+ * divides the y co-ordinate by the y-cordinate of the centre. Other implimentations
+ * may use some of the other parameters
+ *
+ * @param angle The angle of the component
+ * @param x The x-position of the component
+ * @param y The y-position of the component
+ * @param carouselX The x centre of the carousel
+ * @param carouselY The y centre of the carousel
+ * @return A double which will be used to scale x and y co-ordinates
+ */
+ protected double getScale(double angle, double x, double y, double carouselX, double carouselY){
+ return (y / carouselY);
+ }
+
+ /**
+ * Lays out all of the components on the carosel. Using the preferred width and height to base
+ * scaling on
+ * @param target The container currently being laid out
+ */
+ @Override
+ public void layoutContainer(Container target) {
+ //Let's make a local copy of components to avoid concurrent modification
+ //which could happen if someone adds something to the layout outside
+ //of the EDT. This is faster than do any synchronization or brute force
+ //exception catching
+ LinkedList<Component> components = (LinkedList) this.components.clone();
+ int numberOfItems = this.numberOfItems;
+
+ recalculateCarosel();
+ // these variables hold the position where we can draw components
+ // taking into account insets
+ Insets insets = target.getInsets();
+ int width = target.getSize().width - (insets.left + insets.right);
+ int height = target.getSize().height - (insets.top + insets.bottom);
+
+ //No longer calculate the width based on prefered sizes, we're going to control
+ //it by the component not the content'
+ int widestWidth = neutralContentWidth;
+ int highestHeight = 0;
+
+ width -= widestWidth;
+
+
+ int radiusX = width /2;
+ int radiusY = radiusX/3;
+ Dimension radius = getCarouselRadius(target,insets,width,height,widestWidth);
+ if (radius!=null){
+ radiusX = radius.width;
+ radiusY = radius.height;
+ }
+
+ Point center = calculateCenter(insets,width,height,widestWidth);
+ int centerX = center.x;
+ int centerY = center.y;
+
+ //Go through each visible component and set the scale and z-order, and eventually the bounds
+ //Need to protected against other things adding components at the same time
+ Iterator i = components.iterator();
+ int p = 0;
+ CaroselPosition z_order[] = new CaroselPosition[numberOfItems];
+ while (i.hasNext()){
+ Component comp = (Component) i.next();
+ CaroselPosition position = getPosition(comp);
+ double finalAngle = position.getAngle()+this.rotationalOffset;
+
+ double x = (Math.sin(finalAngle) * (double) radiusX)+(double) centerX;
+ double y = (Math.cos(finalAngle) * (double) radiusY)+(double) centerY;
+
+ double initialWidth = (double) comp.getPreferredSize().width;
+ double initialHeight = ((double) comp.getPreferredSize().height) * (initialWidth / (double) comp.getPreferredSize().width);
+
+ double s = getScale(finalAngle, x,y,(double) centerX, (double) centerY);//(y / (double) centerY);
+ double boundsWidth = initialWidth * s;
+ double boundsHeight = initialHeight * s;
+
+ if (!shouldHide(comp, finalAngle,s)){
+ //Even scaling only to avoid windows jitter...
+ int finalWidth = (int)boundsWidth / 1;
+ int finalHeight = (int)boundsHeight / 1;
+ finalWidth = (int) finalWidth & 0xFFFFFFFE;
+ finalHeight = (int) finalHeight & 0xFFFFFFFE;
+ comp.setBounds((int)x - ((int)boundsWidth/2),(int) y - ((int)boundsHeight /2),finalWidth, finalHeight);
+ } else {
+ comp.setBounds(-100,-100,32,32);
+ }
+
+ position.setZ(s);
+ z_order[p++] = position;
+ }
+
+ //Now sort out the z, we may need to cache the dimensions, do the z and then reset the bounds, see what happens on redraw first
+ //bubble sort is actually very fast for a small number of items, and this layout shouldn't be used for loads.
+ boolean swaps = true;
+ int limit = numberOfItems-1;
+ while (swaps){
+ swaps = false;
+ for (int j=0;j<limit;j++){
+ if (z_order[j].getZ()<z_order[j+1].getZ()){
+ CaroselPosition temp = z_order[j+1];
+ z_order[j+1]=z_order[j];
+ z_order[j]=temp;
+ swaps=true;
+ }
+ }
+ limit--;
+ //We must be done if we hit the bottom
+ if (limit==0){
+ swaps=false;
+ }
+ }
+
+ //Re-order everything (yet as little as possible :-)
+ for (int j=0;j<numberOfItems;j++){
+ if (target.getComponentZOrder(z_order[j].getComponent())!=j){
+ target.setComponentZOrder(z_order[j].getComponent(),j);
+ }
+ }
+ }
+
+ /**
+ * Returns the current rotational angle
+ *
+ * @return The current rotated angle in radians
+ */
+ public double getAngle() {
+ return this.rotationalOffset;
+ }
+
+ /**
+ * Sets the current rotational angle. Will not cause an animation to start
+ *
+ * @param d The desired angle in radians
+ */
+ public void setAngle(double d) {
+ this.rotationalOffset = d;
+ }
+
+ /**
+ * Determines if an animation is currently playing
+ *
+ * @return true if it is animating, false if it isn't
+ */
+ protected boolean isAnimating(){
+ if (!animationTimer.isRunning()){
+ return false;
+ }
+
+ try{
+ for (Component comp : components) {
+ CaroselPosition cpos = getPosition(comp);
+ if (cpos.isAnimating()){
+ return true;
+ }
+ }
+ } catch (ConcurrentModificationException ex){
+ return isAnimating();
+ }
+
+ if (Math.abs(rotationalOffset - targetOffset) < 0.001){
+ return false;
+ } else {
+ return true;
+ }
+
+ }
+
+ /**
+ * Manages timer actions, terminating the timer if any event is fully achieved
+ *
+ * @param actionEvent the action event, although this will always be the timer
+ */
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ if (animationTimer==null){
+ return;
+ }
+
+ if (!animationTimer.isRunning()){
+ return;
+ }
+
+ if (!isAnimating()){
+ animationTimer.stop();
+ return;
+ }
+
+ //Update any animating icons, could be subject to modification
+ //outside the EDT
+ try {
+ for (Component comp : components) {
+ CaroselPosition cpos = getPosition(comp);
+
+ if (cpos.isAnimating()){
+ cpos.updateAngle();
+ }
+ }
+ } catch (ConcurrentModificationException cMe){
+ actionPerformed(actionEvent);
+ }
+ rotationalOffset += (targetOffset - rotationalOffset) / 6.0;
+ if (container!=null){
+ this.layoutContainer(container);
+ if (container instanceof Component){
+ ((Component) container).repaint();
+ }
+ }
+ }
+
+ /**
+ * Moves everything to their "target" positions, without animating anything
+ *
+ */
+ public void finalizeLayoutImmediately(){
+ for (Component comp : components){
+ CaroselPosition cpos = getPosition(comp);
+
+ cpos.angle=cpos.targetAngle;
+ }
+ rotationalOffset = targetOffset;
+ recalculateCarosel();
+ container.validate();
+ }
+
+ /**
+ * Sets a target angle to rotate to, always choses a direction that is less than
+ * or equal to 180 degrees
+ *
+ * @param target The target angle in radians
+ */
+ protected final void setTarget(double target){
+ //We should never have to rotate more than PI radians
+ while (Math.abs(target-rotationalOffset) > Math.PI){
+ if (target<rotationalOffset){
+ target += Math.PI * 2;
+ } else {
+ target -= Math.PI * 2;
+ }
+ }
+ targetOffset = target;
+ if (!animationTimer.isRunning()){
+ animationTimer.setCoalesce(true);
+ animationTimer.setRepeats(true);
+ animationTimer.setDelay(20);
+ animationTimer.start();
+ }
+ }
+
+ /**
+ * Moves the specified component to the front
+ *
+ * @param component The component move to the front
+ */
+ public void setFrontMostComponent(Component component){
+ setTarget(-getPosition(component).getTargetAngle());
+ }
+
+ /**
+ * Retrieve the component before the specified one. It does not filter out invisible ones.
+ * @param component The component you are looking for the one before for.
+ * @return The component before it
+ */
+ public Component getPreviousComponent(Component component) {
+ int i = this.components.indexOf(component)-1;
+ if (i<0){
+ return components.get(components.size()-1);
+ } else {
+ return components.get(i);
+ }
+ }
+
+
+ /**
+ * Retrieve the component after the specified one. It does not filter out invisible ones.
+ * @param component The component
+ * @return The one after it
+ */
+ public Component getNextComponent(Component component) {
+ int i = this.components.indexOf(component)+1;
+ if (i>=components.size()){
+ return components.get(0);
+ } else {
+ return components.get(i);
+ }
+ }
+
+ /**
+ * The number of components being laid out. Does not included hidden ones
+ * @return The number of components
+ */
+ public int getComponentCount(){
+ return components.size();
+ }
+
+ /**
+ * Gets the index of the supplied component
+ * @param comp The component
+ * @return The index
+ */
+ public int getComponentIndex(Component comp) {
+ return components.indexOf(comp);
+ }
+
+ /**
+ * The size of comopnents a neutral width
+ * @return The size of components at neutral width (scale 1.0)
+ */
+ public int getNeutralContentWidth() {
+ return neutralContentWidth;
+ }
+class CaroselPosition{
+ protected double angle;
+ protected double scale;
+ protected double z;
+ protected Component component;
+ protected boolean firstSet = false;
+ protected double targetAngle = 0.0;
+
+ public CaroselPosition(Component component){
+ angle = 0.0;
+ scale = 0.0;
+ z = 0.0;
+ this.component = component;
+ }
+
+ public Component getComponent(){
+ return component;
+ }
+
+ public double getZ(){
+ return z;
+ }
+
+ public void setZ(double z){
+ this.z = z;
+ }
+
+ public double getTargetAngle(){
+ return targetAngle;
+ }
+
+ public double getAngle(){
+ return angle;
+ }
+
+ public double getScale(){
+ return scale;
+ }
+
+ public boolean isAnimating(){
+ if ((Math.abs(angle - targetAngle) < 0.001)){
+ return false;
+ }
+ return true;
+ }
+
+ public void moveToTarget(){
+ angle=targetAngle;
+ }
+
+ public void updateAngle(){
+ if ((Math.abs(angle - targetAngle) < 0.001)){
+ angle = targetAngle;
+ } else {
+ angle += Math.min((targetAngle - angle) / 6.0,0.10);
+ }
+ }
+
+ public void setAngle(double angle){
+ if (firstSet){
+ this.angle = angle;
+ this.targetAngle = angle;
+ firstSet = false;
+ } else {
+ this.targetAngle = angle;
+ }
+ }
+
+ public void setScale(double scale){
+ this.scale = scale;
+ }
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/layout/OffsetCaroselLayout.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/layout/OffsetCaroselLayout.java
new file mode 100644
index 0000000..450a7dc
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/swing/layout/OffsetCaroselLayout.java
@@ -0,0 +1,111 @@
+/*
+ * OffsetCaroselLayout.java
+ *
+ * Created on January 12, 2007, 6:11 AM
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.swing.layout;
+
+import java.awt.*;
+
+import org.pushingpixels.lafwidget.contrib.blogofbug.swing.components.RichComponent;
+
+
+/**
+ * Offsets a normal carousel layout be a fixed amount allowing it be "moved" around the screen.
+ * @author nigel
+ */
+public class OffsetCaroselLayout extends CaroselLayout{
+
+ /**
+ * Creates a new instance of OffsetCaroselLayout
+ * @param forContainer The container to associate the layout with.
+ */
+ public OffsetCaroselLayout(Container forContainer) {
+ super(forContainer);
+ }
+
+ //Checks to see if the component should be hidden
+ /**
+ * Overrides the normal layout method to determine if the object is offscreen and can therefore
+ * be ignored (giving a performance gain). In addition, it will adjust the alpha of the component
+ * based on its distance from 3'oclock (or 15:00 if you use a 24-hour compass)
+ * @param comp The component to consider
+ * @param angle Its position on the carousel.
+ * @param scale The scale (applied to size) of the image, that is, how far from the observer is it
+ * @return True if it should be hidden, false if it should not
+ */
+ @Override
+ protected boolean shouldHide(Component comp, double angle, double scale){
+ //Quick test where it's visible
+
+ double cos = Math.cos(angle);
+ double sin = Math.sin(angle);
+ if ((sin>-0.5) && (sin<1) && (cos< 0.3)){
+ //We are not going to hide it, but we will set the alpha
+ if (scale>1.00){
+ scale = Math.abs(scale-1.0);
+ scale *= 3.0;
+ scale = 1.0-scale;
+ } else {
+ scale = 1.0;
+
+ }
+ if (comp instanceof RichComponent){
+ ((RichComponent) comp).setAlpha((float)scale);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines how "wide" the carousel should be drawn based on the side of the container the layout is laying out
+ * @param target The container the radius should be calculated for
+ * @param insets Any insets of the container
+ * @param width The width of the container
+ * @param height The height of the container
+ * @param widestComponent The widest component
+ * @return The size (in a bounding box) of the carousel
+ */
+ @Override
+ protected Dimension getCarouselRadius(Container target, Insets insets, int width, int height, int widestComponent){
+ width = target.getSize().width - (insets.left + insets.right+(widestComponent/2));;
+ height = (int) (height/2.5);
+
+ return new Dimension(width,height);
+ }
+
+ /**
+ * Determines the center of the carousel based on the dimensions of the container
+ * @param insets The container insets
+ * @param width Width of the container
+ * @param height The height of the container
+ * @param widest width of the container
+ * @return A point representing the new center
+ */
+ @Override
+ protected Point calculateCenter(Insets insets, int width, int height, int widest) {
+ return new Point(0,((height-insets.bottom)/2)+widest/3);
+ }
+
+ /**
+ * Over-rides the normal setFrontMostComponent to move the selected component to 3 o'clock instead of 6 o'clock
+ * @param component The component to move to the 'front'
+ */
+ @Override
+ public void setFrontMostComponent(Component component) {
+ setTarget(-getPosition(component).getTargetAngle()+ (Math.PI /2));
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/utility/ImageUtilities.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/utility/ImageUtilities.java
new file mode 100644
index 0000000..7964af1
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/contrib/blogofbug/utility/ImageUtilities.java
@@ -0,0 +1,241 @@
+/*
+ * ImageUtilities.java
+ *
+ * Created on March 16, 2007, 4:34 PM
+ *
+ * Copyright 2006-2007 Nigel Hughes
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
+ * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package org.pushingpixels.lafwidget.contrib.blogofbug.utility;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsEnvironment;
+import java.awt.Image;
+import java.awt.RenderingHints;
+import java.awt.Transparency;
+import java.awt.font.FontRenderContext;
+import java.awt.font.LineBreakMeasurer;
+import java.awt.font.TextAttribute;
+import java.awt.font.TextLayout;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.util.Hashtable;
+import java.util.LinkedList;
+import javax.imageio.ImageIO;
+import javax.swing.JComponent;
+
+/**
+ * Static class with utility methods for images
+ *
+ * @author nigel
+ */
+public class ImageUtilities {
+
+ /**
+ * Creates an image compatible with the current display
+ *
+ * @return A BufferedImage with the appropriate color model
+ */
+ public static BufferedImage createCompatibleImage(int width, int height){
+ GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
+
+ //Create a buffered image which is the right (translucent) format for the current graphics device, this
+ //should ensure the fastest possible performance. Adding on some extra height to make room for the reflection
+ return configuration.createCompatibleImage(width,height, Transparency.TRANSLUCENT);
+ }
+
+ /**
+ * Loads an image in a format compatible with the current display
+ *
+ *
+ * @return A BufferedImage with the appropriate color model
+ */
+ public static BufferedImage loadCompatibleImage(String imageURL){
+ Image image = null;
+ try {
+ image = ImageIO.read(new URL(imageURL));
+ } catch (MalformedURLException ex) {
+ ex.printStackTrace();
+ return null;
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ if (image==null){
+ return null;
+ }
+
+ GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
+
+ //Create a buffered image which is the right (translucent) format for the current graphics device, this
+ //should ensure the fastest possible performance. Adding on some extra height to make room for the reflection
+ BufferedImage originalImage = configuration.createCompatibleImage(image.getWidth(null),image.getHeight(null), Transparency.TRANSLUCENT);
+
+ //Blit the loaded image onto the optimized surface by creating a graphics context for the new image
+ Graphics2D g = originalImage.createGraphics();
+ //Draw the original image
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+ return originalImage;
+ }
+
+ /**
+ * Produces a resized image that is of the given dimensions
+ *
+ * @param image The original image
+ * @param width The desired width
+ * @param height The desired height
+ * @return The new BufferedImage
+ */
+ public static BufferedImage scaledImage(BufferedImage image, int width, int height){
+ BufferedImage newImage = createCompatibleImage(width,height);
+ Graphics graphics = newImage.createGraphics();
+
+ graphics.drawImage(image,0,0,width,height,null);
+
+ graphics.dispose();
+ return newImage;
+ }
+
+ /**
+ * Produces a copy of the supplied image
+ *
+ * @param image The original image
+ * @return The new BufferedImage
+ */
+ public static BufferedImage copyImage(BufferedImage image){
+ return scaledImage(image,image.getWidth(),image.getHeight());
+ }
+
+ /**
+ * Renders a paragraph of text (line breaks ignored) to an image (created and returned).
+ *
+ * @param font The font to use
+ * @param textColor The color of the text
+ * @param text The message
+ * @param width The width the text should be limited to
+ * @return An image with the text rendered into it
+ */
+ public static BufferedImage renderTextToImage(Font font, Color textColor, String text, int width){
+ Hashtable map = new Hashtable();
+ map.put(TextAttribute.FONT, font);
+ AttributedString attributedString = new AttributedString(text,map);
+ AttributedCharacterIterator paragraph = attributedString.getIterator();
+
+ FontRenderContext frc = new FontRenderContext(null, false, false);
+ int paragraphStart = paragraph.getBeginIndex();
+ int paragraphEnd = paragraph.getEndIndex();
+ LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, frc);
+
+ float drawPosY=0;
+
+ //First time around, just determine the height
+ while (lineMeasurer.getPosition() < paragraphEnd) {
+ TextLayout layout = lineMeasurer.nextLayout(width);
+
+ // Move it down
+ drawPosY += layout.getAscent() + layout.getDescent() + layout.getLeading();
+ }
+
+ BufferedImage image = ImageUtilities.createCompatibleImage(width,(int) drawPosY);
+ Graphics2D graphics = (Graphics2D) image.getGraphics();
+ graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+ drawPosY=0;
+ lineMeasurer.setPosition(paragraphStart);
+ while (lineMeasurer.getPosition() < paragraphEnd) {
+ TextLayout layout = lineMeasurer.nextLayout(width);
+
+ // Move y-coordinate by the ascent of the layout.
+ drawPosY += layout.getAscent();
+
+ /* Compute pen x position. If the paragraph is
+ right-to-left, we want to align the TextLayouts
+ to the right edge of the panel.
+ */
+ float drawPosX;
+ if (layout.isLeftToRight()) {
+ drawPosX = 0;
+ } else {
+ drawPosX = width - layout.getAdvance();
+ }
+
+ // Draw the TextLayout at (drawPosX, drawPosY).
+ layout.draw(graphics, drawPosX, drawPosY);
+
+ // Move y-coordinate in preparation for next layout.
+ drawPosY += layout.getDescent() + layout.getLeading();
+ }
+
+ graphics.dispose();
+ return image;
+ }
+
+
+ /**
+ * Renders multiple paragraphs of text in an array to an image (created and returned).
+ *
+ * @param font The font to use
+ * @param textColor The color of the text
+ * @param text The message in an array of strings (one paragraph in each
+ * @param width The width the text should be limited to
+ * @return An image with the text rendered into it
+ */
+ public static BufferedImage renderTextToImage(Font font, Color textColor, String text[], int width){
+ LinkedList<BufferedImage> images = new LinkedList<BufferedImage>();
+
+ int totalHeight = 0;
+
+ for (String paragraph : text){
+ BufferedImage paraImage = renderTextToImage(font,textColor,paragraph,width);
+ totalHeight+=paraImage.getHeight();
+ images.add(paraImage);
+ }
+
+ BufferedImage image = createCompatibleImage(width,totalHeight);
+ Graphics2D graphics = (Graphics2D) image.createGraphics();
+
+ int y=0;
+
+ for (BufferedImage paraImage : images){
+ graphics.drawImage(paraImage,0,y,null);
+ y+=paraImage.getHeight();
+ }
+
+ graphics.dispose();
+ return image;
+ }
+
+ /**
+ * Renders a component into an image, which is useful for playing with the component's
+ * resultant image in special effects or transitions
+ *
+ * @param component The component to render
+ * @return A buffered image with the rendered component.
+ */
+ public static BufferedImage renderComponentToImage(JComponent component){
+ //Create the image
+ BufferedImage image = createCompatibleImage(component.getWidth(),component.getHeight());
+
+ //Render the component onto the image
+ Graphics graphics = image.createGraphics();
+ component.paint(graphics);
+ graphics.dispose();
+ return image;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/desktop/DesktopIconHoverPreviewWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/desktop/DesktopIconHoverPreviewWidget.java
new file mode 100644
index 0000000..b5fb6d2
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/desktop/DesktopIconHoverPreviewWidget.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.desktop;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.JInternalFrame.JDesktopIcon;
+import javax.swing.border.Border;
+import javax.swing.event.MouseInputAdapter;
+import javax.swing.plaf.basic.BasicInternalFrameUI;
+
+import org.pushingpixels.lafwidget.*;
+
+/**
+ * Adds internal frame thumbnail preview on desktop icon mouse hover.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DesktopIconHoverPreviewWidget extends
+ LafWidgetAdapter<JDesktopIcon> {
+ /**
+ * The component that initiates the desktop icon preview (when the mouse
+ * hover above it).
+ */
+ protected JComponent compToHover;
+
+ /**
+ * Listens on the changes to the ancestor.
+ */
+ protected PropertyChangeListener internalFramePropertyListener;
+
+ /**
+ * Snapshot map.
+ */
+ // private static WeakHashMap snapshots = new WeakHashMap();
+ private BufferedImage snapshot;
+
+ /**
+ * Preview window (activated on hover).
+ */
+ private JWindow previewWindow;
+
+ /**
+ * Indicates whether the corresponding desktop icon is dragged.
+ */
+ private boolean isInDrag;
+
+ /**
+ * Mouse handler for the {@link #compToHover}.
+ */
+ protected TitleMouseHandler titleMouseHandler;
+
+ /**
+ * Mouse handler for showing / hiding the preview window.
+ *
+ * @author Kirill Grouchnikov.
+ */
+ protected class TitleMouseHandler extends MouseInputAdapter {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (DesktopIconHoverPreviewWidget.this.isInDrag)
+ return;
+ // final BufferedImage previewImage = DesktopIconHoverPreviewWidget
+ // .getSnapshot(DesktopIconHoverPreviewWidget.this.desktopIcon
+ // .getInternalFrame());
+ BufferedImage previewImage = snapshot;
+ if (previewImage != null) {
+ DesktopIconHoverPreviewWidget.this.previewWindow
+ .getContentPane().removeAll();
+ JLabel previewLabel = new JLabel(new ImageIcon(previewImage));
+ // previewLabel.setBorder(new SubstanceBorder());
+ DesktopIconHoverPreviewWidget.this.previewWindow
+ .getContentPane()
+ .add(previewLabel, BorderLayout.CENTER);
+ DesktopIconHoverPreviewWidget.this.previewWindow.setSize(
+ previewImage.getWidth(), previewImage.getHeight());
+ DesktopIconHoverPreviewWidget.this.syncPreviewWindow(true);
+ DesktopIconHoverPreviewWidget.this.previewWindow
+ .setVisible(true);
+ }
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ DesktopIconHoverPreviewWidget.this.isInDrag = false;
+ DesktopIconHoverPreviewWidget.this.previewWindow.dispose();// setVisible(false);
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ DesktopIconHoverPreviewWidget.this.previewWindow.dispose();// setVisible(false);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ DesktopIconHoverPreviewWidget.this.isInDrag = false;
+ DesktopIconHoverPreviewWidget.this.syncPreviewWindow(true);
+ DesktopIconHoverPreviewWidget.this.previewWindow.setVisible(true);
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ DesktopIconHoverPreviewWidget.this.isInDrag = true;
+ if (DesktopIconHoverPreviewWidget.this.previewWindow.isVisible()) {
+ DesktopIconHoverPreviewWidget.this.syncPreviewWindow(false);
+ DesktopIconHoverPreviewWidget.this.previewWindow.dispose();// setVisible(false);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installComponents()
+ */
+ @Override
+ public void installComponents() {
+ this.previewWindow = new JWindow();
+ this.previewWindow.getContentPane().setLayout(new BorderLayout());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ this.internalFramePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("ancestor".equals(evt.getPropertyName())) {
+ updateSnapshot(jcomp.getInternalFrame());
+ }
+ }
+ };
+ jcomp.getInternalFrame().addPropertyChangeListener(
+ this.internalFramePropertyListener);
+
+ this.titleMouseHandler = new TitleMouseHandler();
+
+ LafWidgetSupport lafSupport = LafWidgetRepository.getRepository()
+ .getLafSupport();
+ this.compToHover = lafSupport.getComponentForHover(jcomp);
+
+ if (this.compToHover != null) {
+ this.compToHover.addMouseMotionListener(this.titleMouseHandler);
+ this.compToHover.addMouseListener(this.titleMouseHandler);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ jcomp.getInternalFrame().removePropertyChangeListener(
+ this.internalFramePropertyListener);
+ this.internalFramePropertyListener = null;
+
+ if (this.compToHover != null) {
+ this.compToHover.removeMouseMotionListener(this.titleMouseHandler);
+ this.compToHover.removeMouseListener(this.titleMouseHandler);
+ }
+ this.titleMouseHandler = null;
+ }
+
+ /**
+ * Synchronizes the preview window.
+ *
+ * @param toShow
+ * Indication whether the preview window is shown.
+ */
+ private void syncPreviewWindow(boolean toShow) {
+ if (toShow) {
+ int x = jcomp.getLocationOnScreen().x;
+ int y = jcomp.getLocationOnScreen().y;
+
+ this.previewWindow.setLocation(x, y
+ - this.previewWindow.getHeight());
+
+ }
+ }
+
+ /**
+ * Updates the snapshot of the specified internal frame.
+ *
+ * @param frame
+ * Internal frame.
+ */
+ private void updateSnapshot(JInternalFrame frame) {
+ if (!frame.isShowing())
+ return;
+ // Draw the current state of the internal frame to a
+ // temp image (w/o border and decorations). It would be nice
+ // to use Robot, but this frame may be partially obscured,
+ // so we take our chances that the frame will be properly
+ // drawn by the user code.
+ int frameWidth = frame.getWidth();
+ int frameHeight = frame.getHeight();
+
+ int dx = 0;
+ int dy = 0;
+ // Now we need to remove the border and the title pane :)
+ Border internalFrameBorder = UIManager
+ .getBorder("InternalFrame.border");
+ Insets borderInsets = internalFrameBorder.getBorderInsets(frame);
+ dx += borderInsets.left;
+ dy += borderInsets.top;
+ frameWidth -= (borderInsets.left + borderInsets.right);
+ frameHeight -= (borderInsets.top + borderInsets.bottom);
+
+ BasicInternalFrameUI frameUI = (BasicInternalFrameUI) frame.getUI();
+ JComponent frameTitlePane = frameUI.getNorthPane();
+
+ if (frameTitlePane != null) {
+ dy += frameTitlePane.getHeight();
+ frameHeight -= frameTitlePane.getHeight();
+ }
+
+ // fix for defect 112 - checking frame height and width
+ if ((frameWidth > 0) && (frameHeight > 0)) {
+ // draw frame (note the canvas translation)
+ BufferedImage tempCanvas = new BufferedImage(frameWidth,
+ frameHeight, BufferedImage.TYPE_INT_ARGB);
+ Graphics tempCanvasGraphics = tempCanvas.getGraphics();
+ tempCanvasGraphics.translate(-dx, -dy);
+ Map<Component, Boolean> dbSnapshot = new HashMap<Component, Boolean>();
+ LafWidgetUtilities.makePreviewable(frame, dbSnapshot);
+ frame.paint(tempCanvasGraphics);
+ LafWidgetUtilities.restorePreviewable(frame, dbSnapshot);
+
+ int maxWidth = UIManager.getInt("DesktopIcon.width");
+ int maxHeight = maxWidth;
+
+ // check if need to scale down
+ double coef = Math.min((double) maxWidth / (double) frameWidth,
+ (double) maxHeight / (double) frameHeight);
+ if (coef < 1.0) {
+ int sdWidth = (int) (coef * frameWidth);
+ // int sdHeight = (int) (coef * frameHeight);
+ // BufferedImage scaledDown = new BufferedImage(sdWidth,
+ // sdHeight,
+ // BufferedImage.TYPE_INT_ARGB);
+ // Graphics g = scaledDown.getGraphics();
+ // g.drawImage(tempCanvas, 0, 0, sdWidth, sdHeight, 0, 0,
+ // frameWidth, frameHeight, null);
+ BufferedImage scaledDown = LafWidgetUtilities.createThumbnail(
+ tempCanvas, sdWidth);
+ // System.out.println("Putting " + frame.hashCode() + "
+ // -> " + scaledDown.hashCode());
+ snapshot = scaledDown;
+ // DesktopIconHoverPreviewWidget.snapshots.put(frame,
+ // scaledDown);
+ } else {
+ // System.out.println("Putting " + frame.hashCode() + "
+ // -> " + snapshot.hashCode());
+ snapshot = tempCanvas;
+ // DesktopIconHoverPreviewWidget.snapshots.put(frame,
+ // tempCanvas);
+ }
+ }
+ }
+
+ /**
+ * Returns the snapshot of the specified internal frame.
+ *
+ * @param frame
+ * Internal frame.
+ * @return The snapshot of the specified internal frame.
+ */
+ public synchronized BufferedImage getSnapshot(JInternalFrame frame) {
+ // BufferedImage result = (BufferedImage)
+ // DesktopIconHoverPreviewWidget.snapshots
+ // .get(frame);
+ // if (result != null)
+ // return result;
+ // // frame.setVisible(true);
+ // // updateSnapshot(frame);
+ // // frame.setVisible(false);
+ // // return snapshots.get(frame);
+ // return null;
+ return this.snapshot;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/menu/MenuSearchWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/menu/MenuSearchWidget.java
new file mode 100644
index 0000000..d1182d2
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/menu/MenuSearchWidget.java
@@ -0,0 +1,948 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.menu;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.*;
+
+import javax.swing.*;
+
+import org.pushingpixels.lafwidget.*;
+
+/**
+ * Adds menu search panel to menu bars.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class MenuSearchWidget extends LafWidgetAdapter<JMenuBar> implements
+ Resettable {
+ /**
+ * Boolean flag to prevent infinite loop. Maybe need to use something more
+ * elegant.
+ */
+ private boolean inEvent = false;
+
+ /**
+ * Listens on changes to the component orientation.
+ */
+ protected PropertyChangeListener propertyListener;
+
+ /**
+ * The associated search panel.
+ */
+ private SearchPanel searchPanel;
+
+ /**
+ * Panel for searching the menus.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class SearchPanel extends JPanel {
+ /**
+ * Toggle button for showing / hiding search controls.
+ */
+ private JToggleButton searchButton;
+
+ /**
+ * Text field for entering search string.
+ */
+ private JTextField searchStringField;
+
+ // /**
+ // * The associated menu bar.
+ // */
+ // private JMenuBar menuBar;
+ //
+ /**
+ * The result buttons. Key is {@link Integer}, value is {@link JButton}.
+ */
+ private Map<Integer, JButton> resultButtons;
+
+ /**
+ * Simple constructor.
+ *
+ * @param menuBar
+ * The associated menu bar.
+ */
+ public SearchPanel(final JMenuBar menuBar) {
+ // this.menuBar = menuBar;
+ this.setLayout(new SearchResultsLayout(this));
+
+ // Search button (toggle) with tooltip.
+ LafWidgetSupport support = LafWidgetRepository.getRepository()
+ .getLafSupport();
+ int iconDim = support.getLookupIconSize();
+ int buttonDim = support.getLookupButtonSize();
+ Icon searchIcon = (support == null) ? LafWidgetUtilities
+ .getSearchIcon(iconDim, jcomp.getComponentOrientation()
+ .isLeftToRight()) : support.getSearchIcon(iconDim,
+ jcomp.getComponentOrientation());
+ this.searchButton = new JToggleButton(searchIcon);
+ this.searchButton.setPreferredSize(new Dimension(buttonDim,
+ buttonDim));
+ ResourceBundle bundle = LafWidgetUtilities
+ .getResourceBundle(menuBar);
+ this.searchButton.setToolTipText(bundle
+ .getString("Tooltip.menuSearchButton"));
+ this.searchButton.setFocusable(false);
+ if (support != null)
+ support.markButtonAsFlat(this.searchButton);
+ this.add(this.searchButton);
+
+ // Add action listener on the toggle button. Based on the
+ // state of the toggle button, the search field and result buttons
+ // will be set visible or invisible.
+ this.searchButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ boolean toShow = SearchPanel.this.searchButton
+ .isSelected();
+ SearchPanel.this.searchStringField
+ .setVisible(toShow);
+ SearchPanel.this.searchStringField.requestFocus();
+ for (JButton resultButton : SearchPanel.this.resultButtons
+ .values()) {
+ resultButton.setVisible(toShow);
+ }
+ SearchPanel.this.repaint();
+ SearchPanel.this.revalidate();
+ }
+ });
+ }
+ });
+ // add mouse listener to remove the search panel on mouse
+ // click when CTRL button is pressed.
+ this.searchButton.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if ((e.getModifiers() & InputEvent.CTRL_MASK) != 0) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ SearchPanel.this.removeAll();
+ SearchPanel.this.repaint();
+ jcomp.revalidate();
+ }
+ });
+ }
+ }
+ });
+
+ // Search field.
+ this.searchStringField = new JTextField();
+ this.searchStringField.setColumns(10);
+ this.add(this.searchStringField);
+ this.searchStringField.setVisible(false);
+ this.searchStringField.setToolTipText(bundle
+ .getString("Tooltip.menuSearchField"));
+
+ // Map to hold the result buttons (need for the icon reset
+ // on theme change and layout manager).
+ this.resultButtons = new HashMap<Integer, JButton>();
+ this.searchStringField.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String searchString = SearchPanel.this.searchStringField
+ .getText().toLowerCase();
+ // See if there is at least one non-white space character.
+ // This is fix for bug 54
+ if (searchString.trim().length() == 0) {
+ return;
+ }
+
+ // remove all old buttons
+ for (JButton toRemove : SearchPanel.this.resultButtons
+ .values()) {
+ SearchPanel.this.remove(toRemove);
+ }
+ SearchPanel.this.resultButtons.clear();
+ // find all matching menu items / menus
+ LinkedList<SearchResult> searchResults = SearchPanel.this
+ .findOccurences(searchString);
+ int count = 0;
+ for (SearchResult searchResult : searchResults) {
+ // show only first 16 results.
+ if (count == 16)
+ break;
+ // create new button with binary icon
+ LafWidgetSupport support = LafWidgetRepository
+ .getRepository().getLafSupport();
+ Icon markerIcon = support.getNumberIcon(count + 1);
+ JButton resultButton = new JButton(markerIcon);
+ // set action listener (to show the menu).
+ resultButton
+ .addActionListener(new SearchResultListener(
+ searchResult));
+ // check if the path to the menu (item) has
+ // only enabled items.
+ resultButton.setEnabled(searchResult.isEnabled());
+ SearchPanel.this.add(resultButton);
+ SearchPanel.this.resultButtons.put(count + 1, resultButton);
+ resultButton.setToolTipText("<html><body><b>"
+ + searchResult.toString()
+ + "</b><br>"
+ + LafWidgetUtilities.getResourceBundle(menuBar)
+ .getString("Tooltip.menuSearchTooltip")
+ + "</html>");
+ if (support != null)
+ support.markButtonAsFlat(resultButton);
+ count++;
+ }
+ SearchPanel.this.repaint();
+ jcomp.revalidate();
+ }
+ });
+ }
+
+ /**
+ * Returns all occurences of the specified string in the menus and menu
+ * items of the associated menu bar.
+ *
+ * @param searchPattern
+ * Pattern to search (no wildcards yet).
+ * @return All occurences of the specified string in the menus and menu
+ * items of the associated menu bar.
+ */
+ private LinkedList<SearchResult> findOccurences(String searchPattern) {
+ LinkedList<SearchResult> result = new LinkedList<SearchResult>();
+
+ LinkedList<JMenu> currentPath = new LinkedList<JMenu>();
+
+ for (int i = 0; i < jcomp.getComponentCount(); i++) {
+ Component component = jcomp.getComponent(i);
+ if (component instanceof JMenu) {
+ JMenu menu = (JMenu) component;
+ this.checkMenu(currentPath, menu, searchPattern, result);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Recursively scans the specified menu (item) and updates the list that
+ * contains all occurences of the specified string in the contained
+ * menus and menu items.
+ *
+ * @param currentPath
+ * The path to the current menu (item). Contains
+ * {@link JMenu}s.
+ * @param menuItem
+ * The menu (item) itself that is being tested.
+ * @param searchPattern
+ * Pattern to search (no wildcards yet).
+ * @param matchingResults
+ * All occurences of the specified string up until now. After
+ * <code>this</code> function returns, will also contain
+ * all occurences of the specified string in the contained
+ * menu (item)s. Contains {@link SearchResult}s.
+ */
+ private void checkMenu(LinkedList<JMenu> currentPath,
+ JMenuItem menuItem, String searchPattern,
+ LinkedList<SearchResult> matchingResults) {
+ String menuItemText = menuItem.getText();
+ if (menuItemText.toLowerCase().indexOf(searchPattern) >= 0) {
+ matchingResults.addLast(new SearchResult(jcomp, currentPath,
+ menuItem));
+ }
+ if (menuItem instanceof JMenu) {
+ JMenu menu = (JMenu) menuItem;
+ currentPath.addLast(menu);
+ for (int i = 0; i < menu.getMenuComponentCount(); i++) {
+ Component menuComponent = menu.getMenuComponent(i);
+ if (menuComponent instanceof JMenuItem) {
+ JMenuItem childItem = (JMenuItem) menuComponent;
+ this.checkMenu(currentPath, childItem, searchPattern,
+ matchingResults);
+ }
+ }
+ currentPath.removeLast();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Component#setVisible(boolean)
+ */
+ @Override
+ public void setVisible(boolean aFlag) {
+ super.setVisible(aFlag);
+ if (aFlag)
+ this.searchStringField.requestFocus();
+ }
+ }
+
+ /**
+ * Listener on the <code>search result</code> button. The action itself -
+ * show the associated menu path to the menu item that contains the string
+ * that has been specified during the search.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class SearchResultListener implements ActionListener {
+ /**
+ * The associated search result.
+ */
+ private SearchResult searchResult;
+
+ /**
+ * Simple constructor.
+ *
+ * @param searchResult
+ * The associated search result.
+ */
+ public SearchResultListener(SearchResult searchResult) {
+ super();
+ this.searchResult = searchResult;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // start opening the menus
+ MenuElement[] menuElements = this.searchResult.menuElements;
+ MenuSelectionManager.defaultManager().setSelectedPath(menuElements);
+ }
+ }
+
+ /**
+ * Single result of menu search.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class SearchResult {
+ /**
+ * Path to the menu (item). The first element is always {@link JMenuBar},
+ * and after each {@link JMenu} there is it's
+ * {@link JMenu#getPopupMenu()}.
+ */
+ private MenuElement[] menuElements;
+
+ /**
+ * Simple constructor.
+ *
+ * @param menuBar
+ * The main menu bar.
+ * @param menuPath
+ * The menus leading to the matching entry. Contains
+ * {@link JMenu}s.
+ * @param menuLeaf
+ * The menu (item) that matches the search pattern string.
+ */
+ public SearchResult(JMenuBar menuBar, LinkedList<JMenu> menuPath,
+ JMenuItem menuLeaf) {
+ int count = 1;
+ if (menuPath != null)
+ count += 2 * menuPath.size();
+ if (menuLeaf != null)
+ count++;
+ this.menuElements = new MenuElement[count];
+ count = 0;
+
+ // the first element is the menu bar itself
+ this.menuElements[count++] = menuBar;
+ if (menuPath != null) {
+ for (JMenu menu : menuPath) {
+ // JMenu menu = (JMenu) it.next();
+ this.menuElements[count++] = menu;
+ // important - don't forget the popup menu of the menu
+ this.menuElements[count++] = menu.getPopupMenu();
+ }
+ }
+ if (menuLeaf != null)
+ this.menuElements[count] = menuLeaf;
+ }
+
+ /**
+ * Returns the path to the menu (item).
+ *
+ * @return Path to the menu (item). The first element is always
+ * {@link JMenuBar}, and after each {@link JMenu} there is it's
+ * {@link JMenu#getPopupMenu()}.
+ */
+ public MenuElement[] getMenuElements() {
+ return this.menuElements;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ if (this.menuElements != null) {
+ String sep = "";
+ for (int i = 0; i < this.menuElements.length; i++) {
+ MenuElement menuElem = this.menuElements[i];
+ if (menuElem instanceof JMenuItem) {
+ sb.append(sep);
+ sep = " -> ";
+ sb.append(((JMenuItem) menuElem).getText());
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Checks that all entries leading to the associated menu (item) are
+ * enabled.
+ *
+ * @return <code>true</code> if all entries leading to the associated
+ * menu (item) are enabled, <code>false</code> otherwise.
+ */
+ public boolean isEnabled() {
+ // all parts must be enabled
+ for (int i = 0; i < this.menuElements.length; i++) {
+ MenuElement menuElem = this.menuElements[i];
+ if (menuElem instanceof JMenuItem) {
+ JMenuItem menuItem = (JMenuItem) menuElem;
+ if (!menuItem.isEnabled())
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Returns the number of menu items under the specified menu item.
+ *
+ * @param menuItem
+ * The root menu item.
+ * @return The number of menu items under the specified menu item.
+ */
+ private static int getMenuItemCount(JMenuItem menuItem) {
+ int result = 1;
+
+ if (menuItem instanceof JMenu) {
+ JMenu menu = (JMenu) menuItem;
+ for (int i = 0; i < menu.getMenuComponentCount(); i++) {
+ Component child = menu.getMenuComponent(i);
+ if (child instanceof JMenuItem)
+ result += MenuSearchWidget
+ .getMenuItemCount((JMenuItem) child);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the number of menu items under the specified menu bar.
+ *
+ * @param menuBar
+ * The root menu bar.
+ * @return The number of menu items under the specified menu bar.
+ */
+ public static int getMenuItemCount(JMenuBar menuBar) {
+ int result = 0;
+
+ for (int i = 0; i < menuBar.getMenuCount(); i++) {
+ JMenu menu = menuBar.getMenu(i);
+ if (menu != null) {
+ result += MenuSearchWidget.getMenuItemCount(menu);
+ }
+ }
+
+ return result;
+ }
+
+ // /**
+ // * Hides search panels recursively on the specified component.
+ // *
+ // * @param comp
+ // * Component.
+ // * @param toRepaint
+ // * Indication whether the relevant menu bars should be repainted.
+ // */
+ // private static void hideSearchPanels(Component comp, final boolean toRepaint)
+ // {
+ // if (comp instanceof JFrame) {
+ // JFrame jf = (JFrame) comp;
+ // if (jf.getRootPane() != null) {
+ // JMenuBar menuBar = jf.getJMenuBar();
+ // if (menuBar != null) {
+ // for (int j = 0; j < menuBar.getComponentCount(); j++) {
+ // if (menuBar.getComponent(j) instanceof SearchPanel) {
+ // SearchPanel sPanel = (SearchPanel) menuBar
+ // .getComponent(j);
+ // menuBar.remove(sPanel);
+ // if (toRepaint)
+ // menuBar.repaint();
+ // break;
+ // }
+ // }
+ // }
+ // }
+ // }
+ //
+ // if (comp instanceof JInternalFrame) {
+ // JInternalFrame jif = (JInternalFrame) comp;
+ // if (jif.getRootPane() != null) {
+ // JMenuBar menuBar = jif.getJMenuBar();
+ // if (menuBar != null) {
+ // for (int j = 0; j < menuBar.getComponentCount(); j++) {
+ // if (menuBar.getComponent(j) instanceof SearchPanel) {
+ // SearchPanel sPanel = (SearchPanel) menuBar
+ // .getComponent(j);
+ // menuBar.remove(sPanel);
+ // if (toRepaint)
+ // menuBar.repaint();
+ // break;
+ // }
+ // }
+ // }
+ // }
+ // }
+ //
+ // if (comp instanceof Container) {
+ // Container cont = (Container) comp;
+ // for (int i = 0; i < cont.getComponentCount(); i++) {
+ // Component child = cont.getComponent(i);
+ // if (child instanceof JDesktopIcon)
+ // child = ((JDesktopIcon) child).getInternalFrame();
+ // hideSearchPanels(child, toRepaint);
+ // }
+ // }
+ // }
+ //
+ // /**
+ // * Hides search panels on all menu bars (both JFrames and JInternalFrames).
+ // *
+ // * @param toRepaint
+ // * Indication whether the relevant menu bars should be repainted.
+ // */
+ // public static void hideSearchPanels(final boolean toRepaint) {
+ // SwingUtilities.invokeLater(new Runnable() {
+ // public void run() {
+ // Frame[] frames = Frame.getFrames();
+ // for (int i = 0; i < frames.length; i++) {
+ // hideSearchPanels(frames[i], toRepaint);
+ // }
+ // };
+ // });
+ // }
+
+ // /**
+ // * Shows search panels on all descendant internal frames of the specified
+ // * component.
+ // *
+ // * @param comp
+ // * A component.
+ // */
+ // protected static void showSearchPanels(Component comp) {
+ // if (comp instanceof JDesktopPane) {
+ // JDesktopPane desktop = (JDesktopPane) comp;
+ // JInternalFrame[] iFrames = desktop.getAllFrames();
+ // for (int i = 0; i < iFrames.length; i++) {
+ // JInternalFrame jif = iFrames[i];
+ // if (jif.getRootPane() != null) {
+ // JMenuBar menuBar = jif.getJMenuBar();
+ // if (menuBar != null)
+ // SwingUtilities.updateComponentTreeUI(menuBar);
+ // }
+ // }
+ // return;
+ // }
+ // if (comp instanceof Container) {
+ // Container cont = (Container) comp;
+ // for (int i = 0; i < cont.getComponentCount(); i++) {
+ // MenuSearchWidget.showSearchPanels(cont.getComponent(i));
+ // }
+ // }
+ // }
+ //
+ // /**
+ // * Shows search panels on all menu bars (both JFrames and JInternalFrames).
+ // */
+ // public static void showSearchPanels() {
+ // SwingUtilities.invokeLater(new Runnable() {
+ // public void run() {
+ // Frame[] frames = Frame.getFrames();
+ // for (int i = 0; i < frames.length; i++) {
+ // Frame frame = frames[i];
+ //
+ // if (frame instanceof JFrame) {
+ // JFrame jf = (JFrame) frame;
+ // if (jf.getRootPane() != null) {
+ // JMenuBar menuBar = jf.getJMenuBar();
+ // if (menuBar != null)
+ // SwingUtilities.updateComponentTreeUI(menuBar);
+ // }
+ // }
+ // // fix for defect 134 - menubars on internal frames
+ // MenuSearchWidget.showSearchPanels(frame);
+ // }
+ // };
+ // });
+ // }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installUI()
+ */
+ @Override
+ public void installUI() {
+ final LafWidgetSupport lafSupport = LafWidgetRepository.getRepository()
+ .getLafSupport();
+ this.searchPanel = new SearchPanel(this.jcomp);
+ this.jcomp.add(searchPanel, this.jcomp.getComponentCount());
+ this.searchPanel.setVisible(lafSupport.toInstallMenuSearch(this.jcomp));
+ // NewMenuSearchWidget.panels.put(this.jcomp, searchPanel);
+ // toAddListener = true;
+ // }
+
+ // if (toAddListener) {
+ // need to add a container listener that will move a newly added
+ // JMenu one entry before the last (so that our search panel
+ // will always be the last).
+ this.jcomp.addContainerListener(new ContainerAdapter() {
+ @Override
+ public void componentAdded(ContainerEvent e) {
+ if (!(e.getChild() instanceof JMenu))
+ return;
+ if (!inEvent) {
+ inEvent = true;
+ Component removed = null;
+ for (int i = 0; i < MenuSearchWidget.this.jcomp
+ .getComponentCount(); i++) {
+ if (MenuSearchWidget.this.jcomp.getComponent(i) instanceof SearchPanel) {
+ removed = MenuSearchWidget.this.jcomp
+ .getComponent(i);
+ break;
+ }
+ }
+ if (removed != null) {
+ MenuSearchWidget.this.jcomp.remove(removed);
+ MenuSearchWidget.this.jcomp
+ .add(removed, MenuSearchWidget.this.jcomp
+ .getComponentCount());
+ // Show search panel only if the LAF-specific
+ // support requests this
+ if (lafSupport.toInstallMenuSearch(jcomp))
+ removed.setVisible(true);
+ else
+ removed.setVisible(false);
+ }
+ inEvent = false;
+ }
+ }
+ });
+ // }
+
+ // SearchPanel sp = (SearchPanel)
+ // NewMenuSearchWidget.panels.get(this.jcomp);
+ // if (sp != null) {
+ searchPanel.applyComponentOrientation(this.jcomp
+ .getComponentOrientation());
+ // }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallUI()
+ */
+ @Override
+ public void uninstallUI() {
+ this.jcomp.remove(this.searchPanel);
+ super.uninstallUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ super.installListeners();
+
+ this.propertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(final PropertyChangeEvent evt) {
+ if ("componentOrientation".equals(evt.getPropertyName())) {
+ // final SearchPanel sp = (SearchPanel)
+ // NewMenuSearchWidget.panels
+ // .get(NewMenuSearchWidget.this.jcomp);
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (searchPanel != null) {
+ searchPanel
+ .applyComponentOrientation((ComponentOrientation) evt
+ .getNewValue());
+ }
+ MenuSearchWidget.this.reset();
+ }
+ });
+ }
+ if ("locale".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ reset();
+ }
+ });
+ }
+ }
+ };
+ this.jcomp.addPropertyChangeListener(this.propertyListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ this.jcomp.removePropertyChangeListener(this.propertyListener);
+ this.propertyListener = null;
+ }
+
+ @Override
+ public void reset() {
+ LafWidgetSupport support = LafWidgetRepository.getRepository()
+ .getLafSupport();
+ // SearchPanel searchPanel = (SearchPanel) NewMenuSearchWidget.panels
+ // .get(this.jcomp);
+ if (searchPanel == null)
+ return;
+ for (Map.Entry<Integer, JButton> entry : searchPanel.resultButtons
+ .entrySet()) {
+ // Map.Entry entry = (Map.Entry) it.next();
+ int index = entry.getKey();
+ JButton button = entry.getValue();
+
+ Icon markerIcon = (support == null) ? LafWidgetUtilities
+ .getHexaMarker(index) : support.getNumberIcon(index);
+ button.setIcon(markerIcon);
+ }
+ int iconDim = support.getLookupIconSize();
+ Icon searchIcon = (support == null) ? LafWidgetUtilities.getSearchIcon(
+ iconDim, searchPanel.getComponentOrientation().isLeftToRight())
+ : support.getSearchIcon(iconDim, searchPanel
+ .getComponentOrientation());
+ searchPanel.searchButton.setIcon(searchIcon);
+ ResourceBundle bundle = LafWidgetUtilities
+ .getResourceBundle(this.jcomp);
+ searchPanel.searchButton.setToolTipText(bundle
+ .getString("Tooltip.menuSearchButton"));
+ searchPanel.searchStringField.setToolTipText(bundle
+ .getString("Tooltip.menuSearchField"));
+ }
+
+ /**
+ * Layout for the search panel. Note that {@link FlowLayout} is almost
+ * perfect for us, but we need the following:
+ * <ul>
+ * <li>Minimum size to be 16*16 (for the search icon)
+ * <li>When there isn't enough place for result buttons, they should
+ * continue (even if they are unseen) and not flow to the next line.
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class SearchResultsLayout implements LayoutManager {
+ /**
+ * The associated search panel.
+ */
+ private SearchPanel searchPanel;
+
+ /**
+ * Simple constructor.
+ *
+ * @param searchPanel
+ * The associated search panel.
+ */
+ public SearchResultsLayout(SearchPanel searchPanel) {
+ this.searchPanel = searchPanel;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ if (this.searchPanel.searchButton.isSelected())
+ return c.getSize();
+ int buttonSize = LafWidgetRepository.getRepository()
+ .getLafSupport().getLookupButtonSize();
+ return new Dimension(buttonSize, buttonSize);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ // enough for the search icon
+ int buttonSize = LafWidgetRepository.getRepository()
+ .getLafSupport().getLookupButtonSize();
+ return new Dimension(buttonSize, buttonSize);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ int height = c.getHeight();
+ int width = c.getWidth();
+
+ if (!this.searchPanel.searchButton.isVisible())
+ return;
+
+ boolean leftToRight = jcomp.getComponentOrientation()
+ .isLeftToRight();
+
+ if (leftToRight) {
+ // start from the toggle button
+ int x = 2;
+ int sbWidth = this.searchPanel.searchButton.getPreferredSize().width;
+ int sbHeight = this.searchPanel.searchButton.getPreferredSize().height;
+ this.searchPanel.searchButton.setBounds(x,
+ (height - sbHeight) / 2, sbWidth, sbHeight);
+
+ x += (sbWidth + 4);
+
+ if (this.searchPanel.isVisible()) {
+ // now - text field
+ int tbWidth = this.searchPanel.searchStringField
+ .getPreferredSize().width;
+ int tbHeight = this.searchPanel.searchStringField
+ .getPreferredSize().height;
+ // make the text field fit in the available height
+ tbHeight = Math.min(tbHeight, height - 2);
+ this.searchPanel.searchStringField.setBounds(x,
+ (height - tbHeight) / 2, tbWidth, tbHeight);
+
+ x += (tbWidth + 2);
+
+ // result buttons
+ int buttonCount = this.searchPanel.resultButtons.size();
+ for (int i = 1; i <= buttonCount; i++) {
+ JButton button = this.searchPanel.resultButtons.get(i);
+ int bw = button.getPreferredSize().width;
+ int bh = button.getPreferredSize().height;
+
+ button.setBounds(x, (height - bh) / 2, bw, bh);
+ x += (bw + 1);
+ }
+ }
+ } else {
+ // start from the toggle button
+ int x = width - 2;
+ int sbWidth = this.searchPanel.searchButton.getPreferredSize().width;
+ int sbHeight = this.searchPanel.searchButton.getPreferredSize().height;
+ this.searchPanel.searchButton.setBounds(x - sbWidth,
+ (height - sbHeight) / 2, sbWidth, sbHeight);
+
+ x -= (sbWidth + 4);
+
+ if (this.searchPanel.isVisible()) {
+ // now - text field
+ int tbWidth = this.searchPanel.searchStringField
+ .getPreferredSize().width;
+ int tbHeight = this.searchPanel.searchStringField
+ .getPreferredSize().height;
+ // make the text field fit in the available height
+ tbHeight = Math.min(tbHeight, height - 2);
+ this.searchPanel.searchStringField.setBounds(x - tbWidth,
+ (height - tbHeight) / 2, tbWidth, tbHeight);
+
+ x -= (tbWidth + 2);
+
+ // result buttons
+ int buttonCount = this.searchPanel.resultButtons.size();
+ for (int i = 1; i <= buttonCount; i++) {
+ JButton button = this.searchPanel.resultButtons.get(i);
+ int bw = button.getPreferredSize().width;
+ int bh = button.getPreferredSize().height;
+
+ button.setBounds(x - bw, (height - bh) / 2, bw, bh);
+ x -= (bw + 1);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/preview/DefaultPreviewPainter.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/preview/DefaultPreviewPainter.java
new file mode 100644
index 0000000..20423da
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/preview/DefaultPreviewPainter.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.preview;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+import javax.swing.JViewport;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+
+/**
+ * Default implementation of the component preview painter. The component
+ * preview is a scaled-down (as necessary) thumbnail of the relevant component.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DefaultPreviewPainter extends PreviewPainter {
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.PreviewPainter#hasPreview(java.awt.Container,
+ * java.awt.Component, int)
+ */
+ @Override
+ public boolean hasPreview(Container parent, Component component,
+ int componentIndex) {
+ return (component != null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.preview.PreviewPainter#previewComponent(java.awt.Container,
+ * java.awt.Component, int, java.awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void previewComponent(Container parent, Component component,
+ int componentIndex, Graphics g, int x, int y, int w, int h) {
+ if (component == null)
+ return;
+ int compWidth = component.getWidth();
+ int compHeight = component.getHeight();
+
+ if ((compWidth > 0) && (compHeight > 0)) {
+ // draw component
+ BufferedImage tempCanvas = new BufferedImage(compWidth, compHeight,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics tempCanvasGraphics = tempCanvas.getGraphics();
+ component.paint(tempCanvasGraphics);
+
+ // check if need to scale down
+ double coef = Math.min((double) w / (double) compWidth, (double) h
+ / (double) compHeight);
+ if (coef < 1.0) {
+ int sdWidth = (int) (coef * compWidth);
+ int sdHeight = (int) (coef * compHeight);
+ int dx = x + (w - sdWidth) / 2;
+ int dy = y + (h - sdHeight) / 2;
+
+ g.drawImage(LafWidgetUtilities.createThumbnail(tempCanvas,
+ sdWidth), dx, dy, null);
+
+ } else {
+ g.drawImage(tempCanvas, x, y, null);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.PreviewPainter#hasPreviewWindow(java.awt.Container,
+ * java.awt.Component, int)
+ */
+ @Override
+ public boolean hasPreviewWindow(Container parent, Component component,
+ int componentIndex) {
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.preview.PreviewPainter#getPreviewWindowDimension(java.awt.Container,
+ * java.awt.Component, int)
+ */
+ @Override
+ public Dimension getPreviewWindowDimension(Container parent,
+ Component component, int componentIndex) {
+ Dimension superResult = super.getPreviewWindowDimension(parent,
+ component, componentIndex);
+ if (parent instanceof JViewport) {
+ Rectangle viewportRect = ((JViewport) parent).getViewRect();
+ int width = Math.min(viewportRect.width / 3, superResult.width);
+ int height = Math.min(viewportRect.height / 3, superResult.height);
+ return new Dimension(width, height);
+ }
+ return superResult;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/preview/InternalFramePreviewPainter.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/preview/InternalFramePreviewPainter.java
new file mode 100644
index 0000000..f30d947
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/preview/InternalFramePreviewPainter.java
@@ -0,0 +1,101 @@
+package org.pushingpixels.lafwidget.preview;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.WeakHashMap;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.basic.BasicInternalFrameUI;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+
+public class InternalFramePreviewPainter extends DefaultPreviewPainter {
+ /**
+ * Snapshot map.
+ */
+ private static WeakHashMap snapshots = new WeakHashMap();
+
+ public static void refreshSnaphost(JInternalFrame frame) {
+ if (!frame.isShowing())
+ return;
+ // Draw the current state of the internal frame to a
+ // temp image (w/o border and decorations). It would be nice
+ // to use Robot, but this frame may be partially obscured,
+ // so we take our chances that the frame will be properly
+ // drawn by the user code.
+ int frameWidth = frame.getWidth();
+ int frameHeight = frame.getHeight();
+
+ int dx = 0;
+ int dy = 0;
+ // Now we need to remove the border and the title pane :)
+ Border internalFrameBorder = UIManager
+ .getBorder("InternalFrame.border");
+ Insets borderInsets = internalFrameBorder.getBorderInsets(frame);
+ dx += borderInsets.left;
+ dy += borderInsets.top;
+ frameWidth -= (borderInsets.left + borderInsets.right);
+ frameHeight -= (borderInsets.top + borderInsets.bottom);
+
+ BasicInternalFrameUI frameUI = (BasicInternalFrameUI) frame.getUI();
+ JComponent frameTitlePane = frameUI.getNorthPane();
+
+ if (frameTitlePane != null) {
+ dy += frameTitlePane.getHeight();
+ frameHeight -= frameTitlePane.getHeight();
+ }
+
+ // fix for defect 112 - checking frame height and width
+ if ((frameWidth > 0) && (frameHeight > 0)) {
+ // draw frame (note the canvas translation)
+ BufferedImage tempCanvas = new BufferedImage(frameWidth,
+ frameHeight, BufferedImage.TYPE_INT_ARGB);
+ Graphics tempCanvasGraphics = tempCanvas.getGraphics();
+ tempCanvasGraphics.translate(-dx, -dy);
+ frame.paint(tempCanvasGraphics);
+
+ int maxWidth = UIManager.getInt("DesktopIcon.width");
+ int maxHeight = maxWidth;
+
+ // check if need to scale down
+ double coef = Math.min((double) maxWidth / (double) frameWidth,
+ (double) maxHeight / (double) frameHeight);
+ if (coef < 1.0) {
+ int sdWidth = (int) (coef * frameWidth);
+ // int sdHeight = (int) (coef * frameHeight);
+ // BufferedImage scaledDown = new BufferedImage(sdWidth,
+ // sdHeight,
+ // BufferedImage.TYPE_INT_ARGB);
+ // Graphics g = scaledDown.getGraphics();
+ // g.drawImage(tempCanvas, 0, 0, sdWidth, sdHeight, 0, 0,
+ // frameWidth, frameHeight, null);
+ BufferedImage scaledDown = LafWidgetUtilities.createThumbnail(
+ tempCanvas, sdWidth);
+ // System.out.println("Putting " + frame.hashCode() + "
+ // -> " + scaledDown.hashCode());
+ snapshots.put(frame, scaledDown);
+ } else {
+ // System.out.println("Putting " + frame.hashCode() + "
+ // -> " + snapshot.hashCode());
+ snapshots.put(frame, tempCanvas);
+ }
+ }
+ }
+
+ @Override
+ public void previewComponent(Container parent, Component component,
+ int componentIndex, Graphics g, int x, int y, int w, int h) {
+ BufferedImage preview = (BufferedImage) snapshots.get(component);
+ if (preview != null) {
+ g.drawImage(preview, x, y, null);
+ }
+ }
+
+ @Override
+ public Dimension getPreviewWindowDimension(Container parent,
+ Component component, int componentIndex) {
+ return new Dimension(UIManager.getInt("DesktopIcon.width"), UIManager
+ .getInt("DesktopIcon.width"));
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/preview/PreviewPainter.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/preview/PreviewPainter.java
new file mode 100644
index 0000000..869eeab
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/preview/PreviewPainter.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.preview;
+
+import java.awt.*;
+
+/**
+ * Base class for component preview painters.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class PreviewPainter {
+ /**
+ * Draws a component preview on the specified graphics.
+ *
+ * @param parent
+ * Component parent. May be <code>null</code>.
+ * @param component
+ * Component. May be <code>null</code>.
+ * @param componentIndex
+ * Component index in its parent. May be negative.
+ * @param g
+ * Graphics context.
+ * @param x
+ * X coordinate of the preview area.
+ * @param y
+ * Y coordinate of the preview area.
+ * @param w
+ * Width of the preview area.
+ * @param h
+ * Height of the preview area.
+ */
+ public void previewComponent(Container parent, Component component,
+ int componentIndex, Graphics g, int x, int y, int w, int h) {
+ }
+
+ /**
+ * Checks whether the specified component is previewable.
+ *
+ * @param parent
+ * Component parent. May be <code>null</code>.
+ * @param component
+ * Component. May be <code>null</code>.
+ * @param componentIndex
+ * Component index in its parent. May be negative.
+ * @return <code>true</code> if the specified component is previewable,
+ * <code>false</code> otherwise.
+ */
+ public boolean hasPreview(Container parent, Component component,
+ int componentIndex) {
+ return false;
+ }
+
+ /**
+ * Checks whether the specified component has a preview window.
+ *
+ * @param parent
+ * Component parent. May be <code>null</code>.
+ * @param component
+ * Component. May be <code>null</code>.
+ * @param componentIndex
+ * Component index in its parent. May be negative.
+ * @return <code>true</code> if the specified component has a preview
+ * window, <code>false</code> otherwise.
+ */
+ public boolean hasPreviewWindow(Container parent, Component component,
+ int componentIndex) {
+ return false;
+ }
+
+ /**
+ * Returns the dimension for the component preview window.
+ *
+ * @param parent
+ * Component parent. May be <code>null</code>.
+ * @param component
+ * Component. May be <code>null</code>.
+ * @param componentIndex
+ * Component index in its parent. May be negative.
+ * @return Dimension of the component preview window.
+ */
+ public Dimension getPreviewWindowDimension(Container parent,
+ Component component, int componentIndex) {
+ return new Dimension(300, 200);
+ }
+
+ /**
+ * Returns extra delay (in milliseconds) for showing the component preview
+ * window. The base delay is 2000 milliseconds (2 seconds). This function
+ * must return a non-negative value. Note that this method may not be called
+ * in some preview scenarios (that require immediate preview functionality).
+ *
+ * @param parent
+ * Component parent. May be <code>null</code>.
+ * @param component
+ * Component. May be <code>null</code>.
+ * @param componentIndex
+ * Component index in its parent. May be negative.
+ * @return Non-negative extra delay (in milliseconds) for showing the
+ * component preview window.
+ */
+ public int getPreviewWindowExtraDelay(Container parent,
+ Component component, int componentIndex) {
+ return 0;
+ }
+
+ /**
+ * Returns indication whether the thumbnail preview should be updated
+ * periodically. If the return value is <code>true</code>, then the
+ * implementation of {@link #getUpdateCycle(Container, Component, int)}
+ * returns the refresh cycle length in milliseconds.
+ *
+ * @param parent
+ * Component parent. May be <code>null</code>.
+ * @param component
+ * Component. May be <code>null</code>.
+ * @param componentIndex
+ * Component index in its parent. May be negative.
+ * @return <code>true</code> if the thumbnail preview of the specified
+ * component should be updated periodically, <code>false</code>
+ * otherwise.
+ */
+ public boolean toUpdatePeriodically(Container parent, Component component,
+ int componentIndex) {
+ return false;
+ }
+
+ /**
+ * If the result of {@link #toUpdatePeriodically(Container, Component, int)}
+ * is <code>true</code>, returns the update cycle length in milliseconds.
+ *
+ * @param parent
+ * Component parent. May be <code>null</code>.
+ * @param component
+ * Component. May be <code>null</code>.
+ * @param componentIndex
+ * Component index in its parent. May be negative.
+ * @return Update cycle length in milliseconds for the thumbnail preview of
+ * the specified component.
+ */
+ public int getUpdateCycle(Container parent, Component component,
+ int componentIndex) {
+ return 10000;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/AutoScrollActivator.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/AutoScrollActivator.java
new file mode 100755
index 0000000..a78b8ed
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/AutoScrollActivator.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.scroll;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+
+/**
+ * Christopher Deckers (chrriis at nextencia.net) http://www.nextencia.net
+ *
+ * @author Christopher Deckers
+ */
+public class AutoScrollActivator {
+
+ protected JScrollPane scrollPane;
+
+ public AutoScrollActivator(JScrollPane scrollPane) {
+ this.scrollPane = scrollPane;
+ configureScrollPane();
+ }
+
+ protected static class AutoScrollProperties {
+ public Point startLocation;
+ public Point currentLocation;
+ public Timer timer;
+ public AWTEventListener toolkitListener;
+ public boolean isDragMode;
+ public JPopupMenu iconPopupMenu;
+ }
+
+ protected AutoScrollProperties autoScrollProperties;
+
+ protected void deactivateAutoScroll() {
+ if (autoScrollProperties == null)
+ return;
+ autoScrollProperties.timer.stop();
+ Toolkit.getDefaultToolkit().removeAWTEventListener(
+ autoScrollProperties.toolkitListener);
+ autoScrollProperties.iconPopupMenu.setVisible(false);
+ autoScrollProperties = null;
+ }
+
+ protected void activateAutoScroll(MouseEvent e) {
+ autoScrollProperties = new AutoScrollProperties();
+ autoScrollProperties.isDragMode = false;
+ JViewport viewport = scrollPane.getViewport();
+ PointerInfo pi = MouseInfo.getPointerInfo();
+ if (pi == null) return; // mouse not on any device
+
+ autoScrollProperties.currentLocation = pi.getLocation();
+ SwingUtilities.convertPointFromScreen(
+ autoScrollProperties.currentLocation, viewport);
+ autoScrollProperties.startLocation = autoScrollProperties.currentLocation;
+ // We use a popup menu so that it can be heavyweight or lightweight
+ // depending on the context.
+ // By default it is probably lightweight and thus uses alpha
+ // transparency
+ final JPopupMenu iconPopupMenu = new JPopupMenu() {
+ @Override
+ public void setBorder(Border border) {
+ // Overriden to avoid having a border set by the L&F
+ }
+ };
+ iconPopupMenu.setFocusable(false);
+ iconPopupMenu.setOpaque(false);
+ JLabel iconLabel = new JLabel(getAutoScrollIcon());
+ iconLabel.addMouseWheelListener(new MouseWheelListener() {
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ deactivateAutoScroll();
+ }
+ });
+ iconPopupMenu.add(iconLabel);
+ iconPopupMenu.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ iconPopupMenu.setVisible(false);
+ }
+ });
+ autoScrollProperties.iconPopupMenu = iconPopupMenu;
+ Dimension iconPopupMenuSize = iconPopupMenu.getPreferredSize();
+ iconPopupMenu.show(viewport, autoScrollProperties.startLocation.x
+ - iconPopupMenuSize.width / 2,
+ autoScrollProperties.startLocation.y - iconPopupMenuSize.height
+ / 2);
+ // Assumption: the popup menu has a parent that is itself added to the
+ // glass pane or to a window.
+ // Some L&F will create borders to that parent, and we don't want that.
+ Container parent = iconPopupMenu.getParent();
+ if (parent instanceof JComponent) {
+ ((JComponent) parent).setBorder(null);
+ }
+ ActionListener actionListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JViewport viewport = scrollPane.getViewport();
+ Component view = viewport.getView();
+ if (view == null) {
+ return;
+ }
+ Point viewPosition = viewport.getViewPosition();
+ int offsetX = autoScrollProperties.currentLocation.x
+ - autoScrollProperties.startLocation.x;
+ int offsetY = autoScrollProperties.currentLocation.y
+ - autoScrollProperties.startLocation.y;
+ offsetX = offsetX > 0 ? Math.max(0, offsetX - 4) : Math.min(0,
+ offsetX + 4);
+ offsetY = offsetY > 0 ? Math.max(0, offsetY - 4) : Math.min(0,
+ offsetY + 4);
+ viewPosition = new Point(viewPosition.x + offsetX,
+ viewPosition.y + offsetY);
+ Dimension extentSize = viewport.getExtentSize();
+ Dimension viewSize = view.getSize();
+ if (viewSize.width - viewPosition.x < extentSize.width) {
+ viewPosition.x = viewSize.width - extentSize.width;
+ }
+ if (viewSize.height - viewPosition.y < extentSize.height) {
+ viewPosition.y = viewSize.height - extentSize.height;
+ }
+ if (viewPosition.x < 0) {
+ viewPosition.x = 0;
+ }
+ if (viewPosition.y < 0) {
+ viewPosition.y = 0;
+ }
+ viewport.setViewPosition(viewPosition);
+ }
+ };
+ autoScrollProperties.timer = new Timer(50, actionListener);
+ autoScrollProperties.timer.start();
+ autoScrollProperties.toolkitListener = new AWTEventListener() {
+ @Override
+ public void eventDispatched(AWTEvent e) {
+ int eventID = e.getID();
+ switch (eventID) {
+ case MouseEvent.MOUSE_MOVED:
+ case MouseEvent.MOUSE_DRAGGED:
+ JViewport viewport = scrollPane.getViewport();
+ PointerInfo pi = MouseInfo.getPointerInfo();
+ if (pi == null) break; // pointer not on any device
+
+ autoScrollProperties.currentLocation = pi.getLocation();
+ SwingUtilities.convertPointFromScreen(
+ autoScrollProperties.currentLocation, viewport);
+ if (!autoScrollProperties.isDragMode
+ && eventID == MouseEvent.MOUSE_DRAGGED) {
+ Dimension size = new Dimension(
+ Math.abs(autoScrollProperties.currentLocation.x
+ - autoScrollProperties.startLocation.x),
+ Math.abs(autoScrollProperties.currentLocation.y
+ - autoScrollProperties.startLocation.y));
+ autoScrollProperties.isDragMode = size.width > HV_IMAGE_ICON
+ .getIconWidth() / 2
+ || size.height > HV_IMAGE_ICON.getIconHeight() / 2;
+ }
+ break;
+ case MouseEvent.MOUSE_PRESSED:
+ case MouseEvent.MOUSE_WHEEL:
+ deactivateAutoScroll();
+ break;
+ case MouseEvent.MOUSE_RELEASED:
+ if (autoScrollProperties.isDragMode
+ && ((MouseEvent) e).getButton() == 2) {
+ deactivateAutoScroll();
+ }
+ break;
+ case WindowEvent.WINDOW_LOST_FOCUS:
+ deactivateAutoScroll();
+ break;
+ }
+ }
+ };
+ Toolkit.getDefaultToolkit().addAWTEventListener(
+ autoScrollProperties.toolkitListener,
+ AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK
+ | AWTEvent.MOUSE_WHEEL_EVENT_MASK
+ | AWTEvent.WINDOW_FOCUS_EVENT_MASK);
+ }
+
+ protected static class AutoScrollMouseListener extends MouseAdapter {
+ protected AutoScrollActivator autoScrollActivator;
+
+ public AutoScrollMouseListener(AutoScrollActivator autoScrollActivator) {
+ this.autoScrollActivator = autoScrollActivator;
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (e.getButton() != 2) {
+ return;
+ }
+ autoScrollActivator.activateAutoScroll(e);
+ }
+ }
+
+ protected void configureScrollPane() {
+ for (MouseListener mouseListener : scrollPane.getMouseListeners()) {
+ if (mouseListener instanceof AutoScrollMouseListener) {
+ return;
+ }
+ }
+ scrollPane.addMouseListener(new AutoScrollMouseListener(this));
+ }
+
+ protected static final ImageIcon H_IMAGE_ICON = new ImageIcon(
+ AutoScrollActivator.class.getResource("resource/autoscroll_h.png"));
+ protected static final ImageIcon V_IMAGE_ICON = new ImageIcon(
+ AutoScrollActivator.class.getResource("resource/autoscroll_v.png"));
+ protected static final ImageIcon HV_IMAGE_ICON = new ImageIcon(
+ AutoScrollActivator.class
+ .getResource("resource/autoscroll_all.png"));
+
+ protected ImageIcon getAutoScrollIcon() {
+ ImageIcon icon;
+ if (scrollPane.getHorizontalScrollBar().isVisible()) {
+ if (scrollPane.getVerticalScrollBar().isVisible()) {
+ icon = HV_IMAGE_ICON;
+ } else {
+ icon = H_IMAGE_ICON;
+ }
+ } else {
+ if (scrollPane.getVerticalScrollBar().isVisible()) {
+ icon = V_IMAGE_ICON;
+ } else {
+ icon = HV_IMAGE_ICON;
+ }
+ }
+ return icon;
+ }
+
+ public static void setAutoScrollEnabled(final JScrollPane scrollPane,
+ boolean isEnabled) {
+ if (isEnabled) {
+ new AutoScrollActivator(scrollPane);
+ } else {
+ for (MouseListener mouseListener : scrollPane.getMouseListeners()) {
+ if (mouseListener instanceof AutoScrollMouseListener) {
+ scrollPane.removeMouseListener(mouseListener);
+ return;
+ }
+ }
+ }
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/AutoScrollWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/AutoScrollWidget.java
new file mode 100644
index 0000000..d7c1f44
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/AutoScrollWidget.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.scroll;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JScrollPane;
+
+import org.pushingpixels.lafwidget.*;
+
+/**
+ * Widget that decorates scroll panes with auto scroll.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class AutoScrollWidget extends LafWidgetAdapter<JScrollPane> {
+ /**
+ * Property change listener - listens on the changes to
+ * {@link LafWidget#AUTO_SCROLL} property.
+ */
+ protected PropertyChangeListener propertyChangeListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installUI()
+ */
+ @Override
+ public void installUI() {
+ if (LafWidgetUtilities.hasAutoScroll(this.jcomp)) {
+ AutoScrollActivator.setAutoScrollEnabled(this.jcomp, true);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallUI()
+ */
+ @Override
+ public void uninstallUI() {
+ AutoScrollActivator.setAutoScrollEnabled(this.jcomp, false);
+ }
+
+ @Override
+ public void installListeners() {
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (LafWidget.AUTO_SCROLL.equals(evt.getPropertyName())) {
+ AutoScrollActivator.setAutoScrollEnabled(jcomp,
+ LafWidgetUtilities.hasAutoScroll(jcomp));
+ }
+ }
+ };
+ this.jcomp.addPropertyChangeListener(this.propertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ this.jcomp.removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/ScrollPaneSelector.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/ScrollPaneSelector.java
new file mode 100644
index 0000000..ae428fd
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/ScrollPaneSelector.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.scroll;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.geom.Area;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.event.MouseInputAdapter;
+import javax.swing.event.MouseInputListener;
+
+import org.pushingpixels.lafwidget.LafWidgetRepository;
+import org.pushingpixels.lafwidget.LafWidgetUtilities2;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.preview.PreviewPainter;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
+
+/**
+ * ScrollPaneSelector is a little utility class that provides a means to quickly
+ * scroll both vertically and horizontally on a single mouse click, by dragging
+ * a selection rectangle over a "thumbnail" of the scrollPane's viewport view.
+ * <p>
+ * Once the selector is installed on a given JScrollPane instance, a little
+ * button appears as soon as at least one of its scroll bars is made visible.
+ * <p>
+ * Contributed by the original author under BSD license. Also appears in the <a
+ * href="https://jdnc-incubator.dev.java.net">JDNC Incubator</a>.
+ *
+ * @author weebib (Pierre LE LANNIC)
+ * @author Kirill Grouchnikov (animations).
+ */
+public class ScrollPaneSelector extends JComponent {
+ // static final fields
+ private static final double MAX_SIZE = 200;
+ // private static final Icon LAUNCH_SELECTOR_ICON = new Icon() {
+ // public void paintIcon(Component c, Graphics g, int x, int y) {
+ // Color tmpColor = g.getColor();
+ // g.setColor(Color.BLACK);
+ // g.drawRect(2, 2, 10, 10);
+ // g.drawRect(4, 5, 6, 4);
+ // g.setColor(tmpColor);
+ // }
+ //
+ // public int getIconWidth() {
+ // return 15;
+ // }
+ //
+ // public int getIconHeight() {
+ // return 15;
+ // }
+ // };
+ // private static Map theInstalledScrollPaneSelectors = new HashMap();
+ private static final String COMPONENT_ORIENTATION = "componentOrientation";
+
+ // private static final String HAS_BEEN_UNINSTALLED =
+ // "lafwidget.internal.scrollPaneSelector.hasBeenUninstalled";
+
+ // member fields
+ private LayoutManager theFormerLayoutManager;
+ private JScrollPane theScrollPane;
+ private JComponent theComponent;
+ private JPopupMenu thePopupMenu;
+ private boolean toRestoreOriginal;
+ private JButton theButton;
+ private BufferedImage theImage;
+ private Rectangle theStartRectangle;
+ private Rectangle theRectangle;
+ private Point theStartPoint;
+ private Point thePrevPoint;
+ private double theScale;
+ private PropertyChangeListener propertyChangeListener;
+ private ContainerAdapter theViewPortViewListener;
+
+ // -- Constructor ------
+ ScrollPaneSelector() {
+ setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
+ theScrollPane = null;
+ theImage = null;
+ theStartRectangle = null;
+ theRectangle = null;
+ theStartPoint = null;
+ theScale = 0.0;
+ theButton = new JButton();
+ LafWidgetRepository.getRepository().getLafSupport().markButtonAsFlat(
+ theButton);
+ theButton.setFocusable(false);
+ theButton.setFocusPainted(false);
+
+ MouseInputListener mil = new MouseInputAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ Point p = e.getPoint();
+ SwingUtilities.convertPointToScreen(p, theButton);
+ display(p);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (!thePopupMenu.isVisible())
+ return;
+ toRestoreOriginal = false;
+ thePopupMenu.setVisible(false);
+ theStartRectangle = theRectangle;
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ if (theStartPoint == null)
+ return;
+
+ if (!thePopupMenu.isShowing())
+ return;
+
+ Point newPoint = e.getPoint();
+ SwingUtilities.convertPointToScreen(newPoint, (Component) e
+ .getSource());
+
+ Rectangle popupScreenRect = new Rectangle(thePopupMenu
+ .getLocationOnScreen(), thePopupMenu.getSize());
+ if (!popupScreenRect.contains(newPoint))
+ return;
+
+ int deltaX = (int) ((newPoint.x - thePrevPoint.x) / theScale);
+ int deltaY = (int) ((newPoint.y - thePrevPoint.y) / theScale);
+ scroll(deltaX, deltaY, false);
+
+ thePrevPoint = newPoint;
+ }
+ };
+ theButton.addMouseListener(mil);
+ theButton.addMouseMotionListener(mil);
+ setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+ thePopupMenu = new JPopupMenu();
+ thePopupMenu.setLayout(new BorderLayout());
+ thePopupMenu.add(this, BorderLayout.CENTER);
+ propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (theScrollPane == null)
+ return;
+ if ("componentOrientation".equals(evt.getPropertyName())) {
+ theScrollPane.setCorner(JScrollPane.LOWER_LEADING_CORNER,
+ null);
+ theScrollPane.setCorner(JScrollPane.LOWER_TRAILING_CORNER,
+ theButton);
+ }
+ }
+ };
+ theViewPortViewListener = new ContainerAdapter() {
+ @Override
+ public void componentAdded(ContainerEvent e) {
+ if (thePopupMenu.isVisible())
+ thePopupMenu.setVisible(false);
+ Component comp = theScrollPane.getViewport().getView();
+ theComponent = (comp instanceof JComponent) ? (JComponent) comp
+ : null;
+ }
+ };
+ thePopupMenu.addPropertyChangeListener(new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("visible".equals(evt.getPropertyName())) {
+ if (!thePopupMenu.isVisible()) {
+ setCursor(Cursor
+ .getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ if (toRestoreOriginal) {
+ int deltaX = (int) ((thePrevPoint.x - theStartPoint.x) / theScale);
+ int deltaY = (int) ((thePrevPoint.y - theStartPoint.y) / theScale);
+ scroll(-deltaX, -deltaY, true);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ // -- JComponent overriden methods ------
+ @Override
+ public Dimension getPreferredSize() {
+ if (theImage == null || theRectangle == null)
+ return new Dimension();
+ Insets insets = getInsets();
+ return new Dimension(theImage.getWidth(null) + insets.left
+ + insets.right, theImage.getHeight(null) + insets.top
+ + insets.bottom);
+ }
+
+ @Override
+ protected void paintComponent(Graphics g1D) {
+ if (theImage == null || theRectangle == null)
+ return;
+ Graphics2D g = (Graphics2D) g1D.create();
+
+ Insets insets = getInsets();
+ int xOffset = insets.left;
+ int yOffset = insets.top;
+ int availableWidth = getWidth() - insets.left - insets.right;
+ int availableHeight = getHeight() - insets.top - insets.bottom;
+ g.drawImage(theImage, xOffset, yOffset, null);
+
+ Color tmpColor = g.getColor();
+ Area area = new Area(new Rectangle(xOffset, yOffset, availableWidth,
+ availableHeight));
+ area.subtract(new Area(theRectangle));
+ g.setColor(new Color(200, 200, 200, 128));
+ g.fill(area);
+ g.setColor(Color.BLACK);
+ g.draw(theRectangle);
+ g.setColor(tmpColor);
+
+ g.dispose();
+ }
+
+ // -- Private methods ------
+ void installOnScrollPane(JScrollPane aScrollPane) {
+ if (theScrollPane != null)
+ uninstallFromScrollPane();
+ theScrollPane = aScrollPane;
+ theFormerLayoutManager = theScrollPane.getLayout();
+ theScrollPane.setLayout(new TweakedScrollPaneLayout());
+ theScrollPane.firePropertyChange("layoutManager", false, true);
+ theScrollPane.addPropertyChangeListener(COMPONENT_ORIENTATION,
+ propertyChangeListener);
+ theScrollPane.getViewport().addContainerListener(
+ theViewPortViewListener);
+ theScrollPane.setCorner(JScrollPane.LOWER_TRAILING_CORNER, theButton);
+ Component comp = theScrollPane.getViewport().getView();
+ theComponent = (comp instanceof JComponent) ? (JComponent) comp : null;
+
+ this.theButton.setIcon(LafWidgetRepository.getRepository()
+ .getLafSupport().getSearchIcon(
+ UIManager.getInt("ScrollBar.width") - 3,
+ theScrollPane.getComponentOrientation()));
+
+ theScrollPane.doLayout();
+ }
+
+ void uninstallFromScrollPane() {
+ if (theScrollPane == null)
+ return;
+ if (thePopupMenu.isVisible())
+ thePopupMenu.setVisible(false);
+ theScrollPane.setCorner(JScrollPane.LOWER_TRAILING_CORNER, null);
+ theScrollPane.removePropertyChangeListener(COMPONENT_ORIENTATION,
+ propertyChangeListener);
+ theScrollPane.getViewport().removeContainerListener(
+ theViewPortViewListener);
+ theScrollPane.setLayout(theFormerLayoutManager);
+ theScrollPane.firePropertyChange("layoutManager", true, false);
+ theScrollPane = null;
+ }
+
+ private void display(Point aPointOnScreen) {
+ if (theComponent == null)
+ return;
+
+ PreviewPainter previewPainter = LafWidgetUtilities2
+ .getComponentPreviewPainter(theScrollPane);
+ if (!previewPainter.hasPreview(theComponent.getParent(), theComponent,
+ 0))
+ return;
+
+ // if (previewPainter == null) {
+ // double compWidth = theComponent.getWidth();
+ // double compHeight = theComponent.getHeight();
+ // double scaleX = MAX_SIZE / compWidth;
+ // double scaleY = MAX_SIZE / compHeight;
+ // theScale = Math.min(scaleX, scaleY);
+ // theImage = new BufferedImage(
+ // (int) (theComponent.getWidth() * theScale),
+ // (int) (theComponent.getHeight() * theScale),
+ // BufferedImage.TYPE_INT_RGB);
+ //
+ // Graphics2D g = theImage.createGraphics();
+ // g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ // RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ // g.scale(theScale, theScale);
+ // theComponent.paint(g);
+ // g.dispose();
+ // } else {
+ Dimension pDimension = previewPainter.getPreviewWindowDimension(
+ theComponent.getParent(), theComponent, 0);
+ double compWidth = theComponent.getWidth();
+ double compHeight = theComponent.getHeight();
+ double scaleX = pDimension.getWidth() / compWidth;
+ double scaleY = pDimension.getHeight() / compHeight;
+ theScale = Math.min(scaleX, scaleY);
+ theImage = new BufferedImage(
+ (int) (theComponent.getWidth() * theScale), (int) (theComponent
+ .getHeight() * theScale), BufferedImage.TYPE_INT_RGB);
+
+ Graphics2D g = theImage.createGraphics();
+ previewPainter.previewComponent(null, theComponent, 0, g, 0, 0,
+ theImage.getWidth(), theImage.getHeight());
+ g.dispose();
+ // }
+
+ theStartRectangle = theComponent.getVisibleRect();
+ Insets insets = getInsets();
+ theStartRectangle.x = (int) (theScale * theStartRectangle.x + insets.left);
+ theStartRectangle.y = (int) (theScale * theStartRectangle.y + insets.right);
+ theStartRectangle.width *= theScale;
+ theStartRectangle.height *= theScale;
+ theRectangle = theStartRectangle;
+
+ Dimension pref = thePopupMenu.getPreferredSize();
+ Point buttonLocation = theButton.getLocationOnScreen();
+ Point popupLocation = new Point(
+ (theButton.getWidth() - pref.width) / 2,
+ (theButton.getHeight() - pref.height) / 2);
+ Point centerPoint = new Point(buttonLocation.x + popupLocation.x
+ + theRectangle.x + theRectangle.width / 2, buttonLocation.y
+ + popupLocation.y + theRectangle.y + theRectangle.height / 2);
+ try {
+ // Attempt to move the mouse pointer to the center of the selector's
+ // rectangle.
+ new Robot().mouseMove(centerPoint.x, centerPoint.y);
+ theStartPoint = centerPoint;
+ } catch (Exception e) {
+ // Since we cannot move the cursor, we'll move the popup instead.
+ theStartPoint = aPointOnScreen;
+ popupLocation.x += theStartPoint.x - centerPoint.x;
+ popupLocation.y += theStartPoint.y - centerPoint.y;
+ }
+ thePrevPoint = new Point(theStartPoint);
+ toRestoreOriginal = true;
+ thePopupMenu.show(theButton, popupLocation.x, popupLocation.y);
+ }
+
+ private void moveRectangle(int aDeltaX, int aDeltaY) {
+ if (theStartRectangle == null)
+ return;
+
+ Insets insets = getInsets();
+ Rectangle newRect = new Rectangle(theStartRectangle);
+ newRect.x += aDeltaX;
+ newRect.y += aDeltaY;
+ newRect.x = Math.min(Math.max(newRect.x, insets.left), getWidth()
+ - insets.right - newRect.width);
+ newRect.y = Math.min(Math.max(newRect.y, insets.right), getHeight()
+ - insets.bottom - newRect.height);
+ Rectangle clip = new Rectangle();
+ Rectangle.union(theRectangle, newRect, clip);
+ clip.grow(2, 2);
+ theRectangle = newRect;
+ paintImmediately(clip);
+ }
+
+ private void syncRectangle() {
+ JViewport viewport = this.theScrollPane.getViewport();
+ Rectangle viewRect = viewport.getViewRect();
+
+ Insets insets = getInsets();
+ Rectangle newRect = new Rectangle();
+ newRect.x = (int) (theScale * viewRect.x + insets.left);
+ newRect.y = (int) (theScale * viewRect.y + insets.top);
+ newRect.width = (int) (viewRect.width * theScale);
+ newRect.height = (int) (viewRect.height * theScale);
+
+ Rectangle clip = new Rectangle();
+ Rectangle.union(theRectangle, newRect, clip);
+ clip.grow(2, 2);
+ theRectangle = newRect;
+
+ // System.out.println(viewRect + "-->" + theRectangle);
+ paintImmediately(clip);
+ }
+
+ private void scroll(final int aDeltaX, final int aDeltaY, boolean toAnimate) {
+ if (theComponent == null)
+ return;
+ final Rectangle oldRectangle = theComponent.getVisibleRect();
+ final Rectangle newRectangle = new Rectangle(oldRectangle.x + aDeltaX,
+ oldRectangle.y + aDeltaY, oldRectangle.width,
+ oldRectangle.height);
+
+ // Animate scrolling
+ if (toAnimate) {
+ Timeline scrollTimeline = new Timeline(theComponent);
+ AnimationConfigurationManager.getInstance().configureTimeline(
+ scrollTimeline);
+ scrollTimeline.addCallback(new UIThreadTimelineCallbackAdapter() {
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ if ((oldState == TimelineState.DONE)
+ && (newState == TimelineState.IDLE)) {
+ theComponent.scrollRectToVisible(newRectangle);
+ syncRectangle();
+ }
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ int x = (int) (oldRectangle.x + timelinePosition * aDeltaX);
+ int y = (int) (oldRectangle.y + timelinePosition * aDeltaY);
+ theComponent.scrollRectToVisible(new Rectangle(x, y,
+ oldRectangle.width, oldRectangle.height));
+ syncRectangle();
+ }
+ });
+ scrollTimeline.play();
+ } else {
+ theComponent.scrollRectToVisible(newRectangle);
+ syncRectangle();
+ }
+ }
+}
\ No newline at end of file
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/ScrollPaneSelectorWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/ScrollPaneSelectorWidget.java
new file mode 100644
index 0000000..e4a3ebe
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/ScrollPaneSelectorWidget.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.scroll;
+
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JScrollPane;
+import javax.swing.plaf.basic.ComboPopup;
+
+import org.pushingpixels.lafwidget.*;
+import org.pushingpixels.lafwidget.preview.PreviewPainter;
+
+/**
+ * Widget that decorates scroll panes with selector.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ScrollPaneSelectorWidget extends LafWidgetAdapter<JScrollPane> {
+ /**
+ * The scroll pane selector for the associated scroll pane.
+ */
+ protected ScrollPaneSelector scrollPaneSelector;
+
+ /**
+ * Hierarchy listener - remove the selector in the scroll pane of a combo
+ * popup.
+ */
+ protected HierarchyListener hierarchyListener;
+
+ /**
+ * Property change listener - listens on the changes to
+ * {@link LafWidget#COMPONENT_PREVIEW_PAINTER} property.
+ */
+ protected PropertyChangeListener propertyChangeListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installUI()
+ */
+ @Override
+ public void installUI() {
+ if (LafWidgetRepository.getRepository().getLafSupport()
+ .toInstallExtraElements(this.jcomp)) {
+
+ PreviewPainter pPainter = LafWidgetUtilities2
+ .getComponentPreviewPainter(this.jcomp);
+ if (pPainter == null)
+ return;
+ this.scrollPaneSelector = new ScrollPaneSelector();
+ this.scrollPaneSelector.installOnScrollPane(this.jcomp);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallUI()
+ */
+ @Override
+ public void uninstallUI() {
+ if (this.scrollPaneSelector != null) {
+ this.scrollPaneSelector.uninstallFromScrollPane();
+ this.scrollPaneSelector = null;
+ }
+ }
+
+ @Override
+ public void installListeners() {
+ this.hierarchyListener = new HierarchyListener() {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.HierarchyListener#hierarchyChanged(java.awt.event
+ * .HierarchyEvent)
+ */
+ @Override
+ public void hierarchyChanged(HierarchyEvent e) {
+ if (jcomp.getParent() instanceof ComboPopup) {
+ if (scrollPaneSelector != null) {
+ scrollPaneSelector.uninstallFromScrollPane();
+ scrollPaneSelector = null;
+ }
+ }
+ }
+ };
+ this.jcomp.addHierarchyListener(this.hierarchyListener);
+
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (LafWidget.COMPONENT_PREVIEW_PAINTER.equals(evt
+ .getPropertyName())) {
+ PreviewPainter pPainter = LafWidgetUtilities2
+ .getComponentPreviewPainter(jcomp);
+ // Uninstall old scroll pane selector
+ if (scrollPaneSelector != null) {
+ scrollPaneSelector.uninstallFromScrollPane();
+ scrollPaneSelector = null;
+ }
+ // Install new scroll pane selector
+ if (pPainter != null
+ && LafWidgetRepository.getRepository()
+ .getLafSupport().toInstallExtraElements(
+ jcomp)) {
+ scrollPaneSelector = new ScrollPaneSelector();
+ scrollPaneSelector.installOnScrollPane(jcomp);
+ }
+ }
+ }
+ };
+ this.jcomp.addPropertyChangeListener(this.propertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ this.jcomp.removeHierarchyListener(this.hierarchyListener);
+ this.hierarchyListener = null;
+
+ this.jcomp.removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/TweakedScrollPaneLayout.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/TweakedScrollPaneLayout.java
new file mode 100644
index 0000000..accc208
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/scroll/TweakedScrollPaneLayout.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.scroll;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import java.awt.*;
+
+/**
+ * A hideous hack to allow the display of the selector's button even when only
+ * one scrollBar is visible.
+ * <p>
+ * Contributed by the original author under BSD license. Also appears in the <a
+ * href="https://jdnc-incubator.dev.java.net">JDNC Incubator</a>.
+ *
+ * @author weebib (Pierre LE LANNIC)
+ */
+public class TweakedScrollPaneLayout extends ScrollPaneLayout {
+ private void superlayoutContainer(Container parent) {
+ JScrollPane scrollPane = (JScrollPane) parent;
+ vsbPolicy = scrollPane.getVerticalScrollBarPolicy();
+ hsbPolicy = scrollPane.getHorizontalScrollBarPolicy();
+
+ Rectangle availR = scrollPane.getBounds();
+ Insets insets = parent.getInsets();
+ availR.x = insets.left;
+ availR.y = insets.top;
+ availR.width -= insets.left + insets.right;
+ availR.height -= insets.top + insets.bottom;
+
+ boolean leftToRight = scrollPane.getComponentOrientation()
+ .isLeftToRight();
+
+ Rectangle colHeadR = new Rectangle(0, availR.y, 0, 0);
+
+ if ((colHead != null) && (colHead.isVisible())) {
+ int colHeadHeight = Math.min(availR.height, colHead
+ .getPreferredSize().height);
+ colHeadR.height = colHeadHeight;
+ availR.y += colHeadHeight;
+ availR.height -= colHeadHeight;
+ }
+
+ Rectangle rowHeadR = new Rectangle(0, 0, 0, 0);
+
+ if ((rowHead != null) && (rowHead.isVisible())) {
+ int rowHeadWidth = Math.min(availR.width, rowHead
+ .getPreferredSize().width);
+ rowHeadR.width = rowHeadWidth;
+ availR.width -= rowHeadWidth;
+ if (leftToRight) {
+ rowHeadR.x = availR.x;
+ availR.x += rowHeadWidth;
+ } else {
+ rowHeadR.x = availR.x + availR.width;
+ }
+ }
+
+ Border viewportBorder = scrollPane.getViewportBorder();
+ Insets vpbInsets;
+ if (viewportBorder != null) {
+ vpbInsets = viewportBorder.getBorderInsets(parent);
+ availR.x += vpbInsets.left;
+ availR.y += vpbInsets.top;
+ availR.width -= vpbInsets.left + vpbInsets.right;
+ availR.height -= vpbInsets.top + vpbInsets.bottom;
+ } else {
+ vpbInsets = new Insets(0, 0, 0, 0);
+ }
+
+ Component view = (viewport != null) ? viewport.getView() : null;
+ Dimension viewPrefSize = (view != null) ? view.getPreferredSize()
+ : new Dimension(0, 0);
+
+ Dimension extentSize = (viewport != null) ? viewport
+ .toViewCoordinates(availR.getSize()) : new Dimension(0, 0);
+
+ boolean viewTracksViewportWidth = false;
+ boolean viewTracksViewportHeight = false;
+ boolean isEmpty = (availR.width < 0 || availR.height < 0);
+ Scrollable sv;
+ if (!isEmpty && view instanceof Scrollable) {
+ sv = (Scrollable) view;
+ viewTracksViewportWidth = sv.getScrollableTracksViewportWidth();
+ viewTracksViewportHeight = sv.getScrollableTracksViewportHeight();
+ } else {
+ sv = null;
+ }
+
+ Rectangle vsbR = new Rectangle(0, availR.y - vpbInsets.top, 0, 0);
+
+ boolean vsbNeeded;
+ if (isEmpty) {
+ vsbNeeded = false;
+ } else if (vsbPolicy == VERTICAL_SCROLLBAR_ALWAYS) {
+ vsbNeeded = true;
+ } else if (vsbPolicy == VERTICAL_SCROLLBAR_NEVER) {
+ vsbNeeded = false;
+ } else { // vsbPolicy == VERTICAL_SCROLLBAR_AS_NEEDED
+ vsbNeeded = !viewTracksViewportHeight
+ && (viewPrefSize.height > extentSize.height);
+ }
+
+ if ((vsb != null) && vsbNeeded) {
+ adjustForVSB(true, availR, vsbR, vpbInsets, leftToRight);
+ extentSize = viewport.toViewCoordinates(availR.getSize());
+ }
+
+ Rectangle hsbR = new Rectangle(availR.x - vpbInsets.left, 0, 0, 0);
+ boolean hsbNeeded;
+ if (isEmpty) {
+ hsbNeeded = false;
+ } else if (hsbPolicy == HORIZONTAL_SCROLLBAR_ALWAYS) {
+ hsbNeeded = true;
+ } else if (hsbPolicy == HORIZONTAL_SCROLLBAR_NEVER) {
+ hsbNeeded = false;
+ } else { // hsbPolicy == HORIZONTAL_SCROLLBAR_AS_NEEDED
+ hsbNeeded = !viewTracksViewportWidth
+ && (viewPrefSize.width > extentSize.width);
+ }
+ if ((hsb != null) && hsbNeeded) {
+ adjustForHSB(true, availR, hsbR, vpbInsets);
+ if ((vsb != null) && !vsbNeeded
+ && (vsbPolicy != VERTICAL_SCROLLBAR_NEVER)) {
+ extentSize = viewport.toViewCoordinates(availR.getSize());
+ vsbNeeded = viewPrefSize.height > extentSize.height;
+ if (vsbNeeded) {
+ adjustForVSB(true, availR, vsbR, vpbInsets, leftToRight);
+
+ // Hack to correct Bug 6411225
+ // I don't actually measure the consequences of this bug
+ // "fix"
+
+ if (!leftToRight) {
+ hsbR.x += vsbR.width;
+ }
+
+ // end of hack
+ }
+ }
+ }
+
+ if (viewport != null) {
+ viewport.setBounds(availR);
+
+ if (sv != null) {
+ extentSize = viewport.toViewCoordinates(availR.getSize());
+
+ boolean oldHSBNeeded = hsbNeeded;
+ boolean oldVSBNeeded = vsbNeeded;
+ viewTracksViewportWidth = sv.getScrollableTracksViewportWidth();
+ viewTracksViewportHeight = sv
+ .getScrollableTracksViewportHeight();
+ if (vsb != null && vsbPolicy == VERTICAL_SCROLLBAR_AS_NEEDED) {
+ boolean newVSBNeeded = !viewTracksViewportHeight
+ && (viewPrefSize.height > extentSize.height);
+ if (newVSBNeeded != vsbNeeded) {
+ vsbNeeded = newVSBNeeded;
+ adjustForVSB(vsbNeeded, availR, vsbR, vpbInsets,
+ leftToRight);
+ extentSize = viewport.toViewCoordinates(availR
+ .getSize());
+ }
+ }
+ if (hsb != null && hsbPolicy == HORIZONTAL_SCROLLBAR_AS_NEEDED) {
+ boolean newHSBbNeeded = !viewTracksViewportWidth
+ && (viewPrefSize.width > extentSize.width);
+ if (newHSBbNeeded != hsbNeeded) {
+ hsbNeeded = newHSBbNeeded;
+ adjustForHSB(hsbNeeded, availR, hsbR, vpbInsets);
+ if ((vsb != null) && !vsbNeeded
+ && (vsbPolicy != VERTICAL_SCROLLBAR_NEVER)) {
+ extentSize = viewport.toViewCoordinates(availR
+ .getSize());
+ vsbNeeded = viewPrefSize.height > extentSize.height;
+ if (vsbNeeded) {
+ adjustForVSB(true, availR, vsbR, vpbInsets,
+ leftToRight);
+ }
+ }
+ }
+ }
+ if (oldHSBNeeded != hsbNeeded || oldVSBNeeded != vsbNeeded) {
+ viewport.setBounds(availR);
+ }
+ }
+ }
+ vsbR.height = availR.height + vpbInsets.top + vpbInsets.bottom;
+ hsbR.width = availR.width + vpbInsets.left + vpbInsets.right;
+ rowHeadR.height = availR.height + vpbInsets.top + vpbInsets.bottom;
+ rowHeadR.y = availR.y - vpbInsets.top;
+ colHeadR.width = availR.width + vpbInsets.left + vpbInsets.right;
+ colHeadR.x = availR.x - vpbInsets.left;
+
+ if (rowHead != null) {
+ rowHead.setBounds(rowHeadR);
+ }
+
+ if (colHead != null) {
+ colHead.setBounds(colHeadR);
+ }
+
+ if (vsb != null) {
+ if (vsbNeeded) {
+ vsb.setVisible(true);
+ vsb.setBounds(vsbR);
+ } else {
+ vsb.setVisible(false);
+ }
+ }
+
+ if (hsb != null) {
+ if (hsbNeeded) {
+ hsb.setVisible(true);
+ hsb.setBounds(hsbR);
+ } else {
+ hsb.setVisible(false);
+ }
+ }
+
+ // bug 6411225 ????????
+
+ if (lowerLeft != null) {
+ lowerLeft.setBounds(leftToRight ? rowHeadR.x : vsbR.x, hsbR.y,
+ leftToRight ? rowHeadR.width : vsbR.width, hsbR.height);
+ }
+
+ if (lowerRight != null) {
+ lowerRight.setBounds(leftToRight ? vsbR.x : rowHeadR.x, hsbR.y,
+ leftToRight ? vsbR.width : rowHeadR.width, hsbR.height);
+ }
+
+ if (upperLeft != null) {
+ upperLeft.setBounds(leftToRight ? rowHeadR.x : vsbR.x, colHeadR.y,
+ leftToRight ? rowHeadR.width : vsbR.width, colHeadR.height);
+ }
+
+ if (upperRight != null) {
+ upperRight.setBounds(leftToRight ? vsbR.x : rowHeadR.x, colHeadR.y,
+ leftToRight ? vsbR.width : rowHeadR.width, colHeadR.height);
+ }
+ }
+
+ private void adjustForVSB(boolean wantsVSB, Rectangle available,
+ Rectangle vsbR, Insets vpbInsets, boolean leftToRight) {
+ int oldWidth = vsbR.width;
+ if (wantsVSB) {
+ int vsbWidth = Math.max(0, Math.min(vsb.getPreferredSize().width,
+ available.width));
+
+ available.width -= vsbWidth;
+ vsbR.width = vsbWidth;
+
+ if (leftToRight) {
+ vsbR.x = available.x + available.width + vpbInsets.right;
+ } else {
+ vsbR.x = available.x - vpbInsets.left;
+ available.x += vsbWidth;
+ }
+ } else {
+ available.width += oldWidth;
+ }
+ }
+
+ private void adjustForHSB(boolean wantsHSB, Rectangle available,
+ Rectangle hsbR, Insets vpbInsets) {
+ int oldHeight = hsbR.height;
+ if (wantsHSB) {
+ int hsbHeight = Math.max(0, Math.min(available.height, hsb
+ .getPreferredSize().height));
+
+ available.height -= hsbHeight;
+ hsbR.y = available.y + available.height + vpbInsets.bottom;
+ hsbR.height = hsbHeight;
+ } else {
+ available.height += oldHeight;
+ }
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ superlayoutContainer(parent);
+ boolean isLeftToRight = parent.getComponentOrientation()
+ .isLeftToRight();
+ if (isLeftToRight && lowerRight == null)
+ return;
+ if (!isLeftToRight && lowerLeft == null)
+ return;
+
+ boolean onlyVsb = (vsb != null) && vsb.isVisible()
+ && ((hsb == null) || ((hsb != null) && (!hsb.isVisible())));
+ if (onlyVsb) {
+ Rectangle vsbBounds = vsb.getBounds();
+ int delta = (hsb == null) ? (Integer) (UIManager
+ .get("ScrollBar.width")) : hsb
+ .getPreferredSize().height;
+ vsbBounds.height -= delta;
+ vsb.setBounds(vsbBounds);
+ if (isLeftToRight) {
+ lowerRight.setBounds(vsbBounds.x, vsbBounds.y
+ + vsbBounds.height, vsbBounds.width, delta);
+ lowerRight.setVisible(true);
+ } else {
+ lowerLeft.setBounds(vsbBounds.x,
+ vsbBounds.y + vsbBounds.height, vsbBounds.width, delta);
+ lowerLeft.setVisible(true);
+ }
+ }
+ boolean onlyHsb = (hsb != null) && hsb.isVisible()
+ && ((vsb == null) || ((vsb != null) && (!vsb.isVisible())));
+ if (onlyHsb) {
+ Rectangle hsbBounds = hsb.getBounds();
+ int delta = (vsb == null) ? (Integer) (UIManager
+ .get("ScrollBar.width")) : vsb
+ .getPreferredSize().width;
+ hsbBounds.width -= delta;
+ if (!isLeftToRight) {
+ hsbBounds.x += delta;
+ }
+ hsb.setBounds(hsbBounds);
+ if (isLeftToRight) {
+ lowerRight.setBounds(hsbBounds.x + hsbBounds.width,
+ hsbBounds.y, delta, hsbBounds.height);
+ lowerRight.setVisible(true);
+ } else {
+ lowerLeft.setBounds(hsbBounds.x - delta, hsbBounds.y, delta,
+ hsbBounds.height);
+ lowerLeft.setVisible(true);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/DefaultTabPreviewPainter.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/DefaultTabPreviewPainter.java
new file mode 100644
index 0000000..7bde104
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/DefaultTabPreviewPainter.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tabbed;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+import javax.swing.JTabbedPane;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+
+/**
+ * Default implementation of the tab preview painter. The tab preview is a
+ * scaled-down (as necessary) thumbnail of the relevant tab.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DefaultTabPreviewPainter extends TabPreviewPainter {
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.tabbed.TabPreviewPainter#hasPreview(javax.swing.JTabbedPane,
+ * int)
+ */
+ @Override
+ public boolean hasPreview(JTabbedPane tabPane, int tabIndex) {
+ return (tabPane.getComponentAt(tabIndex) != null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.tabbed.TabPreviewPainter#isSensitiveToEvents(javax.swing.JTabbedPane,
+ * int)
+ */
+ @Override
+ public boolean isSensitiveToEvents(JTabbedPane tabPane, int tabIndex) {
+ return tabPane.isEnabledAt(tabIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.tabbed.TabPreviewPainter#previewTab(javax.swing.JTabbedPane,
+ * int, java.awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void previewTab(JTabbedPane tabPane, int tabIndex, Graphics g,
+ int x, int y, int w, int h) {
+ Component tabComponent = tabPane.getComponentAt(tabIndex);
+ if (tabComponent == null)
+ return;
+ // if (!tabComponent.isShowing())
+ // return;
+ int compWidth = tabComponent.getWidth();
+ int compHeight = tabComponent.getHeight();
+
+ if ((compWidth > 0) && (compHeight > 0)) {
+ // draw tab component
+ BufferedImage tempCanvas = new BufferedImage(compWidth, compHeight,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics tempCanvasGraphics = tempCanvas.getGraphics();
+ tabComponent.paint(tempCanvasGraphics);
+
+ // check if need to scale down
+ double coef = Math.min((double) w / (double) compWidth, (double) h
+ / (double) compHeight);
+ // fix for issue 177 in Substance - disabled tabs painted in
+ // 50% opacity.
+ Graphics2D g2 = (Graphics2D) g.create();
+ if (!tabPane.isEnabledAt(tabIndex)) {
+ g2.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, 0.5f));
+ }
+ if (coef < 1.0) {
+ int sdWidth = (int) (coef * compWidth);
+ int sdHeight = (int) (coef * compHeight);
+ int dx = (w - sdWidth) / 2;
+ int dy = (h - sdHeight) / 2;
+
+ g2.drawImage(LafWidgetUtilities.createThumbnail(tempCanvas,
+ sdWidth), dx, dy, null);
+
+ } else {
+ // System.out.println("Putting " + frame.hashCode() + "
+ // -> " + snapshot.hashCode());
+ g2.drawImage(tempCanvas, 0, 0, null);
+ }
+ g2.dispose();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.tabbed.TabPreviewPainter#hasPreviewWindow(javax.swing.JTabbedPane,
+ * int)
+ */
+ @Override
+ public boolean hasPreviewWindow(JTabbedPane tabPane, int tabIndex) {
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.tabbed.TabPreviewPainter#hasOverviewDialog(javax.swing.JTabbedPane)
+ */
+ @Override
+ public boolean hasOverviewDialog(JTabbedPane tabPane) {
+ return true;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabHoverPreviewWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabHoverPreviewWidget.java
new file mode 100644
index 0000000..d2027f7
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabHoverPreviewWidget.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tabbed;
+
+import java.awt.event.*;
+
+import javax.swing.JTabbedPane;
+
+import org.pushingpixels.lafwidget.*;
+
+/**
+ * Adds tab preview thumbnail on tab mouse hover.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TabHoverPreviewWidget extends LafWidgetAdapter<JTabbedPane> {
+ /**
+ * Mouse listener for rollover effects.
+ */
+ protected MouseRolloverHandler baseRolloverHandler;
+
+ /**
+ * Mouse listener for rollover effects.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class MouseRolloverHandler implements MouseListener,
+ MouseMotionListener {
+ /**
+ * Index of previously rolled-over tab.
+ */
+ int prevRolledOver = -1;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mousePressed(MouseEvent e) {
+ TabPreviewWindow.cancelPreviewRequest();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ if (e.getSource() != jcomp)
+ return;
+
+ LafWidgetSupport lafSupport = LafWidgetRepository.getRepository()
+ .getLafSupport();
+
+ int currRolledOver = 0;
+
+ try {
+ currRolledOver = lafSupport.getRolloverTabIndex(jcomp);
+ } catch (UnsupportedOperationException uoe) {
+ // Some problems in LAF support - may happen during the LAF
+ // switch when this event is processed after a LAF that doesn't
+ // have the laf-widget support is set. Just ignore it.
+ return;
+ }
+ if (currRolledOver >= jcomp.getTabCount())
+ return;
+
+ if (currRolledOver != jcomp.getSelectedIndex()) {
+ if (currRolledOver == this.prevRolledOver) {
+ if ((currRolledOver >= 0)
+ && (currRolledOver < jcomp.getTabCount())
+ && jcomp.isEnabledAt(currRolledOver)) {
+ TabPreviewWindow.getInstance().postPreviewRequest(
+ jcomp, currRolledOver);
+ }
+ } else {
+ if ((this.prevRolledOver >= 0)
+ && (this.prevRolledOver < jcomp.getTabCount())
+ && jcomp.isEnabledAt(this.prevRolledOver)) {
+ TabPreviewWindow.cancelPreviewRequest();
+ }
+ if ((currRolledOver >= 0)
+ && (currRolledOver < jcomp.getTabCount())
+ && jcomp.isEnabledAt(currRolledOver)) {
+ TabPreviewWindow.getInstance().postPreviewRequest(
+ jcomp, currRolledOver);
+ }
+ }
+ } else {
+ // no preview on the selected tab
+ TabPreviewWindow.cancelPreviewRequest();
+ }
+ this.prevRolledOver = currRolledOver;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if ((this.prevRolledOver >= 0)
+ && (this.prevRolledOver < jcomp.getTabCount())
+ && jcomp.isEnabledAt(this.prevRolledOver)) {
+ TabPreviewWindow.cancelPreviewRequest();
+ }
+ this.prevRolledOver = -1;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ this.baseRolloverHandler = new MouseRolloverHandler();
+ this.jcomp.addMouseMotionListener(this.baseRolloverHandler);
+ this.jcomp.addMouseListener(this.baseRolloverHandler);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ if (this.baseRolloverHandler != null) {
+ this.jcomp.removeMouseMotionListener(this.baseRolloverHandler);
+ this.jcomp.removeMouseListener(this.baseRolloverHandler);
+ this.baseRolloverHandler = null;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallUI()
+ */
+ @Override
+ public void uninstallUI() {
+ if (TabPreviewThread.instanceRunning()) {
+ TabPreviewThread.getInstance().cancelTabPreviewRequests(this.jcomp);
+ }
+ TabPreviewWindow.cancelPreviewRequest();
+
+ super.uninstallUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabOverviewButton.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabOverviewButton.java
new file mode 100644
index 0000000..2bff75c
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabOverviewButton.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tabbed;
+
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.*;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.lafwidget.*;
+
+/**
+ * Button that activates the tab overview dialog.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TabOverviewButton extends JButton implements UIResource {
+ /**
+ * Client property name for locking undesired bound set.
+ */
+ private static final String OWN_BOUNDS = "lafwidget.ownBounds";
+
+ /**
+ * Creates a new tab overview button.
+ *
+ * @param tabPane
+ * The owner tabbed pane.
+ */
+ public TabOverviewButton(final JTabbedPane tabPane) {
+ this.setFocusable(false);
+ LafWidgetSupport support = LafWidgetRepository.getRepository()
+ .getLafSupport();
+
+ if (support != null) {
+ Icon searchIcon = support.getSearchIcon(LafWidgetRepository
+ .getRepository().getLafSupport().getLookupIconSize(),
+ tabPane.getComponentOrientation());
+ this.setIcon(searchIcon);
+ support.markButtonAsFlat(this);
+ }
+ this.setToolTipText(LafWidgetUtilities.getResourceBundle(tabPane).getString(
+ "TabbedPane.overviewButtonTooltip"));
+
+ this.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ TabOverviewDialog.getOverviewDialog(tabPane).setVisible(true);
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Component#setBounds(int, int, int, int)
+ */
+ @Override
+ public void setBounds(int x, int y, int width, int height) {
+ if (Boolean.TRUE.equals(this
+ .getClientProperty(TabOverviewButton.OWN_BOUNDS)))
+ super.setBounds(x, y, width, height);
+ }
+
+ /**
+ * Updates the location of <code>this</code> tab overview button.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabAreaInsets
+ * Tab area insets.
+ */
+ public void updateLocation(JTabbedPane tabbedPane, Insets tabAreaInsets) {
+ if (tabbedPane == null)
+ return;
+
+ // Lock the button for the bounds change
+ this.putClientProperty(TabOverviewButton.OWN_BOUNDS, Boolean.TRUE);
+ int buttonSize = LafWidgetRepository.getRepository().getLafSupport()
+ .getLookupButtonSize();
+
+ switch (tabbedPane.getTabPlacement()) {
+ case SwingConstants.TOP:
+ if (tabbedPane.getComponentOrientation().isLeftToRight())
+ this.setBounds(2, tabAreaInsets.top, buttonSize, buttonSize);
+ else
+ this.setBounds(tabbedPane.getBounds().width
+ - tabAreaInsets.right - buttonSize - 2,
+ tabAreaInsets.top, buttonSize, buttonSize);
+ break;
+ case SwingConstants.BOTTOM:
+ if (tabbedPane.getComponentOrientation().isLeftToRight())
+ this.setBounds(2, tabbedPane.getBounds().height
+ - tabAreaInsets.bottom - buttonSize - 4, buttonSize,
+ buttonSize);
+ else
+ this.setBounds(tabbedPane.getBounds().width
+ - tabAreaInsets.right - buttonSize - 2, tabbedPane
+ .getBounds().height
+ - tabAreaInsets.bottom - buttonSize - 4, buttonSize,
+ buttonSize);
+ break;
+ case SwingConstants.LEFT:
+ this.setBounds(2, tabAreaInsets.top - 1, buttonSize, buttonSize);
+ break;
+ case SwingConstants.RIGHT:
+ this.setBounds(tabbedPane.getBounds().width - tabAreaInsets.right
+ - buttonSize - 2, tabAreaInsets.top - 1, buttonSize,
+ buttonSize);
+ break;
+ }
+ // Unlock the button for the bounds change
+ this.putClientProperty(TabOverviewButton.OWN_BOUNDS, null);
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabOverviewDialog.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabOverviewDialog.java
new file mode 100644
index 0000000..edb09db
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabOverviewDialog.java
@@ -0,0 +1,1252 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tabbed;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.text.MessageFormat;
+
+import javax.swing.*;
+import javax.swing.border.*;
+
+import org.pushingpixels.lafwidget.*;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.contrib.blogofbug.swing.components.*;
+import org.pushingpixels.lafwidget.tabbed.TabPreviewThread.TabPreviewInfo;
+import org.pushingpixels.lafwidget.utils.ShadowPopupBorder;
+import org.pushingpixels.lafwidget.utils.LafConstants.TabOverviewKind;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
+import org.pushingpixels.trident.swing.SwingRepaintCallback;
+
+/**
+ * Tab overview dialog.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TabOverviewDialog extends JDialog {
+ /**
+ * The associated tabbed pane.
+ */
+ protected JTabbedPane tabPane;
+
+ // /**
+ // * The grid overview panel (with all thumbnails).
+ // */
+ // protected JPanel gridOverviewPanel;
+
+ /**
+ * The associated preview callback.
+ */
+ protected TabPreviewThread.TabPreviewCallback previewCallback;
+
+ /**
+ * Listener on LAF switches.
+ */
+ protected PropertyChangeListener lafSwitchListener;
+
+ /**
+ * Handles mouse events on the tab overview dialog (such as highlighting the
+ * currently rolled-over tab preview, closing the overview when a tab
+ * preview is clicked, tooltips etc.)
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class TabPreviewMouseHandler extends MouseAdapter {
+ /**
+ * Tab index.
+ */
+ private int index;
+
+ /**
+ * Tab preview control.
+ */
+ private JComponent previewControl;
+
+ /**
+ * If <code>true</code>, the preview uses double click to select the tab
+ * and dismiss the tab overview dialog.
+ */
+ private boolean useDoubleClick;
+
+ /**
+ * If <code>true</code>, the tab preview controls have rollover effects
+ * on borders.
+ */
+ private boolean hasRolloverBorderEffect;
+
+ /**
+ * Creates the mouse handler for a single tab preview control.
+ *
+ * @param index
+ * Tab index.
+ * @param previewControl
+ * Tab preview control.
+ * @param hasRolloverBorderEffect
+ * If <code>true</code>, the preview uses double click to
+ * select the tab and dismiss the tab overview dialog.
+ * @param useDoubleClick
+ * If <code>true</code>, the tab preview controls have
+ * rollover effects on borders.
+ */
+ public TabPreviewMouseHandler(int index, JComponent previewControl,
+ boolean hasRolloverBorderEffect, boolean useDoubleClick) {
+ this.index = index;
+ this.previewControl = previewControl;
+ this.useDoubleClick = useDoubleClick;
+ this.hasRolloverBorderEffect = hasRolloverBorderEffect;
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (this.useDoubleClick) {
+ if (e.getClickCount() < 2)
+ return;
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ TabOverviewDialog.this.dispose();
+ TabOverviewDialog.this.tabPane
+ .setSelectedIndex(TabPreviewMouseHandler.this.index);
+ }
+ });
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (!this.hasRolloverBorderEffect)
+ return;
+ boolean isSelected = (TabOverviewDialog.this.tabPane
+ .getSelectedIndex() == this.index);
+ Border innerBorder = isSelected ? new LineBorder(Color.blue, 2)
+ : new LineBorder(Color.black, 1);
+ this.previewControl.setBorder(new CompoundBorder(
+ new ShadowPopupBorder(), innerBorder));
+ // if (isSelected)
+ // this.previewControl.setBorder(new LineBorder(Color.blue, 2));
+ // else
+ // this.previewControl.setBorder(new CompoundBorder(
+ // new EmptyBorder(1, 1, 1, 1), new LineBorder(
+ // Color.black, 1)));
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (!this.hasRolloverBorderEffect)
+ return;
+ boolean isSelected = (TabOverviewDialog.this.tabPane
+ .getSelectedIndex() == this.index);
+ Border innerBorder = isSelected ? new LineBorder(Color.black, 2)
+ : new LineBorder(Color.black, 1);
+ this.previewControl.setBorder(new CompoundBorder(
+ new ShadowPopupBorder(), innerBorder));
+ // if (isSelected)
+ // this.previewControl.setBorder(new LineBorder(Color.black, 2));
+ // else
+ // this.previewControl.setBorder(new CompoundBorder(
+ // new EmptyBorder(1, 1, 1, 1), new LineBorder(
+ // Color.black, 1)));
+ }
+ }
+
+ /**
+ * Tab round carousel overview panel. Contains a round carousel of tab
+ * preview widgets. The widgets are created in a separate thread (
+ * {@link TabPreviewThread}) and offered to the tab overview dialog via the
+ * registered implementation of {@link TabPreviewThread.TabPreviewCallback}.
+ * This way the application stays interactive while the tab overview dialog
+ * is being populated.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class TabRoundCarouselOverviewPanel extends JPanel {
+ /**
+ * Tab preview controls.
+ */
+ protected ReflectedImageLabel[] previewControls;
+
+ /**
+ * Width of a single tab preview control.
+ */
+ protected int pWidth;
+
+ /**
+ * Height of a single tab preview control.
+ */
+ protected int pHeight;
+
+ /**
+ * Associated carousel.
+ */
+ protected JCarosel carosel;
+
+ /**
+ * Creates a tab overview panel.
+ *
+ * @param dialogWidth
+ * The width of the parent dialog.
+ * @param dialogHeight
+ * The height of the parent dialog.
+ */
+ public TabRoundCarouselOverviewPanel(final int dialogWidth,
+ final int dialogHeight) {
+ // int tabCount = TabOverviewDialog.this.tabPane.getTabCount();
+
+ // this.previewControls = new HashSet<Component>();
+ TabPreviewThread.TabPreviewCallback previewCallback = new TabPreviewThread.TabPreviewCallback() {
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
+ * TabPreviewCallback#start(javax.swing.JTabbedPane, int,
+ * org.pushingpixels
+ * .lafwidget.tabbed.TabPreviewThread.TabPreviewInfo)
+ */
+ @Override
+ public void start(JTabbedPane tabPane, int tabCount,
+ TabPreviewInfo tabPreviewInfo) {
+ // Check if need to reallocate the preview controls.
+ boolean isSame = (previewControls != null)
+ && (previewControls.length == tabCount);
+ if (isSame)
+ return;
+
+ if (previewControls != null) {
+ for (int i = 0; i < previewControls.length; i++) {
+ carosel.remove(previewControls[i]);
+ }
+ }
+
+ double coef = Math.min(3.5, tabCount / 1.5);
+ coef = Math.max(coef, 4.5);
+ pWidth = (int) (dialogWidth / coef);
+ pHeight = (int) (dialogHeight / coef);
+
+ tabPreviewInfo.setPreviewWidth(pWidth - 4);
+ tabPreviewInfo.setPreviewHeight(pHeight - 4);
+
+ previewControls = new ReflectedImageLabel[tabCount];
+ TabPreviewPainter tpp = LafWidgetUtilities2
+ .getTabPreviewPainter(TabOverviewDialog.this.tabPane);
+ for (int i = 0; i < tabCount; i++) {
+ BufferedImage placeHolder = new BufferedImage(pWidth,
+ pHeight, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = (Graphics2D) placeHolder.getGraphics();
+ g2d.setColor(UIManager.getColor("Label.background"));
+ g2d.fillRect(0, 0, pWidth, pHeight);
+ ReflectedImageLabel ril = (ReflectedImageLabel) carosel
+ .add(placeHolder, tabPane.getTitleAt(i));
+ ril.setForeground(UIManager
+ .getColor("Label.foreground"));
+ ril.setBackground(UIManager
+ .getColor("Label.background"));
+ // TabPreviewControl previewControl = new
+ // TabPreviewControl(
+ // TabOverviewDialog.this.tabPane, i);
+ ril.setPreferredSize(new Dimension(pWidth, pHeight));
+ // fix for issue 177 in Substance (disallowing
+ // selection
+ // of disabled tabs).
+ if (tpp.isSensitiveToEvents(
+ TabOverviewDialog.this.tabPane, i)) {
+ ril.addMouseListener(new TabPreviewMouseHandler(i,
+ ril, false, true));
+ ril
+ .setToolTipText(LafWidgetUtilities
+ .getResourceBundle(tabPane)
+ .getString(
+ "TabbedPane.overviewWidgetTooltip"));
+ }
+ previewControls[i] = ril;
+ // carosel.add(previewControl);
+ }
+ carosel.bringToFront(previewControls[tabPane
+ .getSelectedIndex()]);
+ // System.err.println("Added " + previewControls.length
+ // + " labels");
+ // doLayout();
+ // for (int i = 0; i < tabCount; i++) {
+ // previewControls[i].revalidate();
+ // }
+ // repaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
+ * TabPreviewCallback#offer(javax.swing.JTabbedPane, int,
+ * java.awt.image.BufferedImage)
+ */
+ @Override
+ public void offer(JTabbedPane tabPane, int tabIndex,
+ BufferedImage componentSnap) {
+ int width = componentSnap.getWidth() + 4;
+ int height = componentSnap.getHeight() + 4;
+ BufferedImage result = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = (Graphics2D) result.getGraphics();
+ g2d.setColor(UIManager.getColor("Label.background"));
+ g2d.fillRect(0, 0, width, height);
+ g2d.setColor(UIManager.getColor("Label.foreground"));
+ g2d.drawRect(0, 0, width - 1, height - 1);
+ g2d.drawImage(componentSnap, 2, 2, null);
+
+ Icon tabIcon = tabPane.getIconAt(tabIndex);
+ if (tabIcon != null) {
+ tabIcon.paintIcon(tabPane, g2d, 2, 2);
+ }
+
+ // Component caroselComponent = carosel.add(result, tabPane
+ // .getTitleAt(tabIndex));
+ // caroselComponent.setForeground(UIManager
+ // .getColor("Label.foreground"));
+
+ // System.err.println("Setting image on " + tabIndex);
+ previewControls[tabIndex].setRichImage(result);
+ // System.err.println("Set image on " + tabIndex);
+ previewControls[tabIndex].repaint();
+ // previewControls.add(caroselComponent);
+ // TabRoundCarouselOverviewPanel.this.previewControls[tabIndex]
+ // .setPreviewImage(componentSnap);
+ }
+ };
+
+ this.carosel = new JCarosel();
+ this.carosel.setDepthBasedAlpha(true);
+ // // carosel.setBackground(Color.BLACK, Color.DARK_GRAY);
+ // carosel
+ // .add(
+ // TabOverviewDialog.class
+ // .getResource(
+ // "/contrib/com/blogofbug/examples/images/Acknowledgements.png")
+ // .toString(), "You Rock", 128, 128);
+ // carosel.add(TabOverviewDialog.class.getResource(
+ // "/contrib/com/blogofbug/examples/images/Dock.png")
+ // .toString(), "Docks Rock", 128, 128);
+ // carosel.add(TabOverviewDialog.class.getResource(
+ // "/contrib/com/blogofbug/examples/images/Cascade.png")
+ // .toString(), "Cascade Icon", 128, 128);
+ // carosel.add(TabOverviewDialog.class.getResource(
+ // "/contrib/com/blogofbug/examples/images/Quit.png")
+ // .toString(), "Quit Bugging", 128, 128);
+ this.setLayout(new BorderLayout());
+ this.add(carosel, BorderLayout.CENTER);
+
+ TabPreviewInfo previewInfo = new TabPreviewInfo();
+ previewInfo.tabPane = TabOverviewDialog.this.tabPane;
+ previewInfo.previewCallback = previewCallback;
+ // previewInfo.previewWidth = this.pWidth - 4;
+ // previewInfo.previewHeight = this.pHeight - 20;
+ previewInfo.toPreviewAllTabs = true;
+ previewInfo.initiator = TabOverviewDialog.this;
+
+ TabPreviewThread.getInstance().queueTabPreviewRequest(previewInfo);
+ }
+ }
+
+ /**
+ * Tab menu carousel overview panel. Contains a menu carousel of tab preview
+ * widgets. The widgets are created in a separate thread (
+ * {@link TabPreviewThread}) and offered to the tab overview dialog via the
+ * registered implementation of {@link TabPreviewThread.TabPreviewCallback}.
+ * This way the application stays interactive while the tab overview dialog
+ * is being populated.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class TabMenuCarouselOverviewPanel extends JPanel {
+ /**
+ * Tab preview controls.
+ */
+ protected ReflectedImageLabel[] previewControls;
+
+ /**
+ * Width of a single tab preview control.
+ */
+ protected int pWidth;
+
+ /**
+ * Height of a single tab preview control.
+ */
+ protected int pHeight;
+
+ /**
+ * The associated carousel menu.
+ */
+ protected JCarouselMenu caroselMenu;
+
+ /**
+ * Cell renderer for the carosel menu. Employs a little trick to provide
+ * LAF-consistent painting of the cells.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class MenuCarouselListCellRenderer extends JLabel implements
+ ListCellRenderer {
+ /**
+ * The cell renderer from the currently installed LAF.
+ */
+ protected ListCellRenderer lafDefaultCellRenderer;
+
+ /**
+ * Creates the cell renderer for the carosel menu.
+ *
+ * @param lafDefaultCellRenderer
+ * The cell renderer from the currently installed LAF.
+ */
+ public MenuCarouselListCellRenderer(
+ ListCellRenderer lafDefaultCellRenderer) {
+ this.lafDefaultCellRenderer = lafDefaultCellRenderer;
+ // if (lafDefaultCellRenderer instanceof Component) {
+ // JComponent jc = (JComponent) lafDefaultCellRenderer;
+ // jc.setBorder(new EmptyBorder(5, 5, 5, 5));
+ // jc.setFont(super.getFont().deriveFont(Font.BOLD, 14.0f));
+ // }
+ }
+
+ /**
+ * Sets up the component for stamping
+ */
+ @Override
+ public Component getListCellRendererComponent(JList jList,
+ Object object, int i, boolean isSelected,
+ boolean cellHasFocus) {
+ JCarouselMenu.MenuItem item = (JCarouselMenu.MenuItem) object;
+ Component result = this.lafDefaultCellRenderer
+ .getListCellRendererComponent(jList, item.getLabel(),
+ i, isSelected, cellHasFocus);
+
+ if (result instanceof Component) {
+ JComponent jc = (JComponent) result;
+ jc.setBorder(new EmptyBorder(5, 5, 5, 5));
+ jc.setFont(super.getFont().deriveFont(Font.BOLD, 14.0f));
+ }
+
+ return result;
+ }
+ }
+
+ /**
+ * Creates a tab overview panel.
+ *
+ * @param dialogWidth
+ * The width of the parent dialog.
+ * @param dialogHeight
+ * The height of the parent dialog.
+ */
+ public TabMenuCarouselOverviewPanel(final int dialogWidth,
+ final int dialogHeight) {
+ // int tabCount = TabOverviewDialog.this.tabPane.getTabCount();
+
+ // this.previewControls = new HashSet<Component>();
+ TabPreviewThread.TabPreviewCallback previewCallback = new TabPreviewThread.TabPreviewCallback() {
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
+ * TabPreviewCallback#start(javax.swing.JTabbedPane, int,
+ * org.pushingpixels
+ * .lafwidget.tabbed.TabPreviewThread.TabPreviewInfo)
+ */
+ @Override
+ public void start(JTabbedPane tabPane, int tabCount,
+ TabPreviewInfo tabPreviewInfo) {
+ // Check if need to reallocate the preview controls.
+ boolean isSame = (previewControls != null)
+ && (previewControls.length == tabCount);
+ if (isSame)
+ return;
+
+ if (previewControls != null) {
+ for (int i = 0; i < previewControls.length; i++) {
+ caroselMenu.remove(previewControls[i]);
+ }
+ }
+
+ double coef = Math.min(2.8, tabCount / 1.8);
+ coef = Math.max(2.5, coef);
+ pWidth = (int) (dialogWidth / coef);
+ pHeight = (int) (dialogHeight / coef);
+
+ tabPreviewInfo.setPreviewWidth(pWidth - 4);
+ tabPreviewInfo.setPreviewHeight(pHeight - 4);
+
+ previewControls = new ReflectedImageLabel[tabCount];
+ TabPreviewPainter tpp = LafWidgetUtilities2
+ .getTabPreviewPainter(TabOverviewDialog.this.tabPane);
+ for (int i = 0; i < tabCount; i++) {
+ BufferedImage placeHolder = new BufferedImage(pWidth,
+ pHeight, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = (Graphics2D) placeHolder.getGraphics();
+ g2d.setColor(UIManager.getColor("Label.background"));
+ g2d.fillRect(0, 0, pWidth, pHeight);
+ ReflectedImageLabel ril = (ReflectedImageLabel) caroselMenu
+ .add(placeHolder, tabPane.getTitleAt(i));
+ ril.setForeground(UIManager
+ .getColor("Label.foreground"));
+ ril.setBackground(UIManager
+ .getColor("Label.background"));
+ // TabPreviewControl previewControl = new
+ // TabPreviewControl(
+ // TabOverviewDialog.this.tabPane, i);
+ ril.setPreferredSize(new Dimension(pWidth, pHeight));
+ // fix for issue 177 in Substance (disallowing
+ // selection
+ // of disabled tabs).
+ if (tpp.isSensitiveToEvents(
+ TabOverviewDialog.this.tabPane, i)) {
+ ril.addMouseListener(new TabPreviewMouseHandler(i,
+ ril, false, true));
+ ril
+ .setToolTipText(LafWidgetUtilities
+ .getResourceBundle(tabPane)
+ .getString(
+ "TabbedPane.overviewWidgetTooltip"));
+ }
+ previewControls[i] = ril;
+ // carosel.add(previewControl);
+ }
+ caroselMenu.setSelectedIndex(tabPane.getSelectedIndex());
+ // System.err.println("Added " + previewControls.length
+ // + " labels");
+ // doLayout();
+ // for (int i = 0; i < tabCount; i++) {
+ // previewControls[i].revalidate();
+ // }
+ // repaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
+ * TabPreviewCallback#offer(javax.swing.JTabbedPane, int,
+ * java.awt.image.BufferedImage)
+ */
+ @Override
+ public void offer(JTabbedPane tabPane, int tabIndex,
+ BufferedImage componentSnap) {
+ int width = componentSnap.getWidth() + 4;
+ int height = componentSnap.getHeight() + 4;
+ BufferedImage result = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = (Graphics2D) result.getGraphics();
+ g2d.setColor(UIManager.getColor("Label.background"));
+ g2d.fillRect(0, 0, width, height);
+ g2d.setColor(UIManager.getColor("Label.foreground"));
+ g2d.drawRect(0, 0, width - 1, height - 1);
+ g2d.drawImage(componentSnap, 2, 2, null);
+
+ Icon tabIcon = tabPane.getIconAt(tabIndex);
+ if (tabIcon != null) {
+ tabIcon.paintIcon(tabPane, g2d, 2, 2);
+ }
+ // Component caroselComponent = carosel.add(result, tabPane
+ // .getTitleAt(tabIndex));
+ // caroselComponent.setForeground(UIManager
+ // .getColor("Label.foreground"));
+
+ // System.err.println("Setting image on " + tabIndex);
+ previewControls[tabIndex].setRichImage(result);
+ // System.err.println("Set image on " + tabIndex);
+ previewControls[tabIndex].repaint();
+ // previewControls.add(caroselComponent);
+ // TabRoundCarouselOverviewPanel.this.previewControls[tabIndex]
+ // .setPreviewImage(componentSnap);
+ }
+ };
+
+ this.caroselMenu = new JCarouselMenu(null);
+ JList dummyList = new JList();
+ ListCellRenderer lcr = dummyList.getCellRenderer();
+ this.caroselMenu.setCellRenderer(new MenuCarouselListCellRenderer(
+ lcr));
+ this.caroselMenu.setMenuScrollColor(UIManager
+ .getColor("Panel.background"));
+ this.caroselMenu.setUpDownColor(UIManager
+ .getColor("Label.foreground"));
+ LafWidgetSupport support = LafWidgetRepository.getRepository()
+ .getLafSupport();
+ if (support != null) {
+ this.caroselMenu.setUpDownIcons(support
+ .getArrowIcon(SwingConstants.NORTH), support
+ .getArrowIcon(SwingConstants.SOUTH));
+ }
+
+ // this.carosel.setDepthBasedAlpha(true);
+ // // carosel.setBackground(Color.BLACK, Color.DARK_GRAY);
+ // carosel
+ // .add(
+ // TabOverviewDialog.class
+ // .getResource(
+ // "/contrib/com/blogofbug/examples/images/Acknowledgements.png")
+ // .toString(), "You Rock", 128, 128);
+ // carosel.add(TabOverviewDialog.class.getResource(
+ // "/contrib/com/blogofbug/examples/images/Dock.png")
+ // .toString(), "Docks Rock", 128, 128);
+ // carosel.add(TabOverviewDialog.class.getResource(
+ // "/contrib/com/blogofbug/examples/images/Cascade.png")
+ // .toString(), "Cascade Icon", 128, 128);
+ // carosel.add(TabOverviewDialog.class.getResource(
+ // "/contrib/com/blogofbug/examples/images/Quit.png")
+ // .toString(), "Quit Bugging", 128, 128);
+ this.setLayout(new BorderLayout());
+ this.add(caroselMenu, BorderLayout.CENTER);
+
+ TabPreviewInfo previewInfo = new TabPreviewInfo();
+ previewInfo.tabPane = TabOverviewDialog.this.tabPane;
+ previewInfo.previewCallback = previewCallback;
+ previewInfo.setPreviewWidth(this.pWidth - 4);
+ previewInfo.setPreviewHeight(this.pHeight - 4);
+ previewInfo.toPreviewAllTabs = true;
+ previewInfo.initiator = TabOverviewDialog.this;
+
+ TabPreviewThread.getInstance().queueTabPreviewRequest(previewInfo);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JPanel#updateUI()
+ */
+ @Override
+ public void updateUI() {
+ super.updateUI();
+ if (this.caroselMenu != null) {
+ JList dummyList = new JList();
+ ListCellRenderer lcr = dummyList.getCellRenderer();
+ this.caroselMenu
+ .setCellRenderer(new MenuCarouselListCellRenderer(lcr));
+ this.caroselMenu.setMenuScrollColor(UIManager
+ .getColor("Panel.background"));
+ this.caroselMenu.setUpDownColor(UIManager
+ .getColor("Label.foreground"));
+ this.caroselMenu.setBackground(UIManager
+ .getColor("Panel.background"));
+ LafWidgetSupport support = LafWidgetRepository.getRepository()
+ .getLafSupport();
+ if (support != null) {
+ this.caroselMenu.setUpDownIcons(support
+ .getArrowIcon(SwingConstants.NORTH), support
+ .getArrowIcon(SwingConstants.SOUTH));
+ }
+ }
+ }
+ }
+
+ /**
+ * Tab grid overview panel. Contains a grid of tab preview widgets. The
+ * widgets are created in a separate thread ({@link TabPreviewThread}) and
+ * offered to the tab overview dialog via the registered implementation of
+ * {@link TabPreviewThread.TabPreviewCallback}. This way the application
+ * stays interactive while the tab overview dialog is being populated.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class TabGridOverviewPanel extends JPanel {
+ /**
+ * Tab preview controls.
+ */
+ protected TabPreviewControl[] previewControls;
+
+ /**
+ * Width of a single tab preview control.
+ */
+ protected int pWidth;
+
+ /**
+ * Height of a single tab preview control.
+ */
+ protected int pHeight;
+
+ /**
+ * Number of overview grid columns.
+ */
+ protected int colCount;
+
+ /**
+ * Glass pane for rollover effects.
+ */
+ protected TabGridOverviewGlassPane glassPane;
+
+ /**
+ * Creates a tab overview panel.
+ *
+ * @param dialogWidth
+ * The width of the parent dialog.
+ * @param dialogHeight
+ * The height of the parent dialog.
+ */
+ public TabGridOverviewPanel(final int dialogWidth,
+ final int dialogHeight) {
+ // int tabCount = TabOverviewDialog.this.tabPane.getTabCount();
+
+ TabPreviewThread.TabPreviewCallback previewCallback = new TabPreviewThread.TabPreviewCallback() {
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
+ * TabPreviewCallback#start(javax.swing.JTabbedPane, int,
+ * org.pushingpixels
+ * .lafwidget.tabbed.TabPreviewThread.TabPreviewInfo)
+ */
+ @Override
+ public void start(JTabbedPane tabPane, int tabCount,
+ TabPreviewInfo tabPreviewInfo) {
+ colCount = (int) Math.sqrt(tabCount);
+ if (colCount * colCount < tabCount)
+ colCount++;
+
+ pWidth = (dialogWidth - 8) / colCount;
+ pHeight = (dialogHeight - 32) / colCount;
+
+ tabPreviewInfo.setPreviewWidth(pWidth - 4);
+ tabPreviewInfo.setPreviewHeight(pHeight - 20);
+
+ // Check if need to reallocate the preview controls.
+ boolean isSame = (previewControls != null)
+ && (previewControls.length == tabCount);
+ if (isSame)
+ return;
+
+ if (previewControls != null) {
+ for (int i = 0; i < previewControls.length; i++) {
+ remove(previewControls[i]);
+ }
+ }
+
+ previewControls = new TabPreviewControl[tabCount];
+ TabPreviewPainter tpp = LafWidgetUtilities2
+ .getTabPreviewPainter(TabOverviewDialog.this.tabPane);
+ for (int i = 0; i < tabCount; i++) {
+ TabPreviewControl previewControl = new TabPreviewControl(
+ TabOverviewDialog.this.tabPane, i);
+ // fix for issue 177 in Substance (disallowing selection
+ // of disabled tabs).
+ if (tpp.isSensitiveToEvents(
+ TabOverviewDialog.this.tabPane, i)) {
+ previewControl
+ .addMouseListener(new TabPreviewMouseHandler(
+ i, previewControl, true, false));
+ }
+ previewControls[i] = previewControl;
+ add(previewControl);
+ }
+
+ doLayout();
+ for (int i = 0; i < tabCount; i++) {
+ previewControls[i].revalidate();
+ }
+ repaint();
+
+ JRootPane rp = SwingUtilities
+ .getRootPane(TabGridOverviewPanel.this);
+ glassPane = new TabGridOverviewGlassPane(
+ TabGridOverviewPanel.this);
+ rp.setGlassPane(glassPane);
+ glassPane.setVisible(true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
+ * TabPreviewCallback#offer(javax.swing.JTabbedPane, int,
+ * java.awt.image.BufferedImage)
+ */
+ @Override
+ public void offer(JTabbedPane tabPane, int tabIndex,
+ BufferedImage componentSnap) {
+ TabGridOverviewPanel.this.previewControls[tabIndex]
+ .setPreviewImage(componentSnap, true);
+ }
+ };
+
+ this.setLayout(new TabGridOverviewPanelLayout());
+
+ TabPreviewInfo previewInfo = new TabPreviewInfo();
+ previewInfo.tabPane = TabOverviewDialog.this.tabPane;
+ previewInfo.previewCallback = previewCallback;
+ //previewInfo.setPreviewWidth(this.pWidth - 4);
+ //previewInfo.setPreviewHeight(this.pHeight - 20);
+ previewInfo.toPreviewAllTabs = true;
+ previewInfo.initiator = TabOverviewDialog.this;
+
+ TabPreviewThread.getInstance().queueTabPreviewRequest(previewInfo);
+ }
+
+ /**
+ * Layout manager for the tab overview panel.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class TabGridOverviewPanelLayout implements LayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container parent) {
+ // int width = parent.getWidth();
+ // int height = parent.getHeight();
+ //
+ if (TabGridOverviewPanel.this.previewControls == null)
+ return;
+
+ for (int i = 0; i < TabGridOverviewPanel.this.previewControls.length; i++) {
+ TabPreviewControl previewControl = TabGridOverviewPanel.this.previewControls[i];
+ if (previewControl == null)
+ continue;
+ int rowIndex = i / TabGridOverviewPanel.this.colCount;
+ int colIndex = i % TabGridOverviewPanel.this.colCount;
+
+ previewControl.setBounds(colIndex
+ * TabGridOverviewPanel.this.pWidth, rowIndex
+ * TabGridOverviewPanel.this.pHeight,
+ TabGridOverviewPanel.this.pWidth,
+ TabGridOverviewPanel.this.pHeight);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return parent.getSize();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ return this.minimumLayoutSize(parent);
+ }
+ }
+ }
+
+ /**
+ * Glass pane for the tab grid overview panel. Provides rollover effects,
+ * showing zoomed version of the tab thumbnails.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public class TabGridOverviewGlassPane extends JPanel {
+ private final class RolloverMouseListener extends MouseAdapter {
+ private final int index;
+ private final TabGridOverviewPanel overviewPanel;
+
+ private Timeline rolloverTimeline;
+
+ private RolloverMouseListener(final int index,
+ final TabGridOverviewPanel overviewPanel) {
+ this.index = index;
+ this.overviewPanel = overviewPanel;
+ this.rolloverTimeline = new Timeline(
+ overviewPanel.previewControls[index]);
+ AnimationConfigurationManager.getInstance().configureTimeline(
+ this.rolloverTimeline);
+ this.rolloverTimeline.addPropertyToInterpolate("zoom", 1.0f,
+ 1.2f);
+ this.rolloverTimeline.addCallback(new SwingRepaintCallback(
+ SwingUtilities.getRootPane(overviewPanel)));
+ this.rolloverTimeline
+ .addCallback(new UIThreadTimelineCallbackAdapter() {
+ @Override
+ public void onTimelineStateChanged(
+ TimelineState oldState,
+ TimelineState newState,
+ float durationFraction,
+ float timelinePosition) {
+ if ((oldState == TimelineState.DONE)
+ && (newState == TimelineState.IDLE)) {
+ overviewPanel.previewControls[index]
+ .setToolTipText(LafWidgetUtilities
+ .getResourceBundle(tabPane)
+ .getString(
+ "TabbedPane.overviewWidgetTooltip"));
+ }
+ }
+ });
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ currHoverIndex = index;
+ overviewPanel.previewControls[index].setToolTipText(null);
+ this.rolloverTimeline.play();
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (currHoverIndex == index)
+ currHoverIndex = -1;
+ overviewPanel.previewControls[index].setToolTipText(null);
+ this.rolloverTimeline.playReverse();
+ }
+ }
+
+ /**
+ * Index of the tab thumbnail currently under the mouse pointer.
+ */
+ private int currHoverIndex;
+
+ /**
+ * Mouse listeneres (one for each tab thumbnail).
+ */
+ private MouseListener[] mouseListeners;
+
+ /**
+ * The associated overview panel.
+ */
+ private TabGridOverviewPanel overviewPanel;
+
+ /**
+ * Creates the glass pane.
+ *
+ * @param overviewPanel
+ * The associated overview panel.
+ */
+ public TabGridOverviewGlassPane(final TabGridOverviewPanel overviewPanel) {
+ this.setOpaque(false);
+ this.overviewPanel = overviewPanel;
+
+ int size = this.overviewPanel.previewControls.length;
+ this.mouseListeners = new MouseListener[size];
+ this.currHoverIndex = -1;
+ for (int i = 0; i < size; i++) {
+ final int index = i;
+ this.mouseListeners[i] = new RolloverMouseListener(index,
+ overviewPanel);
+ this.overviewPanel.previewControls[i]
+ .addMouseListener(this.mouseListeners[i]);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
+ */
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ for (int i = 0; i < tabPane.getTabCount(); i++) {
+ TabPreviewControl child = overviewPanel.previewControls[i];
+ if (child.getZoom() > 1.0f) {
+ paintSingleTabComponent(graphics, i);
+ }
+ }
+ if (currHoverIndex >= 0) {
+ // paint the currently hovered once again (so it'll be on top)
+ paintSingleTabComponent(graphics, currHoverIndex);
+ }
+ graphics.dispose();
+ }
+
+ /**
+ * Paints a single tab component.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param index
+ * Tab component index.
+ */
+ private void paintSingleTabComponent(Graphics2D graphics, int index) {
+ TabPreviewControl child = overviewPanel.previewControls[index];
+ Rectangle cBounds = child.getBounds();
+ int dx = child.getLocationOnScreen().x
+ - this.getLocationOnScreen().x;
+ int dy = child.getLocationOnScreen().y
+ - this.getLocationOnScreen().y;
+ double factor = child.getZoom();
+ int bw = (int) (cBounds.width * factor);
+ int bh = (int) (cBounds.height * factor);
+ BufferedImage bi = new BufferedImage(bw, bh,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D bGraphics = (Graphics2D) bi.getGraphics().create();
+ bGraphics.scale(factor, factor);
+
+ TabPreviewControl tChild = child;
+
+ bGraphics.setColor(tChild.getBackground());
+ bGraphics.fillRect(0, 0, tChild.getWidth(), tChild.getHeight());
+
+ Icon icon = tabPane.getIconAt(index);
+ int iy = (icon == null) ? 16 : icon.getIconHeight();
+ if (icon != null) {
+ icon.paintIcon(this, bGraphics, 1, 1);
+ }
+ String title = tabPane.getTitleAt(index);
+ JLabel tempLabel = new JLabel(title);
+ tempLabel.setBounds(tChild.titleLabel.getBounds());
+ tempLabel.setFont(tChild.titleLabel.getFont());
+ int bdx = tempLabel.getX();
+ int bdy = tempLabel.getY();
+ bGraphics.translate(bdx, bdy);
+ tempLabel.paint(bGraphics);
+ bGraphics.translate(-bdx, -bdy);
+ bdx = 1;
+ bdy = iy + 3;
+ bGraphics.translate(bdx, bdy);
+ (child).paintTabThumbnail(bGraphics);
+ bGraphics.translate(-bdx, -bdy);
+ bGraphics.setColor(Color.black);
+ bGraphics.drawRect(0, 0, child.getWidth() - 1,
+ child.getHeight() - 1);
+ bGraphics.dispose();
+
+ dx -= (bw - cBounds.width) / 2;
+ dy -= (bh - cBounds.height) / 2;
+ // make sure that the enlarged thumbnail stays inbounds
+ dx = Math.max(dx, 0);
+ dy = Math.max(dy, 0);
+ if (dx + bi.getWidth() > getWidth()) {
+ dx -= (dx + bi.getWidth() - getWidth());
+ }
+ if (dy + bi.getHeight() > getHeight()) {
+ dy -= (dy + bi.getHeight() - getHeight());
+ }
+ graphics.drawImage(bi, dx, dy, null);
+ }
+ }
+
+ /**
+ * Creates a new tab overview dialog. Declared private to enforce usage of
+ * {@link #getOverviewDialog(JTabbedPane)}.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param overviewKind
+ * Overview kind.
+ * @param owner
+ * Optional owner for the tab overview dialog.
+ * @param modal
+ * Modality indication.
+ * @param dialogWidth
+ * Tab overview dialog width.
+ * @param dialogHeight
+ * Tab overview dialog height.
+ * @throws HeadlessException
+ * @see #getOverviewDialog(JTabbedPane)
+ */
+ private TabOverviewDialog(final JTabbedPane tabPane,
+ TabOverviewKind overviewKind, Frame owner, boolean modal,
+ int dialogWidth, int dialogHeight) throws HeadlessException {
+ super(owner, modal);
+ this.tabPane = tabPane;
+ this.setLayout(new BorderLayout());
+ if (overviewKind == TabOverviewKind.GRID) {
+ TabGridOverviewPanel gridOverviewPanel = new TabGridOverviewPanel(
+ dialogWidth, dialogHeight);
+ this.add(gridOverviewPanel, BorderLayout.CENTER);
+ //
+ // TabGridOverviewGlassPane glassPane = new
+ // TabGridOverviewGlassPane(
+ // gridOverviewPanel);
+ // this.setGlassPane(glassPane);
+ // glassPane.setVisible(true);
+ }
+ if (overviewKind == TabOverviewKind.ROUND_CAROUSEL) {
+ this.add(new TabRoundCarouselOverviewPanel(dialogWidth,
+ dialogHeight), BorderLayout.CENTER);
+ }
+ if (overviewKind == TabOverviewKind.MENU_CAROUSEL) {
+ this
+ .add(new TabMenuCarouselOverviewPanel(dialogWidth,
+ dialogHeight), BorderLayout.CENTER);
+ }
+
+ this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ this.setResizable(false);
+
+ this.lafSwitchListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("lookAndFeel".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ SwingUtilities
+ .updateComponentTreeUI(TabOverviewDialog.this);
+ }
+ });
+ }
+ }
+ };
+
+ UIManager.addPropertyChangeListener(this.lafSwitchListener);
+
+ // Cancel all pending preview requests issued by this overview
+ // dialog when it closes.
+ this.addWindowListener(new WindowAdapter() {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.WindowAdapter#windowClosing(java.awt.event.WindowEvent
+ * )
+ */
+ @Override
+ public void windowClosing(WindowEvent e) {
+ this.cancelRequests();
+ UIManager.removePropertyChangeListener(lafSwitchListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.WindowAdapter#windowClosed(java.awt.event.WindowEvent
+ * )
+ */
+ @Override
+ public void windowClosed(WindowEvent e) {
+ this.cancelRequests();
+ UIManager.removePropertyChangeListener(lafSwitchListener);
+ }
+
+ /**
+ * Cancels preview requests issued by <code>this</code> overview
+ * dialog.
+ */
+ private void cancelRequests() {
+ if (TabPreviewThread.instanceRunning()) {
+ TabPreviewThread.getInstance().cancelTabPreviewRequests(
+ TabOverviewDialog.this);
+ }
+ }
+ });
+ }
+
+ /**
+ * Returns a new instance of a tab overview dialog.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @return Tab overview dialog for the specified tabbed pane.
+ */
+ public static TabOverviewDialog getOverviewDialog(JTabbedPane tabPane) {
+ final TabPreviewPainter previewPainter = LafWidgetUtilities2
+ .getTabPreviewPainter(tabPane);
+ String title = previewPainter.toUpdatePeriodically(tabPane) ? MessageFormat
+ .format(LafWidgetUtilities.getResourceBundle(tabPane)
+ .getString("TabbedPane.overviewDialogTitleRefresh"),
+ new Object[] {previewPainter
+ .getUpdateCycle(tabPane) / 1000})
+ : LafWidgetUtilities.getResourceBundle(tabPane).getString(
+ "TabbedPane.overviewDialogTitle");
+ JFrame frameForModality = previewPainter.getModalOwner(tabPane);
+ boolean isModal = (frameForModality != null);
+ Rectangle dialogScreenBounds = previewPainter
+ .getPreviewDialogScreenBounds(tabPane);
+ TabOverviewKind overviewKind = previewPainter.getOverviewKind(tabPane);
+ final TabOverviewDialog overviewDialog = new TabOverviewDialog(tabPane,
+ overviewKind, frameForModality, isModal,
+ dialogScreenBounds.width, dialogScreenBounds.height);
+ overviewDialog.setTitle(title);
+
+ overviewDialog.setLocation(dialogScreenBounds.x, dialogScreenBounds.y);
+ overviewDialog.setSize(dialogScreenBounds.width,
+ dialogScreenBounds.height);
+
+ // make sure that the tab overview dialog is disposed when
+ // it loses focus
+ final PropertyChangeListener activeWindowListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("activeWindow".equals(evt.getPropertyName())) {
+ if (overviewDialog == evt.getOldValue()) {
+ if (previewPainter.toDisposeOverviewOnFocusLoss()) {
+ overviewDialog.dispose();
+ }
+ }
+ }
+ }
+ };
+ KeyboardFocusManager.getCurrentKeyboardFocusManager()
+ .addPropertyChangeListener(activeWindowListener);
+
+ // make sure that when the window with the tabbed pane is
+ // closed, the tab overview dialog is disposed.
+ final Window tabWindow = SwingUtilities.getWindowAncestor(tabPane);
+ final WindowListener tabWindowListener = new WindowAdapter() {
+ @Override
+ public void windowClosed(WindowEvent e) {
+ overviewDialog.dispose();
+ }
+ };
+ tabWindow.addWindowListener(tabWindowListener);
+ overviewDialog.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosed(WindowEvent e) {
+ tabWindow.removeWindowListener(tabWindowListener);
+ KeyboardFocusManager.getCurrentKeyboardFocusManager()
+ .removePropertyChangeListener(activeWindowListener);
+ }
+ });
+
+ return overviewDialog;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabOverviewDialogWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabOverviewDialogWidget.java
new file mode 100644
index 0000000..e3bd2e1
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabOverviewDialogWidget.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tabbed;
+
+import java.awt.Insets;
+import java.awt.event.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+
+import org.pushingpixels.lafwidget.*;
+
+/**
+ * Adds tab overview dialog to tabbed panes.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TabOverviewDialogWidget extends LafWidgetAdapter<JTabbedPane> {
+ /**
+ * Tab overview button.
+ */
+ protected TabOverviewButton overviewButton;
+
+ /**
+ * Listens on changes to relevant tabbed pane properties.
+ */
+ protected PropertyChangeListener propertyListener;
+
+ /**
+ * Listens on tabs being added or removed.
+ */
+ protected ContainerListener containerListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installComponents()
+ */
+ @Override
+ public void installComponents() {
+ this.overviewButton = new TabOverviewButton(this.jcomp);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installDefaults()
+ */
+ @Override
+ public void installDefaults() {
+ TabPreviewPainter previewPainter = LafWidgetUtilities2
+ .getTabPreviewPainter(this.jcomp);
+ if ((previewPainter != null)
+ && previewPainter.hasOverviewDialog(this.jcomp)) {
+
+ LafWidgetSupport lafSupport = LafWidgetRepository.getRepository()
+ .getLafSupport();
+
+ Insets currTabAreaInsets = lafSupport.getTabAreaInsets(this.jcomp);
+ if (currTabAreaInsets == null)
+ currTabAreaInsets = UIManager
+ .getInsets("TabbedPane.tabAreaInsets");
+
+ Insets tabAreaInsets = new Insets(currTabAreaInsets.top,
+ LafWidgetRepository.getRepository().getLafSupport()
+ .getLookupButtonSize()
+ + 2 + currTabAreaInsets.left,
+ currTabAreaInsets.bottom, currTabAreaInsets.right);
+ lafSupport.setTabAreaInsets(this.jcomp, tabAreaInsets);
+
+ this.jcomp.add(this.overviewButton);
+ this.overviewButton.setVisible(true);
+ this.jcomp.setComponentZOrder(this.overviewButton, 0);
+ this.overviewButton.updateLocation(this.jcomp, tabAreaInsets);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallComponents()
+ */
+ @Override
+ public void uninstallComponents() {
+ if (this.overviewButton.getParent() == this.jcomp)
+ this.jcomp.remove(this.overviewButton);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ this.propertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ LafWidgetSupport lafSupport = LafWidgetRepository
+ .getRepository().getLafSupport();
+
+ Insets lafInsets = lafSupport
+ .getTabAreaInsets(TabOverviewDialogWidget.this.jcomp);
+ final Insets currTabAreaInsets = (lafInsets == null) ? UIManager
+ .getInsets("TabbedPane.tabAreaInsets")
+ : lafInsets;
+
+ if (LafWidget.TABBED_PANE_PREVIEW_PAINTER.equals(evt
+ .getPropertyName())) {
+ TabPreviewPainter previewPainter = LafWidgetUtilities2
+ .getTabPreviewPainter(TabOverviewDialogWidget.this.jcomp);
+
+ if ((previewPainter != null)
+ && previewPainter
+ .hasOverviewDialog(TabOverviewDialogWidget.this.jcomp)) {
+ Insets tabAreaInsets = new Insets(
+ currTabAreaInsets.top, LafWidgetRepository
+ .getRepository().getLafSupport()
+ .getLookupButtonSize()
+ + 2 + currTabAreaInsets.left,
+ currTabAreaInsets.bottom,
+ currTabAreaInsets.right);
+ lafSupport.setTabAreaInsets(
+ TabOverviewDialogWidget.this.jcomp,
+ tabAreaInsets);
+ TabOverviewDialogWidget.this.jcomp
+ .add(TabOverviewDialogWidget.this.overviewButton);
+ TabOverviewDialogWidget.this.overviewButton
+ .setVisible(true);
+ // jtp.setComponentZOrder(overviewButton, 0);
+ TabOverviewDialogWidget.this.overviewButton
+ .updateLocation(
+ TabOverviewDialogWidget.this.jcomp,
+ tabAreaInsets);
+ } else {
+ TabOverviewDialogWidget.this.jcomp
+ .remove(TabOverviewDialogWidget.this.overviewButton);
+ }
+ }
+ if ("tabPlacement".equals(evt.getPropertyName())
+ || "componentOrientation".equals(evt.getPropertyName())
+ || "tabAreaInsets".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (TabOverviewDialogWidget.this.overviewButton
+ .getParent() == TabOverviewDialogWidget.this.jcomp)
+ TabOverviewDialogWidget.this.overviewButton
+ .updateLocation(
+ TabOverviewDialogWidget.this.jcomp,
+ currTabAreaInsets);
+ }
+ });
+ }
+ }
+ };
+ this.jcomp.addPropertyChangeListener(this.propertyListener);
+
+ this.containerListener = new ContainerAdapter() {
+ @Override
+ public void componentAdded(ContainerEvent e) {
+ syncOverviewButtonVisibility();
+ }
+
+ @Override
+ public void componentRemoved(ContainerEvent e) {
+ syncOverviewButtonVisibility();
+ }
+
+ /**
+ * Syncs the visibility of the tab overview button.
+ */
+ private void syncOverviewButtonVisibility() {
+ if (overviewButton.getParent() != jcomp)
+ return;
+ // fix for issue 12 - hide the overview button when
+ // there are no tabs
+ overviewButton.setVisible(jcomp.getTabCount() > 0);
+ }
+ };
+ this.jcomp.addContainerListener(this.containerListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ this.jcomp.removePropertyChangeListener(this.propertyListener);
+ this.propertyListener = null;
+
+ this.jcomp.removeContainerListener(this.containerListener);
+ this.containerListener = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPagerManager.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPagerManager.java
new file mode 100644
index 0000000..05eaf71
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPagerManager.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tabbed;
+
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+
+import javax.swing.*;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities2;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.tabbed.TabPreviewThread.TabPreviewCallback;
+import org.pushingpixels.lafwidget.tabbed.TabPreviewThread.TabPreviewInfo;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
+
+/**
+ * Tab pager manager.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TabPagerManager {
+ /**
+ * Singleton instance of the tab pager manager.
+ */
+ protected static TabPagerManager instance;
+
+ /**
+ * The tabbed pane that is currently paged.
+ */
+ protected JTabbedPane currTabbedPane;
+
+ /**
+ * Index of the central tab.
+ */
+ protected int currTabIndex;
+
+ /**
+ * Index of the next tab.
+ */
+ protected int nextTabIndex;
+
+ /**
+ * Index of the previous tab.
+ */
+ protected int prevTabIndex;
+
+ // protected Map smallPreviewMap;
+ //
+ // protected Map regularPreviewMap;
+ //
+ /**
+ * Preview window for the left (previous) tab.
+ */
+ protected JWindow prevTabWindow;
+
+ /**
+ * Preview window for the central (current) tab.
+ */
+ protected JWindow currTabWindow;
+
+ /**
+ * Preview window for the right (next) tab.
+ */
+ protected JWindow nextTabWindow;
+
+ /**
+ * Indicates whether the tab pager windows are visible.
+ */
+ protected boolean isVisible;
+
+ /**
+ * Implementation of the tab preview callback for the tab pager.
+ *
+ * @author Kirill Grouchnikov.
+ */
+ public class TabPagerPreviewCallback implements TabPreviewCallback {
+ /**
+ * The associated preview window.
+ */
+ private JWindow previewWindow;
+
+ /**
+ * The associated tab preview control.
+ */
+ private TabPreviewControl previewControl;
+
+ /**
+ * Creates a new tab preview callback for the tab pager.
+ *
+ * @param previewWindow
+ * The associated preview window.
+ * @param tabPane
+ * The associated tab preview control.
+ * @param tabIndex
+ * Tab index.
+ */
+ public TabPagerPreviewCallback(JWindow previewWindow,
+ JTabbedPane tabPane, int tabIndex) {
+ this.previewWindow = previewWindow;
+ this.previewControl = new TabPreviewControl(tabPane, tabIndex);
+ this.previewWindow.getContentPane().removeAll();
+ this.previewWindow.getContentPane().add(this.previewControl,
+ BorderLayout.CENTER);
+ this.previewWindow.getContentPane().doLayout();
+ this.previewControl.doLayout();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.tabbed.TabPreviewThread.TabPreviewCallback
+ * #start(javax.swing.JTabbedPane, int,
+ * org.pushingpixels.lafwidget.tabbed.TabPreviewThread.TabPreviewInfo)
+ */
+ @Override
+ public void start(JTabbedPane tabPane, int tabCount,
+ TabPreviewInfo tabPreviewInfo) {
+ // Nothing to do since the callback was registered
+ // for a specific tab.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.tabbed.TabPreviewThread.TabPreviewCallback
+ * #offer(javax.swing.JTabbedPane, int, java.awt.image.BufferedImage)
+ */
+ @Override
+ public void offer(JTabbedPane tabPane, int tabIndex,
+ BufferedImage componentSnap) {
+ if (TabPagerManager.this.currTabbedPane != tabPane) {
+ // has since been cancelled
+ return;
+ }
+
+ if (!this.previewWindow.isVisible()) {
+ // has since been hidden
+ return;
+ }
+
+ this.previewControl.setPreviewImage(componentSnap, true);
+ }
+ }
+
+ /**
+ * Returns the tab pager instance.
+ *
+ * @return Tab pager instance.
+ */
+ public static synchronized TabPagerManager getPager() {
+ if (TabPagerManager.instance == null)
+ TabPagerManager.instance = new TabPagerManager();
+ return TabPagerManager.instance;
+ }
+
+ /**
+ * Constructs a new tab pager manager. Is made private to enforce single
+ * instance.
+ */
+ private TabPagerManager() {
+ // this.smallPreviewMap = new HashMap();
+ // this.regularPreviewMap = new HashMap();
+
+ // Rectangle virtualBounds = new Rectangle();
+ // GraphicsEnvironment ge = GraphicsEnvironment
+ // .getLocalGraphicsEnvironment();
+ // GraphicsDevice[] gds = ge.getScreenDevices();
+ // for (int i = 0; i < gds.length; i++) {
+ // GraphicsDevice gd = gds[i];
+ // GraphicsConfiguration gc = gd.getDefaultConfiguration();
+ // virtualBounds = virtualBounds.union(gc.getBounds());
+ // }
+ //
+ // int screenWidth = virtualBounds.width;
+ // int screenHeight = virtualBounds.height;
+
+ this.currTabWindow = new JWindow();
+ this.currTabWindow.getContentPane().setLayout(new BorderLayout());
+ // int currWidth = screenWidth / 3;
+ // int currHeight = screenHeight / 3;
+ // this.currTabWindow.setSize(currWidth, currHeight);
+ // // Fix for issue 178 on Substance (multiple screens)
+ // this.currTabWindow.setLocation(currWidth + virtualBounds.x,
+ // currHeight);
+ this.currTabWindow.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // fix for issue 177 in Substance (disallowing selection
+ // of disabled tabs).
+ TabPreviewPainter tpp = LafWidgetUtilities2
+ .getTabPreviewPainter(currTabbedPane);
+ if (tpp.isSensitiveToEvents(currTabbedPane,
+ currTabIndex)) {
+ hide();
+ currTabbedPane.setSelectedIndex(currTabIndex);
+ }
+ }
+ });
+ }
+ });
+ this.currTabWindow
+ .addMouseWheelListener(new TabPagerMouseWheelListener());
+
+ // int smallWidth = 2 * screenWidth / 9;
+ // int smallHeight = 2 * screenHeight / 9;
+ this.prevTabWindow = new JWindow();
+ this.prevTabWindow.getContentPane().setLayout(new BorderLayout());
+ // this.prevTabWindow.setSize(smallWidth, smallHeight);
+ // // Fix for issue 178 on Substance (multiple screens)
+ // this.prevTabWindow.setLocation((screenWidth / 18) + virtualBounds.x,
+ // 7 * screenHeight / 18);
+ this.prevTabWindow.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // fix for issue 177 in Substance (disallowing selection
+ // of disabled tabs).
+ TabPreviewPainter tpp = LafWidgetUtilities2
+ .getTabPreviewPainter(currTabbedPane);
+ if (tpp.isSensitiveToEvents(currTabbedPane,
+ prevTabIndex)) {
+ hide();
+ currTabbedPane.setSelectedIndex(prevTabIndex);
+ }
+ }
+ });
+ }
+ });
+ this.prevTabWindow
+ .addMouseWheelListener(new TabPagerMouseWheelListener());
+
+ this.nextTabWindow = new JWindow();
+ // this.nextTabWindow.getContentPane().setLayout(new BorderLayout());
+ // this.nextTabWindow.setSize(smallWidth, smallHeight);
+ // // Fix for issue 178 on Substance (multiple screens)
+ // this.nextTabWindow.setLocation((13 * screenWidth / 18)
+ // + virtualBounds.x, 7 * screenHeight / 18);
+ this.nextTabWindow.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // fix for issue 177 in Substance (disallowing selection
+ // of disabled tabs).
+ TabPreviewPainter tpp = LafWidgetUtilities2
+ .getTabPreviewPainter(currTabbedPane);
+ if (tpp.isSensitiveToEvents(currTabbedPane,
+ nextTabIndex)) {
+ hide();
+ currTabbedPane.setSelectedIndex(nextTabIndex);
+ }
+ }
+ });
+ }
+ });
+ this.nextTabWindow
+ .addMouseWheelListener(new TabPagerMouseWheelListener());
+
+ this.recomputeBounds();
+
+ this.isVisible = false;
+ }
+
+ /**
+ * Recomputes the bounds of tab pager windows.
+ */
+ private void recomputeBounds() {
+ Rectangle virtualBounds = new Rectangle();
+ GraphicsEnvironment ge = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice[] gds = ge.getScreenDevices();
+ for (int i = 0; i < gds.length; i++) {
+ GraphicsDevice gd = gds[i];
+ GraphicsConfiguration gc = gd.getDefaultConfiguration();
+ virtualBounds = virtualBounds.union(gc.getBounds());
+ }
+
+ int screenWidth = virtualBounds.width;
+ int screenHeight = virtualBounds.height;
+
+ int currWidth = screenWidth / 3;
+ int currHeight = screenHeight / 3;
+ this.currTabWindow.setSize(currWidth, currHeight);
+ // Fix for issue 178 on Substance (multiple screens)
+ this.currTabWindow.setLocation(currWidth + virtualBounds.x, currHeight);
+
+ int smallWidth = 2 * screenWidth / 9;
+ int smallHeight = 2 * screenHeight / 9;
+ this.prevTabWindow.setSize(smallWidth, smallHeight);
+ // Fix for issue 178 on Substance (multiple screens)
+ this.prevTabWindow.setLocation((screenWidth / 18) + virtualBounds.x,
+ 7 * screenHeight / 18);
+
+ this.nextTabWindow.getContentPane().setLayout(new BorderLayout());
+ this.nextTabWindow.setSize(smallWidth, smallHeight);
+ // Fix for issue 178 on Substance (multiple screens)
+ this.nextTabWindow.setLocation((13 * screenWidth / 18)
+ + virtualBounds.x, 7 * screenHeight / 18);
+ }
+
+ /**
+ * Sets the tabbed pane on <code>this</code> tab pager manager.
+ *
+ * @param jtp
+ * Tabbed pane to page.
+ */
+ private void setTabbedPane(JTabbedPane jtp) {
+ if (this.currTabbedPane == jtp)
+ return;
+ this.currTabbedPane = jtp;
+ // this.smallPreviewMap.clear();
+ // this.regularPreviewMap.clear();
+ }
+
+ /**
+ * Flips the pages.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param isForward
+ * if <code>true</code>, the tabs are flipped one page (tab)
+ * forward, if <code>false</code>, the tabs are flipped one page
+ * (tab) backward.
+ */
+ public synchronized void page(JTabbedPane tabbedPane, boolean isForward) {
+ this.setTabbedPane(tabbedPane);
+ if (!this.isVisible) {
+ this.recomputeBounds();
+ this.currTabWindow.setVisible(true);
+ this.prevTabWindow.setVisible(true);
+ this.nextTabWindow.setVisible(true);
+ this.isVisible = true;
+ this.currTabIndex = this.currTabbedPane.getSelectedIndex();
+ }
+
+ int delta = isForward ? 1 : -1;
+ this.currTabIndex += delta;
+ if (this.currTabIndex == this.currTabbedPane.getTabCount())
+ this.currTabIndex = 0;
+ if (this.currTabIndex == -1)
+ this.currTabIndex = this.currTabbedPane.getTabCount() - 1;
+
+ this.nextTabIndex = this.currTabIndex + 1;
+ this.prevTabIndex = this.currTabIndex - 1;
+ if (this.nextTabIndex == this.currTabbedPane.getTabCount())
+ this.nextTabIndex = 0;
+ if (this.prevTabIndex == -1)
+ this.prevTabIndex = this.currTabbedPane.getTabCount() - 1;
+
+ TabPreviewThread.TabPreviewInfo currTabPreviewInfo = new TabPreviewThread.TabPreviewInfo();
+ currTabPreviewInfo.tabPane = this.currTabbedPane;
+ currTabPreviewInfo.tabIndexToPreview = this.currTabIndex;
+ currTabPreviewInfo.setPreviewWidth(this.currTabWindow.getWidth() - 4);
+ currTabPreviewInfo.setPreviewHeight(this.currTabWindow.getHeight() - 20);
+ currTabPreviewInfo.previewCallback = new TabPagerPreviewCallback(
+ this.currTabWindow, this.currTabbedPane, this.currTabIndex);
+ currTabPreviewInfo.initiator = this;
+ TabPreviewPainter previewPainter = LafWidgetUtilities2
+ .getTabPreviewPainter(currTabPreviewInfo.tabPane);
+ if ((previewPainter != null)
+ && (previewPainter.hasPreviewWindow(this.currTabbedPane,
+ this.currTabIndex))) {
+ TabPreviewThread.getInstance().queueTabPreviewRequest(
+ currTabPreviewInfo);
+ }
+
+ TabPreviewThread.TabPreviewInfo prevTabPreviewInfo = new TabPreviewThread.TabPreviewInfo();
+ prevTabPreviewInfo.tabPane = this.currTabbedPane;
+ prevTabPreviewInfo.tabIndexToPreview = this.prevTabIndex;
+ prevTabPreviewInfo.setPreviewWidth(this.prevTabWindow.getWidth() - 4);
+ prevTabPreviewInfo.setPreviewHeight(this.prevTabWindow.getHeight() - 20);
+ prevTabPreviewInfo.previewCallback = new TabPagerPreviewCallback(
+ this.prevTabWindow, this.currTabbedPane, this.prevTabIndex);
+ prevTabPreviewInfo.initiator = this;
+ if ((previewPainter != null)
+ && (previewPainter.hasPreviewWindow(this.currTabbedPane,
+ this.prevTabIndex))) {
+ TabPreviewThread.getInstance().queueTabPreviewRequest(
+ prevTabPreviewInfo);
+ }
+
+ TabPreviewThread.TabPreviewInfo nextTabPreviewInfo = new TabPreviewThread.TabPreviewInfo();
+ nextTabPreviewInfo.tabPane = this.currTabbedPane;
+ nextTabPreviewInfo.tabIndexToPreview = this.nextTabIndex;
+ nextTabPreviewInfo.setPreviewWidth(this.nextTabWindow.getWidth() - 4);
+ nextTabPreviewInfo.setPreviewHeight(this.nextTabWindow.getHeight() - 20);
+ nextTabPreviewInfo.previewCallback = new TabPagerPreviewCallback(
+ this.nextTabWindow, this.currTabbedPane, this.nextTabIndex);
+ nextTabPreviewInfo.initiator = this;
+ if ((previewPainter != null)
+ && (previewPainter.hasPreviewWindow(this.currTabbedPane,
+ this.nextTabIndex))) {
+ TabPreviewThread.getInstance().queueTabPreviewRequest(
+ nextTabPreviewInfo);
+ }
+
+ }
+
+ /**
+ * Flips the pages in the currently shown tabbed pane.
+ *
+ * @param isForward
+ * if <code>true</code>, the tabs are flipped one page (tab)
+ * forward, if <code>false</code>, the tabs are flipped one page
+ * (tab) backward.
+ */
+ public void page(boolean isForward) {
+ if (this.currTabbedPane == null)
+ return;
+ this.page(this.currTabbedPane, isForward);
+ }
+
+ /**
+ * Returns indication whether the tab pager windows are showing.
+ *
+ * @return <code>true</code> if the tab pager windows are visible,
+ * <code>false</code> otherwise.
+ */
+ public boolean isVisible() {
+ return this.isVisible;
+ }
+
+ /**
+ * Hides the tab pager windows.
+ *
+ * @return The index of the center (current) tab.
+ */
+ public synchronized int hide() {
+ int result = this.isVisible ? this.currTabIndex : -1;
+
+ final Point currWindowLocation = this.currTabWindow.getLocation();
+ final Dimension currWindowSize = this.currTabWindow.getSize();
+ final Point nextWindowLocation = this.nextTabWindow.getLocation();
+ final Dimension nextWindowSize = this.nextTabWindow.getSize();
+ final Point prevWindowLocation = this.prevTabWindow.getLocation();
+ final Dimension prevWindowSize = this.prevTabWindow.getSize();
+
+ Timeline hideTabPagerTimeline = new Timeline(this.currTabbedPane);
+ AnimationConfigurationManager.getInstance().configureTimeline(
+ hideTabPagerTimeline);
+ hideTabPagerTimeline.addPropertyToInterpolate(Timeline
+ .<Rectangle> property("bounds").on(this.currTabWindow).from(
+ new Rectangle(currWindowLocation, currWindowSize)).to(
+ new Rectangle(currWindowLocation.x
+ + currWindowSize.width / 2,
+ currWindowLocation.y + currWindowSize.height
+ / 2, 0, 0)));
+ hideTabPagerTimeline.addPropertyToInterpolate(Timeline
+ .<Rectangle> property("bounds").on(this.prevTabWindow).from(
+ new Rectangle(prevWindowLocation, prevWindowSize)).to(
+ new Rectangle(prevWindowLocation.x
+ + prevWindowSize.width / 2,
+ prevWindowLocation.y + prevWindowSize.height
+ / 2, 0, 0)));
+ hideTabPagerTimeline.addPropertyToInterpolate(Timeline
+ .<Rectangle> property("bounds").on(this.nextTabWindow).from(
+ new Rectangle(nextWindowLocation, nextWindowSize)).to(
+ new Rectangle(nextWindowLocation.x
+ + nextWindowSize.width / 2,
+ nextWindowLocation.y + nextWindowSize.height
+ / 2, 0, 0)));
+ hideTabPagerTimeline.addCallback(new UIThreadTimelineCallbackAdapter() {
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ if ((oldState == TimelineState.DONE)
+ && (newState == TimelineState.IDLE)) {
+ currTabWindow.setVisible(false);
+ currTabWindow.dispose();
+ prevTabWindow.setVisible(false);
+ prevTabWindow.dispose();
+ nextTabWindow.setVisible(false);
+ nextTabWindow.dispose();
+ }
+ }
+ //
+ // @Override
+ // public void onTimelinePulse(float durationFraction,
+ // float timelinePosition) {
+ // int cx = currWindowLocation.x + currWindowSize.width / 2;
+ // int cy = currWindowLocation.y + currWindowSize.height / 2;
+ // int nWidth = (int) (currWindowSize.width * timelinePosition);
+ // int nHeight = (int) (currWindowSize.height * timelinePosition);
+ // currTabWindow.setBounds(cx - nWidth / 2, cy - nHeight / 2,
+ // nWidth, nHeight);
+ //
+ // cx = prevWindowLocation.x + prevWindowSize.width / 2;
+ // cy = prevWindowLocation.y + prevWindowSize.height / 2;
+ // nWidth = (int) (prevWindowSize.width * timelinePosition);
+ // nHeight = (int) (prevWindowSize.height * timelinePosition);
+ // prevTabWindow.setBounds(cx - nWidth / 2, cy - nHeight / 2,
+ // nWidth, nHeight);
+ //
+ // cx = nextWindowLocation.x + nextWindowSize.width / 2;
+ // cy = nextWindowLocation.y + nextWindowSize.height / 2;
+ // nWidth = (int) (nextWindowSize.width * timelinePosition);
+ // nHeight = (int) (nextWindowSize.height * timelinePosition);
+ // nextTabWindow.setBounds(cx - nWidth / 2, cy - nHeight / 2,
+ // nWidth, nHeight);
+ //
+ // currTabWindow.getRootPane().doLayout();
+ // currTabWindow.repaint();
+ //
+ // nextTabWindow.getRootPane().doLayout();
+ // nextTabWindow.repaint();
+ //
+ // prevTabWindow.getRootPane().doLayout();
+ // prevTabWindow.repaint();
+ // }
+ });
+ hideTabPagerTimeline.play();
+
+ this.isVisible = false;
+ return result;
+ }
+
+ /**
+ * Resets the internal caches.
+ */
+ public static void reset() {
+ // TabPagerManager.instance.regularPreviewMap.clear();
+ // TabPagerManager.instance.smallPreviewMap.clear();
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPagerMouseWheelListener.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPagerMouseWheelListener.java
new file mode 100644
index 0000000..9b0f399
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPagerMouseWheelListener.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tabbed;
+
+import java.awt.event.*;
+
+import javax.swing.JTabbedPane;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities2;
+
+/**
+ * Mouse wheel listener for the {@link TabPagerWidget}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TabPagerMouseWheelListener implements MouseWheelListener {
+ /**
+ * The associated tabbed pane (may be <code>null</code>).
+ */
+ protected JTabbedPane tabbedPane;
+
+ /**
+ * Creates a new mouse wheel listener on the currently paged tabbed pane.
+ */
+ public TabPagerMouseWheelListener() {
+ this(null);
+ }
+
+ /**
+ * Creates a new mouse wheel listener on the specified tabbed pane.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ */
+ public TabPagerMouseWheelListener(JTabbedPane tabbedPane) {
+ this.tabbedPane = tabbedPane;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejava.awt.event.MouseWheelListener#mouseWheelMoved(java.awt.event.
+ * MouseWheelEvent)
+ */
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ TabPreviewPainter tpp = LafWidgetUtilities2
+ .getTabPreviewPainter(this.tabbedPane);
+ if (tpp == null)
+ return;
+
+ if (((e.getModifiers() & InputEvent.CTRL_MASK) != 0)
+ && e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
+ int amount = e.getWheelRotation();
+ TabPagerManager te = TabPagerManager.getPager();
+ if (te.isVisible()) {
+ if (amount > 0) {
+ if (this.tabbedPane != null)
+ te.page(this.tabbedPane, true);
+ else
+ te.page(true);
+ } else {
+ if (this.tabbedPane != null)
+ te.page(this.tabbedPane, false);
+ else
+ te.page(false);
+ }
+ }
+ }
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPagerWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPagerWidget.java
new file mode 100644
index 0000000..3e38af3
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPagerWidget.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tabbed;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseWheelListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.AbstractAction;
+import javax.swing.InputMap;
+import javax.swing.JComponent;
+import javax.swing.JTabbedPane;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.lafwidget.LafWidget;
+import org.pushingpixels.lafwidget.LafWidgetAdapter;
+import org.pushingpixels.lafwidget.LafWidgetUtilities2;
+import org.pushingpixels.lafwidget.Resettable;
+
+/**
+ * Adds tab pager to tabbed panes.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TabPagerWidget extends LafWidgetAdapter<JTabbedPane> implements
+ Resettable {
+ /**
+ * Mouse wheel listener for Ctrl paging (from version 2.1).
+ */
+ protected MouseWheelListener mouseWheelListener;
+
+ /**
+ * Mouse listener for Ctrl paging (from version 2.1).
+ */
+ protected MouseListener mouseListener;
+
+ protected PropertyChangeListener propertyChangeListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installUI()
+ */
+ @Override
+ public void installUI() {
+ super.installUI();
+ if (LafWidgetUtilities2.getTabPreviewPainter(this.jcomp) != null) {
+ installMaps();
+ }
+ }
+
+ private void installMaps() {
+ InputMap currMap = SwingUtilities.getUIInputMap(this.jcomp,
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+
+ InputMap newMap = new InputMap();
+ if (currMap != null) {
+ KeyStroke[] kss = currMap.allKeys();
+ for (int i = 0; i < kss.length; i++) {
+ KeyStroke stroke = kss[i];
+ Object val = currMap.get(stroke);
+ newMap.put(stroke, val);
+ }
+ }
+
+ newMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,
+ InputEvent.CTRL_MASK), "tabSwitcherForward");
+ newMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,
+ InputEvent.CTRL_MASK), "tabSwitcherBackward");
+ newMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, 0, true),
+ "tabSwitcherClose");
+ newMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
+ "tabSwitcherHide");
+
+ this.jcomp.getActionMap().put("tabSwitcherForward",
+ new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // fix for issue 323 in Substance - don't show pager on
+ // tabbed
+ // panes with no preview painter
+ TabPreviewPainter tpp = LafWidgetUtilities2
+ .getTabPreviewPainter(TabPagerWidget.this.jcomp);
+ if (tpp == null)
+ return;
+
+ TabPagerManager te = TabPagerManager.getPager();
+ te.page(TabPagerWidget.this.jcomp, true);
+ }
+ });
+
+ this.jcomp.getActionMap().put("tabSwitcherBackward",
+ new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // fix for issue 323 in Substance - don't show pager on
+ // tabbed panes with no preview painter
+ TabPreviewPainter tpp = LafWidgetUtilities2
+ .getTabPreviewPainter(TabPagerWidget.this.jcomp);
+ if (tpp == null)
+ return;
+
+ TabPagerManager te = TabPagerManager.getPager();
+ te.page(TabPagerWidget.this.jcomp, false);
+ }
+ });
+
+ this.jcomp.getActionMap().put("tabSwitcherClose", new AbstractAction() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // fix for issue 323 in Substance - don't show pager on
+ // tabbed panes with no preview painter
+ TabPreviewPainter tpp = LafWidgetUtilities2
+ .getTabPreviewPainter(TabPagerWidget.this.jcomp);
+ if (tpp == null) {
+ return;
+ }
+
+ TabPagerManager te = TabPagerManager.getPager();
+ int index = te.hide();
+ // fix for issue 177 in Substance (disallowing selection
+ // of disabled tabs).
+ if ((index >= 0)
+ && tpp.isSensitiveToEvents(
+ TabPagerWidget.this.jcomp, index)) {
+ TabPagerWidget.this.jcomp.setSelectedIndex(index);
+ }
+ }
+ });
+ }
+ });
+
+ this.jcomp.getActionMap().put("tabSwitcherHide", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ TabPagerManager te = TabPagerManager.getPager();
+ if (te.isVisible()) {
+ te.hide();
+ } else {
+ // fix for defect 233 on Substance - the key event
+ // is not dispatched when tab pager is not showing.
+ Component comp = jcomp.getParent();
+ while (comp != null) {
+ if (comp instanceof JComponent) {
+ JComponent jc = (JComponent) comp;
+ KeyStroke escapeKeyStroke = KeyStroke.getKeyStroke(
+ KeyEvent.VK_ESCAPE, 0, false);
+ ActionListener al = jc
+ .getActionForKeyStroke(escapeKeyStroke);
+ if (al != null) {
+ al.actionPerformed(e);
+ return;
+ }
+ }
+ comp = comp.getParent();
+ }
+ }
+ }
+ });
+
+ SwingUtilities.replaceUIInputMap(this.jcomp,
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, newMap);
+ }
+
+ private void uninstallMaps() {
+ InputMap currMap = SwingUtilities.getUIInputMap(this.jcomp,
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+
+ InputMap newMap = new InputMap();
+ if (currMap != null) {
+ KeyStroke[] kss = currMap.allKeys();
+ for (int i = 0; i < kss.length; i++) {
+ KeyStroke stroke = kss[i];
+ Object val = currMap.get(stroke);
+ if (stroke.equals(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,
+ InputEvent.CTRL_MASK))) {
+ if ("tabSwitcherForward".equals(val))
+ continue;
+ }
+ if (stroke.equals(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,
+ InputEvent.CTRL_MASK))) {
+ if ("tabSwitcherBackward".equals(val))
+ continue;
+ }
+ if (stroke.equals(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,
+ 0, true))) {
+ if ("tabSwitcherClose".equals(val))
+ continue;
+ }
+ if (stroke
+ .equals(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0))) {
+ if ("tabSwitcherHide".equals(val))
+ continue;
+ }
+ newMap.put(stroke, val);
+ }
+ }
+
+ this.jcomp.getActionMap().remove("tabSwitcherForward");
+ this.jcomp.getActionMap().remove("tabSwitcherBackward");
+ this.jcomp.getActionMap().remove("tabSwitcherClose");
+ this.jcomp.getActionMap().remove("tabSwitcherHide");
+
+ SwingUtilities.replaceUIInputMap(this.jcomp,
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, newMap);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallUI()
+ */
+ @Override
+ public void uninstallUI() {
+ this.uninstallMaps();
+ super.uninstallUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ this.mouseWheelListener = new TabPagerMouseWheelListener(this.jcomp);
+ this.jcomp.addMouseWheelListener(this.mouseWheelListener);
+ this.mouseListener = new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if ((e.getModifiers() & InputEvent.CTRL_MASK) != 0) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ TabPagerManager te = TabPagerManager.getPager();
+ int index = te.hide();
+ if (index >= 0)
+ TabPagerWidget.this.jcomp
+ .setSelectedIndex(index);
+ }
+ });
+ }
+ }
+ };
+ this.jcomp.addMouseListener(this.mouseListener);
+
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (LafWidget.TABBED_PANE_PREVIEW_PAINTER.equals(evt
+ .getPropertyName())) {
+ TabPreviewPainter oldValue = (TabPreviewPainter) evt
+ .getOldValue();
+ TabPreviewPainter newValue = (TabPreviewPainter) evt
+ .getNewValue();
+ if ((oldValue == null) && (newValue != null)) {
+ installMaps();
+ }
+ if ((oldValue != null) && (newValue == null)) {
+ uninstallMaps();
+ }
+ }
+ }
+ };
+ this.jcomp.addPropertyChangeListener(this.propertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ this.jcomp.removeMouseWheelListener(this.mouseWheelListener);
+ this.mouseWheelListener = null;
+
+ this.jcomp.removeMouseListener(this.mouseListener);
+ this.mouseListener = null;
+
+ this.jcomp.removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.Resettable#reset()
+ */
+ @Override
+ public void reset() {
+ TabPagerManager.reset();
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewControl.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewControl.java
new file mode 100644
index 0000000..cc194c9
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewControl.java
@@ -0,0 +1,225 @@
+package org.pushingpixels.lafwidget.tabbed;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+import javax.swing.*;
+import javax.swing.border.*;
+
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.utils.ShadowPopupBorder;
+import org.pushingpixels.trident.Timeline;
+
+/**
+ * Control to display the a single tab preview.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TabPreviewControl extends JPanel {
+ /**
+ * Label for the tab icon.
+ */
+ protected JLabel iconLabel;
+
+ /**
+ * Label for the tab title.
+ */
+ protected JLabel titleLabel;
+
+ /**
+ * Panel for the tab preview image.
+ */
+ protected JPanel previewImagePanel;
+
+ /**
+ * The preview image itself.
+ */
+ protected BufferedImage previewImage;
+
+ /**
+ * The associated tabbed pane.
+ */
+ protected JTabbedPane tabPane;
+
+ private float alpha;
+
+ private float zoom;
+
+ /**
+ * Creates a tab preview control.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ */
+ public TabPreviewControl(final JTabbedPane tabPane, final int tabIndex) {
+ this.tabPane = tabPane;
+ this.setLayout(new TabPreviewControlLayout());
+ this.iconLabel = new JLabel(tabPane.getIconAt(tabIndex));
+ this.titleLabel = new JLabel(tabPane.getTitleAt(tabIndex));
+ this.titleLabel
+ .setFont(this.titleLabel.getFont().deriveFont(Font.BOLD));
+
+ // the panel with the preview image - perhaps use JLabel
+ // instead?
+ this.previewImagePanel = new JPanel() {
+ @Override
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ paintTabThumbnail(g);
+ };
+ };
+ this.add(this.iconLabel);
+ this.add(this.titleLabel);
+ this.add(this.previewImagePanel);
+
+ final boolean isSelected = (tabPane.getSelectedIndex() == tabIndex);
+ Border innerBorder = isSelected ? new LineBorder(Color.black, 2)
+ : new LineBorder(Color.black, 1);
+ this
+ .setBorder(new CompoundBorder(new ShadowPopupBorder(),
+ innerBorder));
+
+ this.alpha = 0.0f;
+ this.zoom = 1.0f;
+ }
+
+ /**
+ * Paints the tab thumbnail on the specified graphics context.
+ *
+ * @param g
+ * Graphics context.
+ */
+ public synchronized void paintTabThumbnail(Graphics g) {
+ if (TabPreviewControl.this.previewImage != null) {
+ int pw = TabPreviewControl.this.previewImage.getWidth();
+ int ph = TabPreviewControl.this.previewImage.getHeight();
+ int w = this.previewImagePanel.getWidth();
+ int h = this.previewImagePanel.getHeight();
+
+ Graphics2D g2 = (Graphics2D) g.create();
+ g2.setComposite(AlphaComposite.SrcOver.derive(alpha));
+ int dx = (w - pw) / 2;
+ int dy = (h - ph) / 2;
+ g2.drawImage(TabPreviewControl.this.previewImage, dx, dy, null);
+ g2.dispose();
+ }
+ }
+
+ /**
+ * Stes the tab index.
+ *
+ * @param tabIndex
+ * Tab index.
+ */
+ public void setTabIndex(int tabIndex) {
+ this.iconLabel.setIcon(this.tabPane.getIconAt(tabIndex));
+ this.titleLabel.setText(this.tabPane.getTitleAt(tabIndex));
+ final boolean isSelected = (this.tabPane.getSelectedIndex() == tabIndex);
+ Border innerBorder = isSelected ? new LineBorder(Color.black, 2)
+ : new LineBorder(Color.black, 1);
+ this
+ .setBorder(new CompoundBorder(new ShadowPopupBorder(),
+ innerBorder));
+ }
+
+ /**
+ * Layout for the tab preview control.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class TabPreviewControlLayout implements LayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container parent) {
+ int width = parent.getWidth();
+ int height = parent.getHeight();
+
+ Insets insets = TabPreviewControl.this.getInsets();
+ TabPreviewControl.this.iconLabel.setBounds(insets.left + 1,
+ insets.top + 1, 16, 16);
+ TabPreviewControl.this.titleLabel
+ .setBounds(insets.left + 18, insets.top + 1, width - 18
+ - insets.left - insets.right, 16);
+ TabPreviewControl.this.previewImagePanel.setBounds(insets.left + 1,
+ insets.top + 17, width - insets.left - insets.right - 2,
+ height - 17 - insets.top - insets.bottom);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return parent.getSize();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ return this.minimumLayoutSize(parent);
+ }
+ }
+
+ /**
+ * Sets the tab preview thumbnail.
+ *
+ * @param previewImage
+ * Tab preview thumbnail.
+ * @param toAnimate
+ * if <code>true</code>, the image will be faded-in.
+ */
+ public void setPreviewImage(BufferedImage previewImage, boolean toAnimate) {
+ this.previewImage = previewImage;
+ if (toAnimate) {
+ Timeline fadeTimeline = new Timeline(this);
+ AnimationConfigurationManager.getInstance().configureTimeline(
+ fadeTimeline);
+ fadeTimeline.addPropertyToInterpolate("alpha", 0.0f, 1.0f);
+ fadeTimeline.play();
+ }
+ }
+
+ public void setAlpha(float alpha) {
+ this.alpha = alpha;
+ this.repaint();
+ }
+
+ public void setZoom(float zoom) {
+ this.zoom = zoom;
+ }
+
+ public float getZoom() {
+ return zoom;
+ }
+}
\ No newline at end of file
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewPainter.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewPainter.java
new file mode 100644
index 0000000..fe3fd99
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewPainter.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tabbed;
+
+import java.awt.*;
+
+import javax.swing.JFrame;
+import javax.swing.JTabbedPane;
+
+import org.pushingpixels.lafwidget.utils.LafConstants.TabOverviewKind;
+
+/**
+ * Base class for tab preview painters.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class TabPreviewPainter {
+ /**
+ * Draws a tab preview on the specified graphics.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param tabIndex
+ * tabIndex Tab index for the preview paint.
+ * @param g
+ * Graphics context.
+ * @param x
+ * X coordinate of the preview area.
+ * @param y
+ * Y coordinate of the preview area.
+ * @param w
+ * Width of the preview area.
+ * @param h
+ * Height of the preview area.
+ */
+ public void previewTab(JTabbedPane tabPane, int tabIndex, Graphics g,
+ int x, int y, int w, int h) {
+ }
+
+ /**
+ * Checks whether the specified tab component is previewable.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index for the preview paint.
+ * @return <code>true</code> if the specified tab component is
+ * previewable, <code>false</code> otherwise.
+ */
+ public boolean hasPreview(JTabbedPane tabPane, int tabIndex) {
+ return false;
+ }
+
+ /**
+ * Checks whether the specified tab component is sensitive to events.
+ * Overriding implementation may decide that disabled tabs do not respond to
+ * mouse and keyboard events, thus not allowing selecting the corresponding
+ * tab.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ * @return <code>true</code> if the specified tab component is sensitive
+ * to events, <code>false</code> otherwise.
+ */
+ public boolean isSensitiveToEvents(JTabbedPane tabPane, int tabIndex) {
+ return false;
+ }
+
+ /**
+ * Returns the screen bounds of the tab preview dialog window.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @return Screen bounds of the preview dialog window of the specified
+ * tabbed pane.
+ */
+ public Rectangle getPreviewDialogScreenBounds(JTabbedPane tabPane) {
+ Rectangle tabPaneBounds = tabPane.getBounds();
+ Point tabPaneScreenLoc = tabPane.getLocationOnScreen();
+ return new Rectangle(tabPaneScreenLoc.x, tabPaneScreenLoc.y,
+ tabPaneBounds.width, tabPaneBounds.height);
+ }
+
+ /**
+ * Returns the owner of the overview dialog of the specified tabbed pane. If
+ * this function retuns a non-<code>null</code> value, the overview
+ * dialog will be modal for the corresponding frame.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @return If not <code>null</code>, the overview dialog for the
+ * specified tabbed pane will be modal for the corresponding frame.
+ */
+ public JFrame getModalOwner(JTabbedPane tabPane) {
+ return null;
+ }
+
+ /**
+ * Checks whether the specified tabbed pane has an overview dialog.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @return <code>true</code> if the specified tabbed pane has an overview
+ * dialog, <code>false</code> otherwise.
+ */
+ public boolean hasOverviewDialog(JTabbedPane tabPane) {
+ return false;
+ }
+
+ /**
+ * Checks whether the specified tabbed pane has a preview window for the
+ * specified tab.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ * @return <code>true</code> if the specified tabbed pane has a preview
+ * window for the specified tab, <code>false</code> otherwise.
+ */
+ public boolean hasPreviewWindow(JTabbedPane tabPane, int tabIndex) {
+ return false;
+ }
+
+ /**
+ * Returns the dimension for the tab preview window.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ * @return Dimension of the tab preview window for the specified tab in the
+ * specified tabbed pane.
+ */
+ public Dimension getPreviewWindowDimension(JTabbedPane tabPane, int tabIndex) {
+ return new Dimension(300, 200);
+ }
+
+ /**
+ * Returns extra delay (in milliseconds) for showing the tab preview window.
+ * The base delay is 2000 milliseconds (2 seconds). This function must
+ * return a non-negative value.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ * @return Non-negative extra delay (in milliseconds) for showing the tab
+ * preview window.
+ */
+ public int getPreviewWindowExtraDelay(JTabbedPane tabPane, int tabIndex) {
+ return 0;
+ }
+
+ /**
+ * Returns indication whether the thumbnail preview should be updated
+ * periodically. If the return value is <code>true</code>, then the
+ * implementation of {@link #getUpdateCycle(JTabbedPane)} returns the
+ * refresh cycle length in milliseconds.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @return <code>true</code> if the thumbnail preview of the specified
+ * tabbed pane should be updated periodically, <code>false</code>
+ * otherwise.
+ */
+ public boolean toUpdatePeriodically(JTabbedPane tabPane) {
+ return false;
+ }
+
+ /**
+ * If the result of {@link #toUpdatePeriodically(JTabbedPane)} is
+ * <code>true</code>, returns the update cycle length in milliseconds.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @return Update cycle length in milliseconds for the thumbnail preview of
+ * the specified tabbed pane.
+ */
+ public int getUpdateCycle(JTabbedPane tabPane) {
+ return 10000;
+ }
+
+ /**
+ * Returns the tab overview kind for the specified tabbed pane. Relevant if
+ * {@link #hasOverviewDialog(JTabbedPane)} returns <code>true</code> for
+ * the same tabbed pane. If {@link #hasOverviewDialog(JTabbedPane)} returns
+ * <code>true</code>, the result should be not <code>null</code>.
+ *
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @return Tab overview kind for the specified tabbed pane.
+ * @since version 3.0
+ */
+ public TabOverviewKind getOverviewKind(JTabbedPane tabPane) {
+ return TabOverviewKind.GRID;
+ }
+
+ /**
+ * Returns indication whether the tab overview dialog should be
+ * automatically disposed when it loses focus.
+ *
+ * @return if <code>true</code>, the tab overview dialog will be disposed
+ * when it loses focus.
+ */
+ public boolean toDisposeOverviewOnFocusLoss() {
+ return true;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewThread.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewThread.java
new file mode 100644
index 0000000..8c49418
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewThread.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tabbed;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.JTabbedPane;
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.LafWidgetUtilities2;
+import org.pushingpixels.lafwidget.utils.DeltaQueue;
+import org.pushingpixels.lafwidget.utils.TrackableThread;
+import org.pushingpixels.lafwidget.utils.DeltaQueue.DeltaMatcher;
+import org.pushingpixels.lafwidget.utils.DeltaQueue.Deltable;
+
+/**
+ * Thread for running the tab preview requests.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TabPreviewThread extends TrackableThread {
+ /**
+ * Indication whether a stop request has been issued on <code>this</code>
+ * thread.
+ */
+ private boolean stopRequested;
+
+ /**
+ * Queue of preview requests. Contains {@link TabPreviewInfo}s.
+ */
+ protected DeltaQueue previewQueue;
+
+ /**
+ * Information for previewing a tabbed pane.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class TabPreviewInfo extends DeltaQueue.Deltable {
+ /**
+ * Tabbed pane.
+ */
+ public JTabbedPane tabPane;
+
+ /**
+ * Callback for passing the preview thumbnail once it is computed.
+ */
+ public TabPreviewThread.TabPreviewCallback previewCallback;
+
+ /**
+ * Width of the preview thumbnail.
+ */
+ private int previewWidth;
+
+ /**
+ * Height of the preview thumbnail.
+ */
+ private int previewHeight;
+
+ /**
+ * Indicates whether all tabs in the {@link #tabPane} should be
+ * previewed.
+ */
+ public boolean toPreviewAllTabs;
+
+ /**
+ * If {@link #toPreviewAllTabs} is <code>false</code>, contains the
+ * index of the tab to be previewed.
+ */
+ public int tabIndexToPreview;
+
+ /**
+ * Points to the preview initiator.
+ */
+ public Object initiator;
+
+ public void setPreviewWidth(int previewWidth) {
+ this.previewWidth = previewWidth;
+ }
+
+ public int getPreviewWidth() {
+ return previewWidth;
+ }
+
+ public void setPreviewHeight(int previewHeight) {
+ this.previewHeight = previewHeight;
+ }
+
+ public int getPreviewHeight() {
+ return previewHeight;
+ }
+ }
+
+ /**
+ * Interface for offering the tab preview image once it has been computed.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static interface TabPreviewCallback {
+ /**
+ * Starts the current cycle of
+ * {@link #offer(JTabbedPane, int, BufferedImage)} calls. This can be
+ * used by the implementing class to revalidate itself in case the tab
+ * count in the specified tabbed pane has changed since the previous
+ * cycle of {@link #offer(JTabbedPane, int, BufferedImage)} call.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param tabCount
+ * Tab count in the tabbed pane.
+ * @param tabPreviewInfo
+ * Tab preview info. Can be changed in the implementation
+ * code.
+ */
+ public void start(JTabbedPane tabPane, int tabCount,
+ TabPreviewInfo tabPreviewInfo);
+
+ /**
+ * Offers the preview image (thumbnail) of a tab in the specified tabbed
+ * pane.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ * @param componentSnap
+ * Tab preview image.
+ */
+ public void offer(JTabbedPane tabPane, int tabIndex,
+ BufferedImage componentSnap);
+ }
+
+ /**
+ * Simple constructor. Defined private for singleton.
+ *
+ * @see #getInstance()
+ */
+ private TabPreviewThread() {
+ super();
+ this.setName("Laf-Widget tab preview");
+ this.stopRequested = false;
+ this.previewQueue = new DeltaQueue();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Thread#run()
+ */
+ @Override
+ public void run() {
+ while (!this.stopRequested) {
+ try {
+ // System.out.println(System.currentTimeMillis() + " Polling");
+ int delay = 500;
+ List<Deltable> expired = this.dequeueTabPreviewRequest(delay);
+ for (Deltable dExpired : expired) {
+ final TabPreviewInfo nextPreviewInfo = (TabPreviewInfo) dExpired;
+ final JTabbedPane jtp = nextPreviewInfo.tabPane;
+ if (jtp == null)
+ continue;
+ final TabPreviewPainter previewPainter = LafWidgetUtilities2
+ .getTabPreviewPainter(jtp);
+ final int tabCount = jtp.getTabCount();
+
+ // SwingUtilities.invokeLater(new Runnable() {
+ // public void run() {
+ // final TabPreviewInfo copyPreviewInfo = new
+ // TabPreviewInfo();
+ // copyPreviewInfo.initiator = nextPreviewInfo.initiator;
+ // copyPreviewInfo.previewCallback =
+ // nextPreviewInfo.previewCallback;
+ // copyPreviewInfo.previewHeight =
+ // nextPreviewInfo.previewHeight;
+ // copyPreviewInfo.previewWidth =
+ // nextPreviewInfo.previewWidth;
+ // copyPreviewInfo.tabIndexToPreview =
+ // nextPreviewInfo.tabIndexToPreview;
+ // copyPreviewInfo.tabPane = nextPreviewInfo.tabPane;
+ // copyPreviewInfo.toPreviewAllTabs =
+ // nextPreviewInfo.toPreviewAllTabs;
+
+ if (nextPreviewInfo.toPreviewAllTabs) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // The call to start() is only relevant for the
+ // preview of all tabs.
+ nextPreviewInfo.previewCallback.start(jtp, jtp
+ .getTabCount(), nextPreviewInfo);
+ }
+ });
+
+ for (int i = 0; i < tabCount; i++) {
+ final int index = i;
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ getSingleTabPreviewImage(jtp,
+ previewPainter, nextPreviewInfo,
+ index);
+ }
+ });
+ }
+ } else {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ getSingleTabPreviewImage(jtp, previewPainter,
+ nextPreviewInfo,
+ nextPreviewInfo.tabIndexToPreview);
+ }
+ });
+ }
+ // }
+ // });
+
+ if (previewPainter.toUpdatePeriodically(jtp)) {
+ TabPreviewInfo cyclePreviewInfo = new TabPreviewInfo();
+ // copy all the fields from the currently processed
+ // request
+ cyclePreviewInfo.tabPane = nextPreviewInfo.tabPane;
+ cyclePreviewInfo.tabIndexToPreview = nextPreviewInfo.tabIndexToPreview;
+ cyclePreviewInfo.toPreviewAllTabs = nextPreviewInfo.toPreviewAllTabs;
+ cyclePreviewInfo.previewCallback = nextPreviewInfo.previewCallback;
+ cyclePreviewInfo.setPreviewWidth(nextPreviewInfo.getPreviewWidth());
+ cyclePreviewInfo.setPreviewHeight(nextPreviewInfo.getPreviewHeight());
+ cyclePreviewInfo.initiator = nextPreviewInfo.initiator;
+
+ // schedule it to app-specific delay
+ cyclePreviewInfo.setDelta(previewPainter
+ .getUpdateCycle(cyclePreviewInfo.tabPane));
+
+ // queue the new request
+ this.queueTabPreviewRequest(cyclePreviewInfo);
+ }
+ }
+ Thread.sleep(delay);
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ // System.out.println("Tab preview finished");
+ }
+
+ /**
+ * Computes and offers the preview thumbnail for a single tab.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param previewPainter
+ * Tab preview painter.
+ * @param previewInfo
+ * Preview info.
+ * @param tabIndex
+ * Index of the tab to preview.
+ */
+ protected void getSingleTabPreviewImage(final JTabbedPane tabPane,
+ final TabPreviewPainter previewPainter,
+ final TabPreviewInfo previewInfo, final int tabIndex) {
+ int pWidth = previewInfo.getPreviewWidth();
+ int pHeight = previewInfo.getPreviewHeight();
+ final BufferedImage previewImage = new BufferedImage(pWidth, pHeight,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D gr = previewImage.createGraphics();
+ Component comp = tabPane.getComponentAt(tabIndex);
+
+ if (previewPainter.hasPreview(tabPane, tabIndex)) {
+ Map<Component, Boolean> dbSnapshot = new HashMap<Component, Boolean>();
+ LafWidgetUtilities.makePreviewable(comp, dbSnapshot);
+ previewPainter.previewTab(tabPane, tabIndex, gr, 0, 0, pWidth,
+ pHeight);
+ LafWidgetUtilities.restorePreviewable(comp, dbSnapshot);
+ } else {
+ gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ gr.setColor(Color.red);
+ gr.setStroke(new BasicStroke(Math.max(5.0f, Math.min(pWidth,
+ pHeight) / 10.0f)));
+ gr.drawLine(0, 0, pWidth, pHeight);
+ gr.drawLine(0, pHeight, pWidth, 0);
+ }
+ gr.dispose();
+
+ if (previewInfo.previewCallback != null) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ previewInfo.previewCallback.offer(tabPane, tabIndex,
+ previewImage);
+ }
+ });
+ }
+ }
+
+ /**
+ * Queues the request to preview one or all tabs in the specified tabbed
+ * pane. Once the request is queued, the thread will pick it up from the
+ * queue (in at most 500 milliseconds in the current implementation) and
+ * start processing it. For each tab (if all tabs were requested to be
+ * previewed), the preview thumbnail will be offered to the relevant
+ * callback. This allows to maintain the interactivity of the application
+ * while generating the preview thumbnails for the tab overview dialog (see
+ * {@link TabOverviewDialog}).
+ *
+ * @param previewInfo
+ * Tab preview info.
+ */
+ public void queueTabPreviewRequest(TabPreviewInfo previewInfo) {
+ this.previewQueue.queue(previewInfo);
+ }
+
+ /**
+ * Cancels all tab preview requests that were initiated by the specified
+ * initiator.
+ *
+ * @param initiator
+ * Initiator.
+ */
+ public void cancelTabPreviewRequests(final Object initiator) {
+ DeltaMatcher matcher = new DeltaMatcher() {
+ @Override
+ public boolean matches(Deltable deltable) {
+ TabPreviewInfo currInfo = (TabPreviewInfo) deltable;
+ return (currInfo.initiator == initiator);
+ }
+ };
+ this.previewQueue.removeMatching(matcher);
+ }
+
+ /**
+ * Removes the tab preview requests that have at most specified delay left.
+ *
+ * @param delay
+ * Delay.
+ * @return The list of all tab preview requests that have at most specified
+ * delay left.
+ */
+ private List<Deltable> dequeueTabPreviewRequest(int delay) {
+ return this.previewQueue.dequeue(delay);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.utils.TrackableThread#requestStop()
+ */
+ @Override
+ protected void requestStop() {
+ this.stopRequested = true;
+ TabPreviewThread.tabPreviewThread = null;
+ }
+
+ /**
+ * The preview thread.
+ */
+ private static TabPreviewThread tabPreviewThread;
+
+ /**
+ * Returns the singleton instance of the tab preview thread.
+ *
+ * @return The singleton instance of the tab preview thread.
+ */
+ public static synchronized TabPreviewThread getInstance() {
+ if (TabPreviewThread.tabPreviewThread == null) {
+ // System.err.println("Allocating ");
+ // Thread.dumpStack();
+ TabPreviewThread.tabPreviewThread = new TabPreviewThread();
+ TabPreviewThread.tabPreviewThread.start();
+ }
+ return TabPreviewThread.tabPreviewThread;
+ }
+
+ /**
+ * Returns indication whether tab preview thread is running.
+ *
+ * @return <code>true</code> if the tab preview thread is running,
+ * <code>false</code> otherwise.
+ */
+ public static synchronized boolean instanceRunning() {
+ return (TabPreviewThread.tabPreviewThread != null);
+ }
+
+ // public void dump() {
+ // System.out.println("Dump");
+ // for (int i = 0; i < this.previewRequests.size(); i++) {
+ // TabPreviewInfo tpi = (TabPreviewInfo) this.previewRequests.get(i);
+ // System.out.println("\t" + tpi.tabIndexToPreview + " -> "
+ // + tpi.timeToExpire);
+ // }
+ // }
+ //
+ // public static void main(String[] args) {
+ // TabPreviewThread tpt = new TabPreviewThread();
+ // TabPreviewInfo tpi11 = new TabPreviewInfo();
+ // tpi11.tabIndexToPreview = 11;
+ // tpi11.timeToExpire = 100;
+ // tpt.queueTabPreviewRequest(tpi11);
+ // tpt.dump();
+ //
+ // TabPreviewInfo tpi12 = new TabPreviewInfo();
+ // tpi12.tabIndexToPreview = 12;
+ // tpi12.timeToExpire = 100;
+ // tpt.queueTabPreviewRequest(tpi12);
+ // tpt.dump();
+ //
+ // TabPreviewInfo tpi21 = new TabPreviewInfo();
+ // tpi21.tabIndexToPreview = 21;
+ // tpi21.timeToExpire = 200;
+ // tpt.queueTabPreviewRequest(tpi21);
+ // tpt.dump();
+ //
+ // TabPreviewInfo tpi31 = new TabPreviewInfo();
+ // tpi31.tabIndexToPreview = 31;
+ // tpi31.timeToExpire = 300;
+ // tpt.queueTabPreviewRequest(tpi31);
+ // tpt.dump();
+ //
+ // TabPreviewInfo tpi13 = new TabPreviewInfo();
+ // tpi13.tabIndexToPreview = 13;
+ // tpi13.timeToExpire = 100;
+ // tpt.queueTabPreviewRequest(tpi13);
+ // tpt.dump();
+ //
+ // TabPreviewInfo tpi22 = new TabPreviewInfo();
+ // tpi22.tabIndexToPreview = 22;
+ // tpi22.timeToExpire = 200;
+ // tpt.queueTabPreviewRequest(tpi22);
+ // tpt.dump();
+ //
+ // TabPreviewInfo tpi25 = new TabPreviewInfo();
+ // tpi25.tabIndexToPreview = 25;
+ // tpi25.timeToExpire = 250;
+ // tpt.queueTabPreviewRequest(tpi25);
+ // tpt.dump();
+ //
+ // TabPreviewInfo tpi51 = new TabPreviewInfo();
+ // tpi51.tabIndexToPreview = 51;
+ // tpi51.timeToExpire = 500;
+ // tpt.queueTabPreviewRequest(tpi51);
+ // tpt.dump();
+ //
+ // List gr150 = tpt.dequeueTabPreviewRequest(2500);
+ // System.out.println("Dump 150");
+ // for (int i = 0; i < gr150.size(); i++) {
+ // TabPreviewInfo tpi = (TabPreviewInfo) gr150.get(i);
+ // System.out.println("\t" + tpi.tabIndexToPreview);
+ // }
+ // tpt.dump();
+ //
+ // TrackableThread.requestStopAllThreads();
+ // }
+}
\ No newline at end of file
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewWindow.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewWindow.java
new file mode 100644
index 0000000..aa2fe25
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tabbed/TabPreviewWindow.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tabbed;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+
+import javax.swing.*;
+
+import org.pushingpixels.lafwidget.*;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.tabbed.TabPreviewThread.TabPreviewInfo;
+import org.pushingpixels.trident.Timeline;
+
+/**
+ * Tab preview window. Is displayed when the mouse hovers over relevant
+ * (previewable) tabs.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TabPreviewWindow extends JWindow implements ActionListener {
+ public static final class PreviewLabel extends JLabel {
+ float alpha = 0.0f;
+
+ private PreviewLabel(Icon image) {
+ super(image);
+ }
+
+ public void setAlpha(float alpha) {
+ this.alpha = alpha;
+ this.repaint();
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2 = (Graphics2D) g.create();
+ g2.setComposite(AlphaComposite.SrcOver.derive(this.alpha));
+ super.paintComponent(g2);
+ g2.dispose();
+ }
+ }
+
+ /**
+ * Singleton instance of tab preview window.
+ */
+ protected static TabPreviewWindow instance;
+
+ /**
+ * Information on the current tab preview request.
+ */
+ protected static TabPreviewInfo currTabPreviewInfo;
+
+ /**
+ * Currently running timer task.
+ */
+ protected static Timer currTabPreviewTimer;
+
+ /**
+ * Returns the singleton instance of tab preview window.
+ *
+ * @return The singleton instance of tab preview window.
+ */
+ public static synchronized TabPreviewWindow getInstance() {
+ if (TabPreviewWindow.instance == null) {
+ TabPreviewWindow.instance = new TabPreviewWindow();
+ TabPreviewWindow.instance.setLayout(new BorderLayout());
+ // instance.addHierarchyListener(new HierarchyListener() {
+ // public void hierarchyChanged(HierarchyEvent e) {
+ // System.err.println(e.getID());
+ // }
+ // });
+ }
+ return TabPreviewWindow.instance;
+ }
+
+ /**
+ * Posts a preview request for a tab component in the specified tabbed pane.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Index of the tab to preview.
+ */
+ public synchronized void postPreviewRequest(JTabbedPane tabPane,
+ int tabIndex) {
+ TabPreviewPainter previewPainter = LafWidgetUtilities2
+ .getTabPreviewPainter(tabPane);
+ if ((previewPainter == null)
+ || (!previewPainter.hasPreviewWindow(tabPane, tabIndex)))
+ return;
+
+ // check if already showing
+ if (currTabPreviewInfo != null) {
+ if ((currTabPreviewInfo.tabPane == tabPane)
+ && (currTabPreviewInfo.tabIndexToPreview == tabIndex))
+ return;
+ }
+
+ if (currTabPreviewTimer != null) {
+ if (currTabPreviewTimer.isRunning())
+ currTabPreviewTimer.stop();
+ }
+
+ Dimension previewDim = previewPainter.getPreviewWindowDimension(
+ tabPane, tabIndex);
+ int pWidth = previewDim.width;
+ int pHeight = previewDim.height;
+ Component tabComponent = tabPane.getComponentAt(tabIndex);
+ if (tabComponent != null) {
+ int width = tabComponent.getWidth();
+ int height = tabComponent.getHeight();
+ double ratio = (double) width / (double) height;
+ double pRatio = (double) previewDim.width
+ / (double) previewDim.height;
+ if (pRatio > ratio) {
+ pWidth = (int) (pHeight * ratio);
+ } else {
+ pHeight = (int) (pWidth / ratio);
+ }
+ }
+
+ currTabPreviewInfo = new TabPreviewInfo();
+ currTabPreviewInfo.tabPane = tabPane;
+ currTabPreviewInfo.tabIndexToPreview = tabIndex;
+ currTabPreviewInfo.setPreviewWidth(pWidth);
+ currTabPreviewInfo.setPreviewHeight(pHeight);
+ currTabPreviewInfo.initiator = tabPane;
+ currTabPreviewInfo.previewCallback = new TabPreviewThread.TabPreviewCallback() {
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
+ * TabPreviewCallback #start(javax.swing.JTabbedPane, int,
+ * org.pushingpixels
+ * .lafwidget.tabbed.TabPreviewThread.TabPreviewInfo)
+ */
+ @Override
+ public void start(JTabbedPane tabPane, int tabCount,
+ TabPreviewInfo tabPreviewInfo) {
+ // Nothing to do since the callback was registered
+ // for a specific tab.
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.lafwidget.tabbed.TabPreviewThread.
+ * TabPreviewCallback #offer(javax.swing.JTabbedPane, int,
+ * java.awt.image.BufferedImage)
+ */
+ @Override
+ public void offer(JTabbedPane tabPane, int tabIndex,
+ BufferedImage componentSnap) {
+ if (currTabPreviewInfo == null) {
+ // has since been cancelled
+ return;
+ }
+ if ((tabPane != currTabPreviewInfo.tabPane)
+ || (tabIndex != currTabPreviewInfo.tabIndexToPreview)) {
+ // has since been cancelled
+ return;
+ }
+ Rectangle previewScreenRectangle = TabPreviewWindow.this
+ .getPreviewWindowScreenRect(tabPane, tabIndex,
+ currTabPreviewInfo.getPreviewWidth(),
+ currTabPreviewInfo.getPreviewHeight());
+ TabPreviewWindow.this.getContentPane().removeAll();
+ final JLabel previewLabel = new PreviewLabel(new ImageIcon(
+ componentSnap));
+ TabPreviewWindow.this
+ .addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentShown(ComponentEvent e) {
+ previewLabel.setVisible(true);
+ // Start fading-in of the image.
+ Timeline timeline = new Timeline(previewLabel);
+ AnimationConfigurationManager.getInstance()
+ .configureTimeline(timeline);
+ timeline.addPropertyToInterpolate("alpha",
+ 0.0f, 1.0f);
+ timeline.play();
+ }
+ });
+ // previewLabel.setBorder(new SubstanceBorder());
+ TabPreviewWindow.this.getContentPane().add(previewLabel,
+ BorderLayout.CENTER);
+ TabPreviewWindow.this.setSize(previewScreenRectangle.width,
+ previewScreenRectangle.height);
+ TabPreviewWindow.this.setLocation(previewScreenRectangle.x,
+ previewScreenRectangle.y);
+ previewLabel.setVisible(false);
+ TabPreviewWindow.this.setVisible(true);
+ }
+ };
+
+ int extraDelay = previewPainter.getPreviewWindowExtraDelay(tabPane,
+ tabIndex);
+ if (extraDelay < 0) {
+ throw new IllegalArgumentException(
+ "Extra delay for tab preview must be non-negative");
+ }
+ currTabPreviewTimer = new Timer(2000 + extraDelay, this);
+ currTabPreviewTimer.setRepeats(false);
+ currTabPreviewTimer.start();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (currTabPreviewInfo == null)
+ return;
+
+ // If we are here - the delay timer has expired.
+
+ // System.err.println("Post " + tabIndex);
+ TabPreviewPainter previewPainter = LafWidgetUtilities2
+ .getTabPreviewPainter(currTabPreviewInfo.tabPane);
+ if ((previewPainter == null)
+ || (!previewPainter.hasPreviewWindow(
+ currTabPreviewInfo.tabPane,
+ currTabPreviewInfo.tabIndexToPreview)))
+ return;
+
+ // Queue the request with the preview thread.
+ TabPreviewThread.getInstance().queueTabPreviewRequest(
+ currTabPreviewInfo);
+ }
+
+ /**
+ * Returns the screen rectangle for the preview window.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ * @param pWidth
+ * Preview width.
+ * @param pHeight
+ * Preview height.
+ * @return The screen rectangle for the preview window.
+ */
+ protected Rectangle getPreviewWindowScreenRect(JTabbedPane tabPane,
+ int tabIndex, int pWidth, int pHeight) {
+ LafWidgetSupport lafSupport = LafWidgetRepository.getRepository()
+ .getLafSupport();
+
+ Rectangle relative = lafSupport.getTabRectangle(tabPane, tabIndex);
+ if (relative == null)
+ return null;
+
+ Rectangle result = new Rectangle(pWidth, pHeight);
+ boolean ltr = tabPane.getComponentOrientation().isLeftToRight();
+ if (ltr) {
+ if (tabPane.getTabPlacement() != SwingConstants.BOTTOM) {
+ result.setLocation(relative.x, relative.y + relative.height);
+ } else {
+ result.setLocation(relative.x, relative.y - pHeight);
+ }
+ } else {
+ if (tabPane.getTabPlacement() != SwingConstants.BOTTOM) {
+ result.setLocation(relative.x + relative.width - pWidth,
+ relative.y + relative.height);
+ } else {
+ result.setLocation(relative.x + relative.width - pWidth,
+ relative.y - pHeight);
+ }
+ }
+ int dx = tabPane.getLocationOnScreen().x;
+ int dy = tabPane.getLocationOnScreen().y;
+ result.x += dx;
+ result.y += dy;
+
+ // Fix to make the tab preview window stay in screen bounds
+ Rectangle virtualBounds = new Rectangle();
+ GraphicsEnvironment ge = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice[] gds = ge.getScreenDevices();
+ for (int i = 0; i < gds.length; i++) {
+ GraphicsDevice gd = gds[i];
+ GraphicsConfiguration gc = gd.getDefaultConfiguration();
+ virtualBounds = virtualBounds.union(gc.getBounds());
+ }
+
+ if (result.x + result.width > (virtualBounds.width - 1)) {
+ result.x -= (result.x + result.width - virtualBounds.width + 1);
+ }
+ if (result.y + result.height > (virtualBounds.height - 1)) {
+ result.y -= (result.y + result.height - virtualBounds.height + 1);
+ }
+ if (result.x < virtualBounds.x)
+ result.x = virtualBounds.x + 1;
+ if (result.y < virtualBounds.y)
+ result.y = virtualBounds.y + 1;
+ return result;
+ }
+
+ /**
+ * Cancels the currently pending preview request.
+ */
+ public static synchronized void cancelPreviewRequest() {
+ // System.err.println("Cancel");
+ currTabPreviewInfo = null;
+ if ((currTabPreviewTimer != null) && currTabPreviewTimer.isRunning()) {
+ currTabPreviewTimer.stop();
+ currTabPreviewTimer = null;
+ }
+ if (instance != null)
+ instance.dispose();// setVisible(false);
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/EditContextMenuWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/EditContextMenuWidget.java
new file mode 100644
index 0000000..62aaac9
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/EditContextMenuWidget.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.text;
+
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+import javax.swing.AbstractAction;
+import javax.swing.ImageIcon;
+import javax.swing.JPopupMenu;
+import javax.swing.SwingUtilities;
+import javax.swing.text.JTextComponent;
+
+import org.pushingpixels.lafwidget.LafWidgetAdapter;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+
+/**
+ * Adds edit context menu on text components.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class EditContextMenuWidget extends LafWidgetAdapter<JTextComponent> {
+ /**
+ * Mouse listener for showing the edit context menu.
+ */
+ protected MouseListener menuMouseListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ this.menuMouseListener = new MouseAdapter() {
+ // fix for issue 8 - use mousePressed instead of
+ // mouseClicked so that it will be triggered on Linux.
+ @Override
+ public void mousePressed(MouseEvent e) {
+ this.handleMouseEvent(e);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ this.handleMouseEvent(e);
+ }
+
+ private void handleMouseEvent(MouseEvent e) {
+ if (!LafWidgetUtilities.hasTextEditContextMenu(jcomp))
+ return;
+ if (!e.isPopupTrigger())
+ return;
+
+ // request focus
+ jcomp.requestFocus(true);
+
+ JPopupMenu editMenu = new JPopupMenu();
+ editMenu.add(new CutAction());
+ editMenu.add(new CopyAction());
+ editMenu.add(new PasteAction());
+ editMenu.addSeparator();
+ editMenu.add(new DeleteAction());
+ editMenu.add(new SelectAllAction());
+
+ Point pt = SwingUtilities.convertPoint(e.getComponent(), e
+ .getPoint(), jcomp);
+ editMenu.show(jcomp, pt.x, pt.y);
+ }
+ };
+ jcomp.addMouseListener(this.menuMouseListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ jcomp.removeMouseListener(this.menuMouseListener);
+ this.menuMouseListener = null;
+ }
+
+ /**
+ * <code>Paste</code> action.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class PasteAction extends AbstractAction {
+ /**
+ * Creates new <code>Paste</code> action.
+ */
+ public PasteAction() {
+ super(LafWidgetUtilities.getResourceBundle(jcomp).getString(
+ "EditMenu.paste"), new ImageIcon(
+ EditContextMenuWidget.class.getClassLoader().getResource(
+ "org/pushingpixels/lafwidget/text/edit-paste.png")));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent
+ * )
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ jcomp.paste();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.AbstractAction#isEnabled()
+ */
+ @Override
+ public boolean isEnabled() {
+ if (jcomp.isEditable() && jcomp.isEnabled()) {
+ Transferable contents = Toolkit.getDefaultToolkit()
+ .getSystemClipboard().getContents(this);
+ return contents.isDataFlavorSupported(DataFlavor.stringFlavor);
+ } else
+ return false;
+ }
+ }
+
+ /**
+ * <code>Select All</code> action.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class SelectAllAction extends AbstractAction {
+ /**
+ * Creates new <code>Select All</code> action.
+ */
+ public SelectAllAction() {
+ super(
+ LafWidgetUtilities.getResourceBundle(jcomp).getString(
+ "EditMenu.selectAll"),
+ new ImageIcon(
+ EditContextMenuWidget.class
+ .getClassLoader()
+ .getResource(
+ "org/pushingpixels/lafwidget/text/edit-select-all.png")));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent
+ * )
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ jcomp.selectAll();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.AbstractAction#isEnabled()
+ */
+ @Override
+ public boolean isEnabled() {
+ return jcomp.isEnabled() && (jcomp.getDocument().getLength() > 0);
+ }
+ }
+
+ /**
+ * <code>Delete</code> action.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class DeleteAction extends AbstractAction {
+ /**
+ * Creates new <code>Delete</code> action.
+ */
+ public DeleteAction() {
+ super(
+ LafWidgetUtilities.getResourceBundle(jcomp).getString(
+ "EditMenu.delete"),
+ new ImageIcon(
+ EditContextMenuWidget.class
+ .getClassLoader()
+ .getResource(
+ "org/pushingpixels/lafwidget/text/edit-delete.png")));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent
+ * )
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ jcomp.replaceSelection(null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.AbstractAction#isEnabled()
+ */
+ @Override
+ public boolean isEnabled() {
+ return jcomp.isEditable() && jcomp.isEnabled()
+ && (jcomp.getSelectedText() != null);
+ }
+ }
+
+ /**
+ * <code>Cut</code> action.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class CutAction extends AbstractAction {
+ /**
+ * Creates new <code>Cut</code> action.
+ */
+ public CutAction() {
+ super(LafWidgetUtilities.getResourceBundle(jcomp).getString(
+ "EditMenu.cut"), new ImageIcon(EditContextMenuWidget.class
+ .getClassLoader().getResource(
+ "org/pushingpixels/lafwidget/text/edit-cut.png")));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent
+ * )
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ jcomp.cut();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.AbstractAction#isEnabled()
+ */
+ @Override
+ public boolean isEnabled() {
+ return jcomp.isEditable() && jcomp.isEnabled()
+ && (jcomp.getSelectedText() != null);
+ }
+ }
+
+ /**
+ * <code>Copy</code> action.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class CopyAction extends AbstractAction {
+ /**
+ * Creates new <code>Copy</code> action.
+ */
+ public CopyAction() {
+ super(LafWidgetUtilities.getResourceBundle(jcomp).getString(
+ "EditMenu.copy"), new ImageIcon(EditContextMenuWidget.class
+ .getClassLoader().getResource(
+ "org/pushingpixels/lafwidget/text/edit-copy.png")));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent
+ * )
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ jcomp.copy();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.AbstractAction#isEnabled()
+ */
+ @Override
+ public boolean isEnabled() {
+ return jcomp.isEnabled() && (jcomp.getSelectedText() != null);
+ }
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/LockBorder.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/LockBorder.java
new file mode 100644
index 0000000..86d03ab
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/LockBorder.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.text;
+
+import java.awt.*;
+
+import javax.swing.Icon;
+import javax.swing.JViewport;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.lafwidget.*;
+
+/**
+ * Border with "lock" indication.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LockBorder implements Border, UIResource {
+ /**
+ * The original (decorated) border.
+ */
+ private Border originalBorder;
+
+ /**
+ * Constructs a new lock border.
+ *
+ * @param originalBorder
+ * The original border.
+ */
+ public LockBorder(Border originalBorder) {
+ if (originalBorder != null)
+ this.originalBorder = originalBorder;
+ else
+ this.originalBorder = new EmptyBorder(0, 0, 0, 0);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#getBorderInsets(java.awt.Component)
+ */
+ @Override
+ public Insets getBorderInsets(Component c) {
+ LafWidgetSupport lafSupport = LafWidgetRepository.getRepository()
+ .getLafSupport();
+ Icon lockIcon = lafSupport.getLockIcon(c);
+ if (lockIcon == null)
+ lockIcon = LafWidgetUtilities.getSmallLockIcon();
+
+ Insets origInsets = this.originalBorder.getBorderInsets(c);
+
+ if (c.getComponentOrientation().isLeftToRight()) {
+ return new Insets(origInsets.top, Math.max(origInsets.left,
+ lockIcon.getIconWidth() + 2), origInsets.bottom,
+ origInsets.right);
+ } else {
+ // support for RTL
+ return new Insets(origInsets.top, origInsets.left,
+ origInsets.bottom, Math.max(origInsets.right, lockIcon
+ .getIconWidth() + 2));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#isBorderOpaque()
+ */
+ @Override
+ public boolean isBorderOpaque() {
+ return this.originalBorder.isBorderOpaque();
+ }
+
+ public Border getOriginalBorder() {
+ return originalBorder;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#paintBorder(java.awt.Component,
+ * java.awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height) {
+ this.originalBorder.paintBorder(c, g, x, y, width, height);
+ LafWidgetSupport lafSupport = LafWidgetRepository.getRepository()
+ .getLafSupport();
+ Icon lockIcon = lafSupport.getLockIcon(c);
+ if (lockIcon == null)
+ lockIcon = LafWidgetUtilities.getSmallLockIcon();
+
+ int offsetY = 0;
+ if (c.getParent() instanceof JViewport) {
+ // enhancement 9 - show the lock icon of components
+ // in JScrollPane so that it is visible in the bottom
+ // corner of the scroll pane
+ JViewport viewport = (JViewport) c.getParent();
+ // have to set to simple scroll mode since the default (blit)
+ // results in visual artifacts due to optimized buffer-copy
+ // painting.
+ if (viewport.getScrollMode() != JViewport.SIMPLE_SCROLL_MODE) {
+ viewport.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
+ }
+ Rectangle viewRect = viewport.getViewRect();
+ offsetY = c.getHeight() - viewRect.y - viewRect.height;
+ }
+
+ if (c.getComponentOrientation().isLeftToRight()) {
+ lockIcon.paintIcon(c, g, x, y + height - lockIcon.getIconHeight()
+ - offsetY);
+ } else {
+ // support for RTL
+ lockIcon.paintIcon(c, g, x + width - lockIcon.getIconWidth(), y
+ + height - lockIcon.getIconHeight() - offsetY);
+ }
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/LockBorderWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/LockBorderWidget.java
new file mode 100644
index 0000000..ab42e85
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/LockBorderWidget.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.text;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+
+import org.pushingpixels.lafwidget.*;
+
+/**
+ * Adds visual indication on non-editable text components.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LockBorderWidget extends LafWidgetAdapter {
+ /**
+ * Listens on all properties to decide whether a lock border should be shown
+ * / hidden.
+ */
+ protected PropertyChangeListener propertyChangeListener;
+
+ /**
+ * <code>true</code> if this widget is uninstalling. Fix for defect 7.
+ */
+ protected boolean isUninstalling = false;
+
+ /**
+ * Name for client property that stores the original border.
+ */
+ public static String ORIGINAL_BORDER = "lafwidget.internal.originalBorder";
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ // fix for defect 5 - infinite event chain.
+ if ("border".equals(evt.getPropertyName()))
+ return;
+ if (LockBorderWidget.ORIGINAL_BORDER.equals(evt
+ .getPropertyName()))
+ return;
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // fix for defect 7 - not removing lock border
+ // on LAF switch
+ if (isUninstalling)
+ return;
+ LafWidgetSupport lafSupport = LafWidgetRepository
+ .getRepository().getLafSupport();
+ boolean hasLockIcon = lafSupport.hasLockIcon(jcomp);
+ if (hasLockIcon) {
+ installLockBorder();
+ } else {
+ restoreOriginalBorder();
+ }
+ }
+
+ });
+ }
+ };
+ this.jcomp.addPropertyChangeListener(this.propertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ this.jcomp.removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallUI()
+ */
+ @Override
+ public void uninstallUI() {
+ // fix for issue 7 - restoring original border on LAF switch.
+ this.isUninstalling = true;
+ Border original = (Border) this.jcomp
+ .getClientProperty(LockBorderWidget.ORIGINAL_BORDER);
+ if (original != null) {
+ this.jcomp.setBorder(original);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installDefaults()
+ */
+ @Override
+ public void installDefaults() {
+ super.installDefaults();
+ LafWidgetSupport lafSupport = LafWidgetRepository.getRepository()
+ .getLafSupport();
+ boolean hasLockIcon = lafSupport.hasLockIcon(this.jcomp);
+ if (hasLockIcon) {
+ Border currBorder = this.jcomp.getBorder();
+ this.jcomp.putClientProperty(LockBorderWidget.ORIGINAL_BORDER,
+ currBorder);
+ this.jcomp.setBorder(new LockBorder(currBorder));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallDefaults()
+ */
+ @Override
+ public void uninstallDefaults() {
+ // fix for issue 7 - restoring original border on LAF switch.
+ this.isUninstalling = true;
+ this.jcomp.putClientProperty(LockBorderWidget.ORIGINAL_BORDER, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+
+ /**
+ * Installs the lock border on the associated component.
+ */
+ private void installLockBorder() {
+ if (jcomp.getClientProperty(LockBorderWidget.ORIGINAL_BORDER) instanceof Border) {
+ // already installed
+ return;
+ }
+ // need to install
+ Border currBorder = jcomp.getBorder();
+ if (currBorder != null) {
+ jcomp.putClientProperty(LockBorderWidget.ORIGINAL_BORDER,
+ currBorder);
+ jcomp.setBorder(new LockBorder(currBorder));
+ }
+ }
+
+ /**
+ * Restores the original border on the associated component.
+ */
+ private void restoreOriginalBorder() {
+ if (jcomp.getClientProperty(LockBorderWidget.ORIGINAL_BORDER) instanceof Border) {
+ // revert to original
+ Border originalBorder = (Border) jcomp
+ .getClientProperty(LockBorderWidget.ORIGINAL_BORDER);
+ jcomp.setBorder(originalBorder);
+ jcomp.putClientProperty(LockBorderWidget.ORIGINAL_BORDER, null);
+ } else {
+ // already uninstalled
+ }
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/PasswordStrengthChecker.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/PasswordStrengthChecker.java
new file mode 100644
index 0000000..82111fd
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/PasswordStrengthChecker.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.text;
+
+import org.pushingpixels.lafwidget.utils.LafConstants.PasswordStrength;
+
+/**
+ * Specifies the interface for the password strength checkers.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface PasswordStrengthChecker {
+ /**
+ * Computes the strength of the specified password.
+ *
+ * @param password
+ * Password.
+ * @return Password strength.
+ */
+ public PasswordStrength getStrength(char[] password);
+
+ /**
+ * Returns the description of the password strength. The returned value can
+ * contain HTML constructs and will be used as popup text on the
+ * strength-check enabled password fields.
+ *
+ * @param strength
+ * Password strength.
+ * @return The description of the specified password strength.
+ */
+ public String getDescription(PasswordStrength strength);
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/PasswordStrengthCheckerWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/PasswordStrengthCheckerWidget.java
new file mode 100644
index 0000000..854a560
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/PasswordStrengthCheckerWidget.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.text;
+
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JPasswordField;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.plaf.BorderUIResource;
+
+import org.pushingpixels.lafwidget.*;
+import org.pushingpixels.lafwidget.utils.LafConstants.PasswordStrength;
+
+/**
+ * Adds password strength indication on password fields.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class PasswordStrengthCheckerWidget extends
+ LafWidgetAdapter<JPasswordField> {
+ /**
+ * Listens on changes to {@link LafWidget#PASSWORD_STRENGTH_CHECKER}
+ * property.
+ */
+ protected PropertyChangeListener strengthCheckerListener;
+
+ /**
+ * Border with password strength indication.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class StrengthCheckedBorder implements Border {
+ /**
+ * Gutter width.
+ */
+ public static final int GUTTER_WIDTH = 5;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#isBorderOpaque()
+ */
+ @Override
+ public boolean isBorderOpaque() {
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#getBorderInsets(java.awt.Component)
+ */
+ @Override
+ public Insets getBorderInsets(Component c) {
+ JPasswordField jpf = (JPasswordField) c;
+ if (LafWidgetUtilities2.getPasswordStrengthChecker(jpf) == null) {
+ return new Insets(0, 0, 0, 0);
+ } else {
+ if (c.getComponentOrientation().isLeftToRight())
+ return new Insets(0, 0, 0,
+ StrengthCheckedBorder.GUTTER_WIDTH);
+ else
+ return new Insets(0, StrengthCheckedBorder.GUTTER_WIDTH, 0,
+ 0);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#paintBorder(java.awt.Component,
+ * java.awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ JPasswordField jpf = (JPasswordField) c;
+ PasswordStrengthChecker passwordStrengthChecker = LafWidgetUtilities2
+ .getPasswordStrengthChecker(jpf);
+ if (passwordStrengthChecker == null)
+ return;
+
+ PasswordStrength strength = passwordStrengthChecker.getStrength(jpf
+ .getPassword());
+ LafWidgetSupport lafSupport = LafWidgetRepository.getRepository()
+ .getLafSupport();
+ if (c.getComponentOrientation().isLeftToRight())
+ lafSupport.paintPasswordStrengthMarker(g, x + width
+ - StrengthCheckedBorder.GUTTER_WIDTH, y,
+ StrengthCheckedBorder.GUTTER_WIDTH, height, strength);
+ else
+ lafSupport.paintPasswordStrengthMarker(g, x, y,
+ StrengthCheckedBorder.GUTTER_WIDTH, height, strength);
+
+ String tooltip = passwordStrengthChecker.getDescription(strength);
+ jpf.setToolTipText(tooltip);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ this.strengthCheckerListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (LafWidget.PASSWORD_STRENGTH_CHECKER.equals(evt
+ .getPropertyName())) {
+ Object newValue = evt.getNewValue();
+ Object oldValue = evt.getOldValue();
+ if ((newValue != null)
+ && (newValue instanceof PasswordStrengthChecker)
+ && (!(oldValue instanceof PasswordStrengthChecker))) {
+ jcomp
+ .setBorder(new BorderUIResource.CompoundBorderUIResource(
+ jcomp.getBorder(),
+ new StrengthCheckedBorder()));
+ } else {
+ // restore core border
+ Border coreBorder = UIManager
+ .getBorder("PasswordField.border");
+ jcomp.setBorder(coreBorder);
+ jcomp.setToolTipText(null);
+ }
+ }
+ }
+ };
+ this.jcomp.addPropertyChangeListener(this.strengthCheckerListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ this.jcomp.removePropertyChangeListener(this.strengthCheckerListener);
+ this.strengthCheckerListener = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installDefaults()
+ */
+ @Override
+ public void installDefaults() {
+ super.installDefaults();
+
+ // check if the property is already set - can happen on LAF change
+ Object checker = this.jcomp
+ .getClientProperty(LafWidget.PASSWORD_STRENGTH_CHECKER);
+ if ((checker != null) && (checker instanceof PasswordStrengthChecker)) {
+ this.jcomp.setBorder(new BorderUIResource.CompoundBorderUIResource(
+ this.jcomp.getBorder(), new StrengthCheckedBorder()));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/SelectAllOnFocusGainWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/SelectAllOnFocusGainWidget.java
new file mode 100644
index 0000000..fa284b3
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/SelectAllOnFocusGainWidget.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.text;
+
+import java.awt.event.*;
+
+import javax.swing.SwingUtilities;
+import javax.swing.text.JTextComponent;
+
+import org.pushingpixels.lafwidget.LafWidgetAdapter;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+
+/**
+ * Adds "select all on focus gain" behaviour on text components.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SelectAllOnFocusGainWidget extends
+ LafWidgetAdapter<JTextComponent> {
+ /**
+ * The focus listener.
+ */
+ protected FocusListener focusListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ this.focusListener = new FocusAdapter() {
+ @Override
+ public void focusGained(FocusEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (LafWidgetUtilities
+ .hasTextFocusSelectAllProperty(jcomp)
+ && jcomp.isEditable())
+ jcomp.selectAll();
+ }
+ });
+ }
+ };
+ this.jcomp.addFocusListener(this.focusListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ this.jcomp.removeFocusListener(this.focusListener);
+ this.focusListener = null;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/SelectOnEscapeWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/SelectOnEscapeWidget.java
new file mode 100644
index 0000000..fa1adaa
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/text/SelectOnEscapeWidget.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.text;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.text.JTextComponent;
+
+import org.pushingpixels.lafwidget.*;
+
+/**
+ * Adds "select / deselect on Escape key press" behaviour on text components.
+ *
+ * @author Kirill Grouchnikov
+ * @since 2.1
+ */
+public class SelectOnEscapeWidget extends LafWidgetAdapter<JTextComponent> {
+ protected PropertyChangeListener propertyChangeListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+
+ private void installTracking() {
+ InputMap currMap = SwingUtilities.getUIInputMap(this.jcomp,
+ JComponent.WHEN_FOCUSED);
+
+ InputMap newMap = new InputMap();
+ if (currMap != null) {
+ KeyStroke[] kss = currMap.allKeys();
+ for (int i = 0; i < kss.length; i++) {
+ KeyStroke stroke = kss[i];
+ Object val = currMap.get(stroke);
+ newMap.put(stroke, val);
+ }
+ }
+
+ newMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
+ "flipTextSelection");
+
+ this.jcomp.getActionMap().put("flipTextSelection",
+ new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ int selectionLength = jcomp.getSelectionEnd()
+ - jcomp.getSelectionStart();
+ if (selectionLength == 0) {
+ jcomp.selectAll();
+ } else {
+ int lastPos = jcomp.getSelectionEnd();
+ jcomp.setSelectionStart(0);
+ jcomp.setSelectionEnd(0);
+ jcomp.setCaretPosition(lastPos);
+ }
+ }
+ });
+ }
+ });
+
+ SwingUtilities.replaceUIInputMap(this.jcomp, JComponent.WHEN_FOCUSED,
+ newMap);
+ }
+
+ private void uninstallTracking() {
+ InputMap currMap = SwingUtilities.getUIInputMap(this.jcomp,
+ JComponent.WHEN_FOCUSED);
+ if (currMap != null) {
+ InputMap newMap = new InputMap();
+ KeyStroke[] kss = currMap.allKeys();
+ for (int i = 0; i < kss.length; i++) {
+ KeyStroke stroke = kss[i];
+ Object val = currMap.get(stroke);
+ if (stroke
+ .equals(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0))
+ && "flipTextSelection".equals(val)) {
+ continue;
+ }
+ newMap.put(stroke, val);
+ }
+ SwingUtilities.replaceUIInputMap(this.jcomp,
+ JComponent.WHEN_FOCUSED, newMap);
+ }
+ this.jcomp.getActionMap().remove("flipTextSelection");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (LafWidget.TEXT_FLIP_SELECT_ON_ESCAPE.equals(evt
+ .getPropertyName())) {
+ boolean hasTextFlipSelection = LafWidgetUtilities
+ .hasTextFlipSelectOnEscapeProperty(jcomp);
+ if (hasTextFlipSelection) {
+ // change the input map
+ installTracking();
+ } else {
+ // remove the input map
+ uninstallTracking();
+ }
+ }
+ }
+ };
+ this.jcomp.addPropertyChangeListener(this.propertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ this.jcomp.removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/AutoScrollingTreeDropTarget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/AutoScrollingTreeDropTarget.java
new file mode 100644
index 0000000..a12fe08
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/AutoScrollingTreeDropTarget.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tree.dnd;
+
+import java.awt.Point;
+import java.awt.dnd.*;
+
+import javax.swing.*;
+
+class AutoScrollingTreeDropTarget extends DropTarget {
+ private JViewport viewport;
+ private int scrollUnits;
+ private JTree tree;
+
+ AutoScrollingTreeDropTarget(JTree aTree, DropTargetListener listener) {
+ super(aTree, DnDConstants.ACTION_COPY_OR_MOVE, listener);
+ this.viewport = (JViewport) SwingUtilities.getAncestorOfClass(
+ JViewport.class, aTree);
+ this.scrollUnits = Math.max(aTree.getRowHeight(), 16);
+ this.tree = aTree;
+ }
+
+ private Point lastDragCursorLocn = new Point(0, 0);
+
+ @Override
+ protected void updateAutoscroll(Point dragCursorLocn) {
+ if (this.lastDragCursorLocn.equals(dragCursorLocn))
+ return;
+ else
+ this.lastDragCursorLocn.setLocation(dragCursorLocn);
+ this.doAutoscroll(dragCursorLocn);
+ }
+
+ @Override
+ protected void initializeAutoscrolling(Point p) {
+ this.doAutoscroll(p);
+ }
+
+ @Override
+ protected void clearAutoscroll() {
+ }
+
+ private static final int AUTOSCROLL_MARGIN = 16;
+
+ protected void doAutoscroll(Point aPoint) {
+ if (this.viewport == null)
+ return;
+
+ Point treePosition = this.viewport.getViewPosition();
+ int vH = this.viewport.getExtentSize().height;
+ int vW = this.viewport.getExtentSize().width;
+ Point nextPoint = null;
+ if ((aPoint.y - treePosition.y) < AutoScrollingTreeDropTarget.AUTOSCROLL_MARGIN) {
+ nextPoint = new Point(treePosition.x, Math.max(treePosition.y
+ - this.scrollUnits, 0));
+ } else if (treePosition.y + vH - aPoint.y < AutoScrollingTreeDropTarget.AUTOSCROLL_MARGIN) {
+ nextPoint = new Point(treePosition.x, Math.min(aPoint.y
+ + AutoScrollingTreeDropTarget.AUTOSCROLL_MARGIN, this.tree
+ .getHeight()
+ - vH));
+ } else if (aPoint.x - treePosition.x < AutoScrollingTreeDropTarget.AUTOSCROLL_MARGIN) {
+ nextPoint = new Point(Math.max(treePosition.x
+ - AutoScrollingTreeDropTarget.AUTOSCROLL_MARGIN, 0),
+ treePosition.y);
+ } else if (treePosition.x + vW - aPoint.x < AutoScrollingTreeDropTarget.AUTOSCROLL_MARGIN) {
+ nextPoint = new Point(Math.min(treePosition.x
+ + AutoScrollingTreeDropTarget.AUTOSCROLL_MARGIN, this.tree
+ .getWidth()
+ - vW), treePosition.y);
+ }
+ if (nextPoint != null)
+ this.viewport.setViewPosition(nextPoint);
+ }
+}
\ No newline at end of file
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/DnDBorderFactory.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/DnDBorderFactory.java
new file mode 100644
index 0000000..ee5e319
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/DnDBorderFactory.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tree.dnd;
+
+import java.awt.*;
+import java.net.URL;
+
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.border.Border;
+
+/**
+ * DnDBorderFactory is responsible for creating node borders used under
+ * different drag and drop operations.
+ *
+ * @author Antonio Vieiro (antonio at antonioshome.net), $Author: kirillcool $
+ */
+class DnDBorderFactory {
+ /**
+ * DropAllowedBorder is a Border that indicates that something is being
+ * droped on top of a valid node.
+ *
+ * @author Antonio Vieiro (antonio at antonioshome.net), $Author: kirillcool $
+ */
+ static class DropAllowedBorder implements Border {
+ private static Insets insets = new Insets(0, 0, 3, 0);
+ private ImageIcon plusIcon;
+
+ /**
+ * Creates a new instance of DropAllowedBorder
+ */
+ public DropAllowedBorder() {
+ URL iconURL = DropAllowedBorder.class
+ .getResource("icons/drop-on-leaf.png");
+ if (iconURL != null)
+ this.plusIcon = new ImageIcon(iconURL);
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ int yh = y + height - 1;
+ if (this.plusIcon != null) {
+ this.plusIcon.paintIcon(c, g, x + 8, yh - 8);
+ }
+ yh -= 4;
+ g.setColor(Color.DARK_GRAY);
+ g.drawLine(x + 24, yh, x + 48, yh);
+ }
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return DropAllowedBorder.insets;
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+ }
+
+ /**
+ * OffsetBorder is a Border that contains an offset. This is used to
+ * "separate" the node under the drop.
+ *
+ * @author Antonio Vieiro (antonio at antonioshome.net), $Author: kirillcool $
+ */
+ class OffsetBorder implements Border {
+ private Insets insets = new Insets(5, 0, 0, 0);
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ // empty
+ }
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return this.insets;
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ }
+
+ /**
+ * DropOnNodeBorder is a Border that indicates that something cannot be
+ * dropped here.
+ *
+ * @author Antonio Vieiro (antonio at antonioshome.net), $Author: kirillcool $
+ */
+ class DropNotAllowedBorder implements Border {
+ private Insets insets = new Insets(0, 0, 0, 0);
+ private ImageIcon plusIcon;
+
+ /**
+ * Creates a new instance of DropOnNodeBorder
+ */
+ public DropNotAllowedBorder() {
+ URL iconURL = DnDBorderFactory.class
+ .getResource("icons/drop-not-allowed.png");
+ if (iconURL != null)
+ this.plusIcon = new ImageIcon(iconURL);
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ if (this.plusIcon != null) {
+ this.plusIcon.paintIcon(c, g, x, y);
+ }
+ // g.setColor( Color.RED );
+ // g.drawRect( x, y, width-1, height-1 );
+ }
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return this.insets;
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ }
+
+ /**
+ * Creates a new instance of DnDBorderFactory
+ */
+ public DnDBorderFactory() {
+ this.setDropAllowedBorder(new DropAllowedBorder()); // DropOnFolderBorder()
+ // );
+ this.setDropNotAllowedBorder(new DropNotAllowedBorder());
+ this.setOffsetBorder(new OffsetBorder());
+ this.setEmptyBorder(BorderFactory.createEmptyBorder());
+ }
+
+ /**
+ * Holds value of property dropAllowedBorder.
+ */
+ private Border dropAllowedBorder;
+
+ /**
+ * Getter for property dropAllowedBorder.
+ *
+ * @return Value of property dropAllowedBorder.
+ */
+ public Border getDropAllowedBorder() {
+ return this.dropAllowedBorder;
+ }
+
+ /**
+ * Setter for property dropAllowedBorder.
+ *
+ * @param dropAllowedBorder
+ * New value of property dropAllowedBorder.
+ */
+ public void setDropAllowedBorder(Border dropAllowedBorder) {
+ this.dropAllowedBorder = dropAllowedBorder;
+ }
+
+ /**
+ * Holds value of property dropNotAllowedBorder.
+ */
+ private Border dropNotAllowedBorder;
+
+ /**
+ * Getter for property dropNotAllowedBorder.
+ *
+ * @return Value of property dropNotAllowedBorder.
+ */
+ public Border getDropNotAllowedBorder() {
+ return this.dropNotAllowedBorder;
+ }
+
+ /**
+ * Setter for property dropNotAllowedBorder.
+ *
+ * @param dropNotAllowedBorder
+ * New value of property dropNotAllowedBorder.
+ */
+ public void setDropNotAllowedBorder(Border dropNotAllowedBorder) {
+ this.dropNotAllowedBorder = dropNotAllowedBorder;
+ }
+
+ /**
+ * Holds value of property offsetBorder.
+ */
+ private Border offsetBorder;
+
+ /**
+ * Getter for property offsetBorder.
+ *
+ * @return Value of property offsetBorder.
+ */
+ public Border getOffsetBorder() {
+ return this.offsetBorder;
+ }
+
+ /**
+ * Setter for property offsetBorder.
+ *
+ * @param offsetBorder
+ * New value of property offsetBorder.
+ */
+ public void setOffsetBorder(Border offsetBorder) {
+ this.offsetBorder = offsetBorder;
+ }
+
+ private Border emptyBorder;
+
+ public Border getEmptyBorder() {
+ return this.emptyBorder;
+ }
+
+ public void setEmptyBorder(Border anEmptyBorder) {
+ this.emptyBorder = anEmptyBorder;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/DnDCellRendererProxy.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/DnDCellRendererProxy.java
new file mode 100644
index 0000000..646c122
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/DnDCellRendererProxy.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tree.dnd;
+
+import java.awt.Component;
+import java.awt.Image;
+
+import javax.swing.JComponent;
+import javax.swing.JTree;
+import javax.swing.border.Border;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.tree.TreeNode;
+
+/**
+ * DnDCellRendererProxy is a TreeCellRenderer that proxies operations to a true
+ * TreeCellRenderer, but that draws a border around specific TreeNodes.
+ *
+ * @author Antonio Vieiro (antonio at antonioshome.net), $Author: kirillcool $
+ */
+class DnDCellRendererProxy extends Component implements TreeCellRenderer {
+ private TreeCellRenderer originalTreeCellRenderer;
+ private DnDBorderFactory borderFactory;
+ private TreeNode draggedNode;
+ private TreeNode dropNode;
+ private int dropNodeRow;
+ private Image shadowImage;
+ private boolean fetchBorder;
+ private Border originalBorder;
+
+ /**
+ * Creates a new instance of DragAndDropCellRenderer.
+ *
+ * @param trueCellRenderer
+ * the original cell renderer.
+ */
+ public DnDCellRendererProxy(TreeCellRenderer trueCellRenderer) {
+ this.originalTreeCellRenderer = trueCellRenderer;
+ this.borderFactory = new DnDBorderFactory();
+ this.fetchBorder = true;
+ }
+
+ public TreeCellRenderer getOriginalTreeCellRenderer() {
+ return this.originalTreeCellRenderer;
+ }
+
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree, Object value,
+ boolean selected, boolean expanded, boolean leaf, int row,
+ boolean hasFocus) {
+ Component c = this.originalTreeCellRenderer
+ .getTreeCellRendererComponent(tree, value, selected, expanded,
+ leaf, row, hasFocus);
+
+ TreeNode nodeToRender = (TreeNode) value;
+
+ if (c instanceof JComponent) {
+ if (this.fetchBorder) {
+ this.fetchBorder = false;
+ this.originalBorder = ((JComponent) c).getBorder();
+ }
+ // TODO: This *REMOVES* the border in c.
+ // TODO: Use compound borders to draw BOTH borders.
+ JComponent jComponent = (JComponent) c;
+ if (nodeToRender.equals(this.dropNode)) {
+ Border border = null;
+ if (this.isDropAllowed()) {
+ border = this.borderFactory.getDropAllowedBorder();
+ this.dropNodeRow = row;
+ } else {
+ border = this.borderFactory.getDropNotAllowedBorder();
+ this.dropNodeRow = -2;
+ }
+ jComponent.setBorder(border);
+ } else if (this.isDropAllowed() && (row == this.dropNodeRow + 1)) {
+ jComponent.setBorder(this.borderFactory.getOffsetBorder());
+ } else {
+ jComponent.setBorder(this.originalBorder);
+ this.dropNodeRow = -2;
+ }
+ }
+ return c;
+ }
+
+ /**
+ * Getter for property draggedNode.
+ *
+ * @return Value of property draggedNode.
+ */
+ public TreeNode getDraggedNode() {
+ return this.draggedNode;
+ }
+
+ /**
+ * Setter for property draggedNode.
+ *
+ * @param draggedNode
+ * New value of property draggedNode.
+ */
+ public void setDraggedNode(TreeNode draggedNode) {
+ this.draggedNode = draggedNode;
+ }
+
+ /**
+ * Getter for property dropNode.
+ *
+ * @return Value of property dropNode.
+ */
+ public TreeNode getDropNode() {
+ return this.dropNode;
+ }
+
+ /**
+ * Setter for property dropNode.
+ *
+ * @param dropNode
+ * New value of property dropNode.
+ */
+ public void setDropNode(TreeNode dropNode) {
+ this.dropNode = dropNode;
+ if (dropNode == null)
+ this.dropNodeRow = -2;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("[DnDCellRendererProxy for : ").append(
+ this.originalTreeCellRenderer).append("]");
+ return sb.toString();
+ }
+
+ /**
+ * Holds value of property dropAllowed.
+ */
+ private boolean dropAllowed;
+
+ /**
+ * Getter for property dropAllowed.
+ *
+ * @return Value of property dropAllowed.
+ */
+ public boolean isDropAllowed() {
+ return this.dropAllowed;
+ }
+
+ /**
+ * Setter for property dropAllowed.
+ *
+ * @param dropAllowed
+ * New value of property dropAllowed.
+ */
+ public void setDropAllowed(boolean dropAllowed) {
+ this.dropAllowed = dropAllowed;
+ if (!dropAllowed)
+ this.dropNodeRow = -2;
+ }
+
+ public void setShadowImage(Image anImage) {
+ this.shadowImage = anImage;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/DnDVetoException.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/DnDVetoException.java
new file mode 100644
index 0000000..ce9edea
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/DnDVetoException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tree.dnd;
+
+/**
+ * DnDVetoException is an exception thrown to signal that a drag and drop
+ * operation is not valid.
+ *
+ * @author Antonio Vieiro (antonio at antonioshome.net), $Author: kirillcool $
+ */
+public class DnDVetoException extends Exception {
+ /**
+ * Creates a new instance of DnDVetoException
+ *
+ * @param aMessage
+ * the message to show.
+ */
+ public DnDVetoException(String aMessage) {
+ super(aMessage);
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/StringTreeDnDEvent.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/StringTreeDnDEvent.java
new file mode 100644
index 0000000..18b6a88
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/StringTreeDnDEvent.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tree.dnd;
+
+import java.util.EventObject;
+
+import javax.swing.JTree;
+import javax.swing.tree.TreeNode;
+
+/**
+ * StringTreeDnDEvent is an event fired whenever a String is about to be dropped
+ * into a node in a JTree.
+ *
+ * @author Antonio Vieiro (antonio at antonioshome.net), $Author: kirillcool $
+ */
+public class StringTreeDnDEvent extends EventObject {
+ private JTree targetTree;
+ private TreeNode targetNode;
+ private String sourceString;
+
+ /**
+ * Creates a new instance of StringTreeDnDEvent
+ *
+ * @param aSourceString
+ * the String being dragged.
+ * @param aTargetTree
+ * the JTree containing the node.
+ * @param aTargetNode
+ * the node onto which the String is about to be dropped into.
+ */
+ public StringTreeDnDEvent(String aSourceString, JTree aTargetTree,
+ TreeNode aTargetNode) {
+ super(aSourceString);
+ this.setSourceString(aSourceString);
+ this.setTargetTree(aTargetTree);
+ this.setTargetNode(aTargetNode);
+ }
+
+ public JTree getTargetTree() {
+ return this.targetTree;
+ }
+
+ public void setTargetTree(JTree targetTree) {
+ this.targetTree = targetTree;
+ }
+
+ public TreeNode getTargetNode() {
+ return this.targetNode;
+ }
+
+ public void setTargetNode(TreeNode targetNode) {
+ this.targetNode = targetNode;
+ }
+
+ public String getSourceString() {
+ return this.sourceString;
+ }
+
+ public void setSourceString(String sourceString) {
+ this.sourceString = sourceString;
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/StringTreeDnDListener.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/StringTreeDnDListener.java
new file mode 100644
index 0000000..63cde32
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/StringTreeDnDListener.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tree.dnd;
+
+import java.util.EventListener;
+
+/**
+ * StringTreeDnDListener represents a listener that is informed when a String is
+ * about to be dropped into a node in a JTree.
+ *
+ * @author Antonio Vieiro (antonio at antonioshome.net), $Author: kirillcool $
+ */
+public interface StringTreeDnDListener extends EventListener {
+ /**
+ * Invoked to verify that aSourceString may be dropped into aTargetNode
+ * inside aTargetTree.
+ *
+ * @param anEvent
+ * a StringTreeDnDEvent containing information about the data
+ * being dropped.
+ * @throws DnDVetoException
+ * if the drag and drop operation is not valid.
+ */
+ public void mayDrop(StringTreeDnDEvent anEvent) throws DnDVetoException;
+
+ /**
+ * Invoked when the drop operation happens.
+ *
+ * @param anEvent
+ * a StringTreeDnDEvent the event containing information about
+ * the Drag and Drop operation.
+ * @throws DnDVetoException
+ * if the drag and drop operation is not valid.
+ */
+ public void drop(StringTreeDnDEvent anEvent) throws DnDVetoException;
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/TransferableTreeNode.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/TransferableTreeNode.java
new file mode 100644
index 0000000..936bb85
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/TransferableTreeNode.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tree.dnd;
+
+import java.awt.datatransfer.*;
+import java.io.IOException;
+
+import javax.swing.JTree;
+import javax.swing.tree.MutableTreeNode;
+
+/**
+ * TransferableTreeNode is a Transferable object used to transfer TreeNodes or
+ * Strings in drag and drop operations.
+ *
+ * @author Antonio Vieiro (antonio at antonioshome.net), $Author: kirillcool $
+ */
+class TransferableTreeNode implements Transferable {
+ /**
+ * The local JVM DataFlavor.
+ */
+ private static DataFlavor javaJVMLocalObjectFlavor;
+ /**
+ * The supported data flavors.
+ */
+ private static DataFlavor[] supportedDataFlavors;
+
+ /**
+ * Returns the Java JVM LocalObject Flavor.
+ */
+ public static DataFlavor getJavaJVMLocalObjectFlavor() {
+ if (TransferableTreeNode.javaJVMLocalObjectFlavor == null) {
+ try {
+ TransferableTreeNode.javaJVMLocalObjectFlavor = new DataFlavor(
+ DataFlavor.javaJVMLocalObjectMimeType);
+ } catch (ClassNotFoundException cnfe) {
+ System.err.println("Cannot create JVM Local Object Flavor "
+ + cnfe.getMessage());
+ }
+ }
+ return TransferableTreeNode.javaJVMLocalObjectFlavor;
+ }
+
+ /**
+ * Returns the supported data flavors.
+ */
+ private static DataFlavor[] getSupportedDataFlavors() {
+ if (TransferableTreeNode.supportedDataFlavors == null) {
+ DataFlavor localJVMFlavor = TransferableTreeNode
+ .getJavaJVMLocalObjectFlavor();
+ TransferableTreeNode.supportedDataFlavors = localJVMFlavor == null ? new DataFlavor[] { DataFlavor.stringFlavor }
+ : new DataFlavor[] { localJVMFlavor,
+ DataFlavor.stringFlavor };
+ }
+ return TransferableTreeNode.supportedDataFlavors;
+ }
+
+ /**
+ * Creates a new instance of TransferableTreeNode.
+ *
+ * @param aTree
+ * the JTree that contains de dragged node.
+ * @param aNode
+ * the MutableTreeNode in JTree that is to be dragged.
+ * @param wasExpanded
+ * true if the source node was expanded, false otherwise.
+ */
+ public TransferableTreeNode(JTree aTree, MutableTreeNode aNode,
+ boolean wasExpanded) {
+ this.setSourceTree(aTree);
+ this.setSourceNode(aNode);
+ this.setNodeWasExpanded(wasExpanded);
+ }
+
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ DataFlavor[] flavors = TransferableTreeNode.getSupportedDataFlavors();
+ for (int i = 0; i < flavors.length; i++) {
+ if (flavor.equals(flavors[i]))
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Object getTransferData(DataFlavor flavor)
+ throws UnsupportedFlavorException, IOException {
+ if (flavor.equals(TransferableTreeNode.javaJVMLocalObjectFlavor)) {
+ return this;
+ } else if (flavor.equals(DataFlavor.stringFlavor)) {
+ return this.getSourceNode().toString();
+ } else
+ throw new UnsupportedFlavorException(flavor);
+ }
+
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ return TransferableTreeNode.getSupportedDataFlavors();
+ }
+
+ /**
+ * Holds value of property sourceTree.
+ */
+ private JTree sourceTree;
+
+ /**
+ * Getter for property sourceTree.
+ *
+ * @return Value of property sourceTree.
+ */
+ public JTree getSourceTree() {
+ return this.sourceTree;
+ }
+
+ /**
+ * Setter for property sourceTree.
+ *
+ * @param sourceTree
+ * New value of property sourceTree.
+ */
+ public void setSourceTree(JTree sourceTree) {
+ this.sourceTree = sourceTree;
+ }
+
+ /**
+ * Holds value of property sourceNode.
+ */
+ private MutableTreeNode sourceNode;
+
+ /**
+ * Getter for property sourceNode.
+ *
+ * @return Value of property sourceNode.
+ */
+ public MutableTreeNode getSourceNode() {
+ return this.sourceNode;
+ }
+
+ /**
+ * Setter for property sourceNode.
+ *
+ * @param sourceNode
+ * New value of property sourceNode.
+ */
+ public void setSourceNode(MutableTreeNode sourceNode) {
+ this.sourceNode = sourceNode;
+ }
+
+ /**
+ * Holds value of property nodeWasExpanded.
+ */
+ private boolean nodeWasExpanded;
+
+ /**
+ * Getter for property nodeWasExpanded.
+ *
+ * @return Value of property nodeWasExpanded.
+ */
+ public boolean isNodeWasExpanded() {
+ return this.nodeWasExpanded;
+ }
+
+ /**
+ * Setter for property nodeWasExpanded.
+ *
+ * @param nodeWasExpanded
+ * New value of property nodeWasExpanded.
+ */
+ public void setNodeWasExpanded(boolean nodeWasExpanded) {
+ this.nodeWasExpanded = nodeWasExpanded;
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/TreeDragAndDropWidget.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/TreeDragAndDropWidget.java
new file mode 100644
index 0000000..20973c0
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/TreeDragAndDropWidget.java
@@ -0,0 +1,776 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tree.dnd;
+
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.*;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.EventListener;
+
+import javax.swing.JTree;
+import javax.swing.event.EventListenerList;
+import javax.swing.tree.*;
+
+import org.pushingpixels.lafwidget.*;
+
+/**
+ * TreeWrapper is used to handle drag and drop and popup menus in any JTree.
+ *
+ * @author Antonio Vieiro (antonio at antonioshome.net), $Author: kirillcool $
+ */
+public class TreeDragAndDropWidget extends LafWidgetAdapter<JTree> {
+ /**
+ * This to avoid excesive creation of objects in invocation.
+ */
+ private static Class[] EMPTY_CLASS_ARRAY = new Class[0];
+
+ /**
+ * This to avoid excessive reflection to find the "getTransferable" method.
+ */
+ private static Method getTransferableMethod = null;
+
+ /**
+ * This to avoid excesive creation of objects in invocation.
+ */
+ private static Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+
+ protected DnDCellRendererProxy rendererProxy;
+
+ protected DragSource dragSource;
+
+ protected DropTarget dropTarget;
+
+ protected MutableTreeNode dropNode;
+
+ protected EventListenerList listeners;
+
+ protected PropertyChangeListener propertyChangeListener;
+
+ protected PropertyChangeListener cellRendererChangeListener;
+
+ protected TreeDropTargetListener dropListener;
+
+ protected TreeDragGestureListener gestureListener;
+
+ protected DragGestureRecognizer dragGestureRecognizer;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidget#requiresCustomLafSupport()
+ */
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#installListeners()
+ */
+ @Override
+ public void installListeners() {
+ this.listeners = new EventListenerList();
+
+ this.propertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (LafWidget.TREE_AUTO_DND_SUPPORT.equals(evt
+ .getPropertyName())) {
+ Object oldValue = evt.getOldValue();
+ Object newValue = evt.getNewValue();
+ boolean hadDnd = false;
+ if (oldValue instanceof Boolean)
+ hadDnd = (Boolean) oldValue;
+ boolean hasDnd = false;
+ if (newValue instanceof Boolean)
+ hasDnd = (Boolean) newValue;
+
+ if (!hadDnd && hasDnd
+ && TreeDragAndDropWidget.this.jcomp.isEnabled()) {
+ TreeDragAndDropWidget.this.installDnDSupport();
+ }
+ if (hadDnd && !hasDnd) {
+ TreeDragAndDropWidget.this.uninstallDnDSupport();
+ }
+ }
+
+ if ("enabled".equals(evt.getPropertyName())) {
+ boolean wasEnabled = (Boolean) evt.getOldValue();
+ boolean isEnabled = (Boolean) evt.getNewValue();
+ if (!wasEnabled && isEnabled) {
+ if (LafWidgetUtilities
+ .hasAutomaticDnDSupport(TreeDragAndDropWidget.this.jcomp))
+ TreeDragAndDropWidget.this.installDnDSupport();
+ }
+ if (wasEnabled && !isEnabled)
+ TreeDragAndDropWidget.this.uninstallDnDSupport();
+ }
+ }
+ };
+ this.jcomp.addPropertyChangeListener(this.propertyChangeListener);
+
+ if (this.jcomp.isEnabled()
+ && LafWidgetUtilities.hasAutomaticDnDSupport(this.jcomp))
+ this.installDnDSupport();
+ }
+
+ private void installDnDSupport() {
+ if (this.cellRendererChangeListener != null) {
+ // already installed.
+ return;
+ }
+
+ this.cellRendererChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ String name = evt.getPropertyName();
+
+ if (name.equals(JTree.CELL_RENDERER_PROPERTY)) {
+ TreeCellRenderer renderer = TreeDragAndDropWidget.this.jcomp
+ .getCellRenderer();
+ if (!(renderer instanceof DnDCellRendererProxy)) {
+ TreeDragAndDropWidget.this.rendererProxy = new DnDCellRendererProxy(
+ renderer);
+ TreeDragAndDropWidget.this.jcomp
+ .setCellRenderer(TreeDragAndDropWidget.this.rendererProxy);
+ TreeDragAndDropWidget.this.jcomp.repaint();
+ } else
+ TreeDragAndDropWidget.this.rendererProxy = (DnDCellRendererProxy) renderer;
+ }
+ }
+ };
+
+ this.jcomp.addPropertyChangeListener(this.cellRendererChangeListener);
+ if (this.jcomp.getCellRenderer() != null) {
+ this.jcomp.setCellRenderer(new DnDCellRendererProxy(this.jcomp
+ .getCellRenderer()));
+ }
+ this.dragSource = new DragSource();
+ this.gestureListener = new TreeDragGestureListener();
+ this.dragGestureRecognizer = this.dragSource
+ .createDefaultDragGestureRecognizer(this.jcomp,
+ DnDConstants.ACTION_COPY_OR_MOVE, this.gestureListener);
+ this.dropListener = new TreeDropTargetListener();
+ this.dropTarget = new AutoScrollingTreeDropTarget(this.jcomp,
+ this.dropListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetAdapter#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ this.jcomp.removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+
+ this.uninstallDnDSupport();
+ }
+
+ private void uninstallDnDSupport() {
+ if (this.cellRendererChangeListener != null) {
+ this.jcomp
+ .removePropertyChangeListener(this.cellRendererChangeListener);
+ this.cellRendererChangeListener = null;
+ }
+
+ TreeCellRenderer tcl = this.jcomp.getCellRenderer();
+ if (tcl instanceof DnDCellRendererProxy) {
+ TreeCellRenderer origRenderer = ((DnDCellRendererProxy) tcl)
+ .getOriginalTreeCellRenderer();
+ this.jcomp.setCellRenderer(origRenderer);
+ }
+
+ if (this.dropListener != null) {
+ this.dropTarget.removeDropTargetListener(this.dropListener);
+ this.dropListener = null;
+ this.jcomp.setDropTarget(null);
+ }
+
+ if (this.dragGestureRecognizer != null) {
+ this.dragGestureRecognizer
+ .removeDragGestureListener(this.gestureListener);
+ this.gestureListener = null;
+ this.dragGestureRecognizer = null;
+ }
+ }
+
+ /**
+ * Internal class that implements DragGestureListener.
+ */
+ class TreeDragGestureListener implements DragGestureListener {
+ @Override
+ public void dragGestureRecognized(DragGestureEvent dge) {
+ // If tree is disabled then discard drag from it
+ if (!TreeDragAndDropWidget.this.jcomp.isEnabled())
+ return;
+
+ // Select the node that the user is trying to drag, if any.
+ TreePath draggedPath = TreeDragAndDropWidget.this.jcomp
+ .getClosestPathForLocation(dge.getDragOrigin().x, dge
+ .getDragOrigin().y);
+ if (draggedPath == null)
+ return;
+ TreeNode node = (TreeNode) draggedPath.getLastPathComponent();
+ if ((node instanceof MutableTreeNode) && (node.getParent() != null)
+ && (node.getParent() instanceof MutableTreeNode))
+ ;
+ else
+ return;
+
+ // Prepare the node for transfer
+ TransferableTreeNode transferableNode = new TransferableTreeNode(
+ TreeDragAndDropWidget.this.jcomp, (MutableTreeNode) node,
+ TreeDragAndDropWidget.this.jcomp.isExpanded(draggedPath));
+ TreeDragAndDropWidget.this.rendererProxy.setDraggedNode(node);
+
+ // Initialize the drag. If isDragImageSupported then build a
+ // transparent image
+ BufferedImage image = null;
+ Point imageOffset = null;
+ // Create an image with the dragged node.
+ TreeCellRenderer renderer = TreeDragAndDropWidget.this.rendererProxy
+ .getOriginalTreeCellRenderer();
+ Rectangle dragBounds = TreeDragAndDropWidget.this.jcomp
+ .getPathBounds(draggedPath);
+ imageOffset = new Point(dge.getDragOrigin().x - dragBounds.x, dge
+ .getDragOrigin().y
+ - dragBounds.y);
+ Component component = renderer.getTreeCellRendererComponent(
+ TreeDragAndDropWidget.this.jcomp, node, false,
+ TreeDragAndDropWidget.this.jcomp.isExpanded(draggedPath),
+ node.isLeaf(), 0, false);
+ component.setSize(dragBounds.width, dragBounds.height);
+ image = new BufferedImage(dragBounds.width, dragBounds.height,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = image.createGraphics();
+ g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC,
+ 0.75f));
+ component.paint(g2d);
+ g2d.dispose();
+
+ // Initiate the drag
+ if (DragSource.isDragImageSupported()) {
+ TreeDragAndDropWidget.this.dragSource.startDrag(dge, null,
+ image, imageOffset, transferableNode,
+ new TreeDragSourceListener());
+ } else {
+ TreeDragAndDropWidget.this.dragSource.startDrag(dge, null,
+ transferableNode, new TreeDragSourceListener());
+ }
+ }
+ }
+
+ /**
+ * Internal class that implements DragSourceListener.
+ */
+ class TreeDragSourceListener implements DragSourceListener {
+ @Override
+ public void dragExit(DragSourceEvent dse) {
+ // dropNode = null;
+ // rendererProxy.setDropNode( null );
+ // tree.repaint();
+ }
+
+ @Override
+ public void dropActionChanged(DragSourceDragEvent dsde) {
+ }
+
+ @Override
+ public void dragOver(DragSourceDragEvent dsde) {
+ }
+
+ @Override
+ public void dragEnter(DragSourceDragEvent dsde) {
+ }
+
+ @Override
+ public void dragDropEnd(DragSourceDropEvent dsde) {
+ TreeDragAndDropWidget.this.resetDragAndDrop();
+ }
+ }
+
+ class TreeDropTargetListener implements DropTargetListener {
+ @Override
+ public void drop(DropTargetDropEvent dtde) {
+ TreePath dropPath = TreeDragAndDropWidget.this.jcomp
+ .getClosestPathForLocation(dtde.getLocation().x, dtde
+ .getLocation().y);
+
+ if (!TreeDragAndDropWidget.this.jcomp.isEnabled()
+ || (dropPath == null)) {
+ dtde.rejectDrop();
+ dtde.dropComplete(false);
+ TreeDragAndDropWidget.this.resetDragAndDrop();
+ return;
+ }
+
+ TreeDragAndDropWidget.this.dropNode = (MutableTreeNode) dropPath
+ .getLastPathComponent();
+
+ // Handle dropping java JVM local objects (tree nodes)
+ try {
+ dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+ // Are we dropping a JVM local object?
+ TransferableTreeNode ttn = (TransferableTreeNode) dtde
+ .getTransferable().getTransferData(
+ TransferableTreeNode
+ .getJavaJVMLocalObjectFlavor());
+ JTree sourceTree = ttn.getSourceTree();
+ MutableTreeNode sourceNode = ttn.getSourceNode();
+ if (TreeDragAndDropWidget.this.mayDropHere(sourceTree,
+ sourceNode, TreeDragAndDropWidget.this.dropNode)) {
+ dtde
+ .dropComplete(this
+ .dropNodes(
+ ttn.getSourceTree(),
+ ttn.getSourceNode(),
+ TreeDragAndDropWidget.this.jcomp,
+ TreeDragAndDropWidget.this.dropNode,
+ (dtde.getDropAction() & DnDConstants.ACTION_MOVE) != 0));
+ if (ttn.isNodeWasExpanded()) {
+ DefaultTreeModel targetModel = (DefaultTreeModel) TreeDragAndDropWidget.this.jcomp
+ .getModel();
+ TreeDragAndDropWidget.this.jcomp
+ .expandPath(new TreePath(targetModel
+ .getPathToRoot(ttn.getSourceNode())));
+ }
+
+ TreeDragAndDropWidget.this.resetDragAndDrop();
+ } else {
+ try {
+ dtde.rejectDrop();
+ } catch (Exception e) {
+ // An exception may be thrown here if the user leaves
+ // and enters the window.
+ // This is an exceptional case.
+ }
+ dtde.dropComplete(false);
+ TreeDragAndDropWidget.this.resetDragAndDrop();
+ return;
+ }
+ } catch (UnsupportedFlavorException ufe) {
+ // So this is not a JVM local object, maybe it's a String...
+ try {
+ String droppedString = (String) dtde.getTransferable()
+ .getTransferData(DataFlavor.stringFlavor);
+ if (TreeDragAndDropWidget.this.mayDropHere(droppedString,
+ TreeDragAndDropWidget.this.jcomp, dropPath)) {
+ dtde.dropComplete(this.dropString(droppedString));
+ TreeDragAndDropWidget.this.resetDragAndDrop();
+ } else {
+ dtde.rejectDrop();
+ dtde.dropComplete(false);
+ TreeDragAndDropWidget.this.resetDragAndDrop();
+ return;
+ }
+ } catch (Exception exception) {
+ dtde.rejectDrop();
+ dtde.dropComplete(false);
+ TreeDragAndDropWidget.this.resetDragAndDrop();
+ return;
+ }
+ } catch (Exception e) {
+ // ClassCastException: So this is a JVM local object but not a
+ // TransferableTreeNode, right? Well, then just discard
+ // IOException: So there's a problem deserializing this, right?
+ // Well, then just discard
+ e.printStackTrace();
+ dtde.rejectDrop();
+ dtde.dropComplete(true);
+ TreeDragAndDropWidget.this.resetDragAndDrop();
+ return;
+ }
+ }
+
+ private boolean dropString(String droppedString) {
+ // Get the mutable TreeModel
+ DefaultTreeModel model = (DefaultTreeModel) TreeDragAndDropWidget.this.jcomp
+ .getModel();
+
+ // Ask the listeners to handle this drop
+ boolean doItOurselves = true;
+ EventListener[] listeners = TreeDragAndDropWidget.this.listeners
+ .getListeners(StringTreeDnDListener.class);
+ if ((listeners != null) && (listeners.length > 0)) {
+ try {
+ StringTreeDnDEvent event = new StringTreeDnDEvent(
+ droppedString, TreeDragAndDropWidget.this.jcomp,
+ TreeDragAndDropWidget.this.dropNode);
+ for (int i = 0; i < listeners.length; i++) {
+ ((StringTreeDnDListener) listeners[i]).drop(event);
+ }
+ } catch (DnDVetoException exception) {
+ doItOurselves = true;
+ }
+ }
+ if (doItOurselves) {
+ DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(
+ droppedString);
+
+ MutableTreeNode parent = (MutableTreeNode) TreeDragAndDropWidget.this.dropNode
+ .getParent();
+ if (TreeDragAndDropWidget.this.dropNode.isLeaf()) {
+ int index = parent
+ .getIndex(TreeDragAndDropWidget.this.dropNode);
+ model.insertNodeInto(newNode, parent, index + 1);
+ } else {
+ model.insertNodeInto(newNode,
+ TreeDragAndDropWidget.this.dropNode, 0); // dropNode.getChildCount()
+ // );
+ }
+ }
+ return true;
+ }
+
+ private boolean dropNodes(JTree aSourceTree,
+ MutableTreeNode aSourceNode, JTree aTargetTree,
+ MutableTreeNode aDropNode, boolean move) {
+ // Get the mutable TreeModel
+ DefaultTreeModel sourceModel = (DefaultTreeModel) aSourceTree
+ .getModel();
+ DefaultTreeModel targetModel = (DefaultTreeModel) aTargetTree
+ .getModel();
+
+ boolean doItOurselves = true;
+ EventListener[] listeners = TreeDragAndDropWidget.this.listeners
+ .getListeners(TreeTreeDnDListener.class);
+ if ((listeners != null) && (listeners.length > 0)) {
+ try {
+ TreeTreeDnDEvent event = new TreeTreeDnDEvent(aSourceTree,
+ aSourceNode, aTargetTree, aDropNode);
+ for (int i = 0; i < listeners.length; i++) {
+ ((TreeTreeDnDListener) listeners[i]).drop(event);
+ }
+ } catch (DnDVetoException exception) {
+ doItOurselves = false;
+ }
+ }
+ if (doItOurselves) {
+ MutableTreeNode sourceNodeCopy = aSourceNode;
+ if (move) {
+ sourceModel.removeNodeFromParent(aSourceNode);
+ } else {
+ sourceNodeCopy = this.recursivelyCopyNodes(targetModel,
+ aSourceNode);
+ }
+ // Attach the draggedNode into the new parent
+ MutableTreeNode parent = (MutableTreeNode) aDropNode
+ .getParent();
+ if (aDropNode.isLeaf() && (parent != null)) {
+ int index = parent.getIndex(aDropNode);
+ targetModel.insertNodeInto(sourceNodeCopy, parent,
+ index + 1);
+ } else {
+ targetModel.insertNodeInto(sourceNodeCopy, aDropNode, 0);// aDropNode.getChildCount()
+ // );
+ }
+ }
+ return true;
+ }
+
+ private DefaultMutableTreeNode recursivelyCopyNodes(
+ DefaultTreeModel aModel, TreeNode aNode) {
+ DefaultMutableTreeNode copy = new DefaultMutableTreeNode(aNode
+ .toString());
+ copy.setAllowsChildren(aNode.getAllowsChildren());
+ if (aNode.getChildCount() != 0) {
+ Enumeration children = aNode.children();
+ while (children.hasMoreElements()) {
+ TreeNode child = (TreeNode) children.nextElement();
+ DefaultMutableTreeNode childCopy = this
+ .recursivelyCopyNodes(aModel, child);
+ aModel
+ .insertNodeInto(childCopy, copy, copy
+ .getChildCount());
+ childCopy.setParent(copy);
+ }
+ }
+ return copy;
+ }
+
+ @Override
+ public void dragExit(DropTargetEvent dte) {
+ TreeDragAndDropWidget.this.dropNode = null;
+ TreeDragAndDropWidget.this.rendererProxy.setDropNode(null);
+ TreeDragAndDropWidget.this.jcomp.repaint();
+ }
+
+ @Override
+ public void dropActionChanged(DropTargetDragEvent dtde) {
+ }
+
+ private Transferable getTransferable(DropTargetDragEvent dtde) {
+ try {
+ DropTargetContext context = dtde.getDropTargetContext();
+ if (TreeDragAndDropWidget.getTransferableMethod == null) {
+ TreeDragAndDropWidget.getTransferableMethod = context
+ .getClass().getDeclaredMethod("getTransferable",
+ TreeDragAndDropWidget.EMPTY_CLASS_ARRAY);
+ TreeDragAndDropWidget.getTransferableMethod
+ .setAccessible(true);
+ }
+ return (Transferable) TreeDragAndDropWidget.getTransferableMethod
+ .invoke(context,
+ TreeDragAndDropWidget.EMPTY_OBJECT_ARRAY);
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ return null;
+ }
+ }
+
+ /** This node to avoid too many invocations to dragOver */
+ private TreeNode lastDragOverNode = null;
+
+ @Override
+ public void dragOver(DropTargetDragEvent dtde) {
+ if (!TreeDragAndDropWidget.this.jcomp.isEnabled()) {
+ dtde.rejectDrag();
+ return;
+ }
+
+ // Is this a valid node for dropping?
+ TreePath dropPath = TreeDragAndDropWidget.this.jcomp
+ .getClosestPathForLocation(dtde.getLocation().x, dtde
+ .getLocation().y);
+
+ TreeNode currentDropNode = (TreeNode) dropPath
+ .getLastPathComponent();
+
+ if ((dropPath == null) || (currentDropNode == null)
+ || currentDropNode.equals(this.lastDragOverNode)) {
+ return;
+ } else {
+ this.lastDragOverNode = currentDropNode;
+ }
+
+ Transferable transferable = this.getTransferable(dtde); // dtde.getTransferable();
+
+ boolean mayDropHere = false;
+
+ try {
+ // WARNING: getTransferable available only on JDK 1.5
+ TransferableTreeNode transferredNode = (TransferableTreeNode) transferable
+ .getTransferData(TransferableTreeNode
+ .getJavaJVMLocalObjectFlavor());
+ JTree sourceTree = transferredNode.getSourceTree();
+ MutableTreeNode sourceNode = transferredNode.getSourceNode();
+ if (TreeDragAndDropWidget.this.mayDropHere(sourceTree,
+ sourceNode, dropPath)) {
+ TreeDragAndDropWidget.this.dropNode = (MutableTreeNode) dropPath
+ .getLastPathComponent();
+ if (!TreeDragAndDropWidget.this.jcomp.isExpanded(dropPath))
+ TreeDragAndDropWidget.this.jcomp.expandPath(dropPath);
+ mayDropHere = true;
+ } else {
+ TreeDragAndDropWidget.this.dropNode = null;
+ }
+ } catch (UnsupportedFlavorException ufe) {
+ // Oh, this is not a TransferableTreeNode, so maybe is a String,
+ // maybe?
+ try {
+ String sourceText = (String) transferable
+ .getTransferData(DataFlavor.stringFlavor);
+ if (TreeDragAndDropWidget.this.mayDropHere(sourceText,
+ TreeDragAndDropWidget.this.jcomp, dropPath)) {
+ TreeDragAndDropWidget.this.dropNode = (MutableTreeNode) dropPath
+ .getLastPathComponent();
+ if (!TreeDragAndDropWidget.this.jcomp
+ .isExpanded(dropPath))
+ TreeDragAndDropWidget.this.jcomp
+ .expandPath(dropPath);
+ mayDropHere = true;
+ } else {
+ TreeDragAndDropWidget.this.dropNode = null;
+ }
+ } catch (Exception e) {
+ // Well, whatever, just discard
+ TreeDragAndDropWidget.this.dropNode = null;
+ }
+ } catch (Exception e) {
+ // IOException: Oh, there's a problem with serialization. Maybe
+ // a classloader issue? Well, ummm... just discard this and say
+ // no
+ // ClassCastException: Oh, user is transferring a JVM object but
+ // not a TransferableTreeNode, well, umm... just discard and say
+ // no
+ TreeDragAndDropWidget.this.dropNode = null;
+ }
+ TreeDragAndDropWidget.this.rendererProxy
+ .setDropAllowed(mayDropHere);
+ TreeDragAndDropWidget.this.rendererProxy
+ .setDropNode((TreeNode) dropPath.getLastPathComponent());
+ TreeDragAndDropWidget.this.jcomp.repaint();
+ if (!mayDropHere) {
+ dtde.rejectDrag();
+ } else {
+ dtde.acceptDrag(dtde.getDropAction());
+ }
+ TreeDragAndDropWidget.this.jcomp.repaint();
+ }
+
+ @Override
+ public void dragEnter(DropTargetDragEvent dtde) {
+ this.dragOver(dtde);
+ // dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE );
+ }
+ }
+
+ /**
+ * Invoked to decide if a given String can be dropped in the last path
+ * component of the given path.
+ *
+ * @param aSourceString
+ * the String being dragged.
+ * @param aPath
+ * the path to drop into.
+ * @return true to allow the drop operation, false otherwise.
+ */
+ private boolean mayDropHere(String aSourceString, JTree aTargetTree,
+ TreePath aPath) {
+ return this.mayDropHere(aSourceString, aTargetTree, (TreeNode) aPath
+ .getLastPathComponent());
+ }
+
+ /**
+ * Invoked to decide if a given String can be dropped in the last path
+ * component of the given path.
+ *
+ * @param aSourceString
+ * the String being dragged.
+ * @return true to allow the drop operation, false otherwise.
+ */
+ private boolean mayDropHere(String aSourceString, JTree aTargetTree,
+ TreeNode aNode) {
+ EventListener[] listeners = this.listeners
+ .getListeners(StringTreeDnDListener.class);
+ if ((listeners != null) && (listeners.length > 0)) {
+ try {
+ StringTreeDnDEvent event = new StringTreeDnDEvent(
+ aSourceString, aTargetTree, aNode);
+ for (int i = 0; i < listeners.length; i++) {
+ ((StringTreeDnDListener) listeners[i]).mayDrop(event);
+ }
+ } catch (DnDVetoException exception) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Invoked to decide if draggedNode can be dropped in the last path
+ * component of the given path.
+ *
+ * @param aSourceTree
+ * the source tree.
+ * @param aSourceNode
+ * the source node.
+ * @param aPath
+ * the path to drop into.
+ * @return true to allow the drop operation, false otherwise.
+ */
+ private boolean mayDropHere(JTree aSourceTree, MutableTreeNode aSourceNode,
+ TreePath aPath) {
+ if (aPath == null)
+ return false;
+ return this.mayDropHere(aSourceTree, aSourceNode, (TreeNode) aPath
+ .getLastPathComponent());
+ }
+
+ /**
+ * Invoked to decide if draggedNode can be dropped into aNode.
+ *
+ * @param aSourceTree
+ * the source tree.
+ * @param aSourceNode
+ * the source node.
+ * @param aNode
+ * the node to drop into.
+ * @return true to allow the drop operation, false to avoid it.
+ */
+ private boolean mayDropHere(JTree aSourceTree, MutableTreeNode aSourceNode,
+ TreeNode aNode) {
+ boolean mayDropHere = (aNode != aSourceNode)
+ && (aNode instanceof MutableTreeNode)
+ && ((aNode.getParent() == null) || (aNode.getParent() instanceof MutableTreeNode))
+ && (this.jcomp.getModel() instanceof DefaultTreeModel)
+ && !((this.jcomp == aSourceTree) && TreeDragAndDropWidget
+ .isAncestorOf(aSourceNode, aNode));
+
+ if (mayDropHere) {
+ // Ask listeners
+ EventListener[] listeners = this.listeners
+ .getListeners(TreeTreeDnDListener.class);
+ if ((listeners != null) && (listeners.length > 0)) {
+ try {
+ TreeTreeDnDEvent event = new TreeTreeDnDEvent(aSourceTree,
+ aSourceNode, this.jcomp, aNode);
+ for (int i = 0; i < listeners.length; i++) {
+ ((TreeTreeDnDListener) listeners[i]).mayDrop(event);
+ }
+ } catch (DnDVetoException exception) {
+ mayDropHere = false;
+ }
+ }
+ }
+
+ return mayDropHere;
+ }
+
+ /**
+ * See if aPossibleParent is ancestor of aNode
+ */
+ private static boolean isAncestorOf(TreeNode aPossibleParent, TreeNode aNode) {
+ if ((aPossibleParent == null) || (aNode.getParent() == null))
+ return false;
+ else if (aNode.getParent() == aPossibleParent)
+ return true;
+ else
+ return TreeDragAndDropWidget.isAncestorOf(aPossibleParent, aNode
+ .getParent());
+ }
+
+ private void resetDragAndDrop() {
+ this.dropNode = null;
+ this.rendererProxy.setDraggedNode(null);
+ this.rendererProxy.setDropAllowed(false);
+ this.rendererProxy.setDropNode(null);
+ this.jcomp.repaint();
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/TreeTreeDnDEvent.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/TreeTreeDnDEvent.java
new file mode 100644
index 0000000..b010d55
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/TreeTreeDnDEvent.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tree.dnd;
+
+import java.util.EventObject;
+
+import javax.swing.JTree;
+import javax.swing.tree.TreeNode;
+
+/**
+ * TreeTreeDnDEvent is an event fired when a node from a JTree is dropped into
+ * another node (of the same or of other JTree).
+ *
+ * @author Antonio Vieiro (antonio at antonioshome.net), $Author: kirillcool $
+ */
+public class TreeTreeDnDEvent extends EventObject {
+ private JTree sourceTree;
+ private JTree targetTree;
+ private TreeNode sourceNode;
+ private TreeNode targetNode;
+
+ /**
+ * Creates a new instance of TreeTreeDnDEvent.
+ *
+ * @param aSourceTree
+ * the JTree containing the dragged node.
+ * @param aSourceNode
+ * the TreeNode being dragged into aTargetNode.
+ * @param aTargetTree
+ * the JTree containing the node on which the drop operation is
+ * about to happen.
+ * @param aTargetNode
+ * the TreeNode onto which aSourceNode is about to be dropped.
+ */
+ public TreeTreeDnDEvent(JTree aSourceTree, TreeNode aSourceNode,
+ JTree aTargetTree, TreeNode aTargetNode) {
+ super(aSourceTree);
+ this.setSourceTree(aSourceTree);
+ this.setSourceNode(aSourceNode);
+ this.setTargetTree(aTargetTree);
+ this.setTargetNode(aTargetNode);
+ }
+
+ public JTree getSourceTree() {
+ return this.sourceTree;
+ }
+
+ public void setSourceTree(JTree sourceTree) {
+ this.sourceTree = sourceTree;
+ }
+
+ public JTree getTargetTree() {
+ return this.targetTree;
+ }
+
+ public void setTargetTree(JTree targetTree) {
+ this.targetTree = targetTree;
+ }
+
+ public TreeNode getSourceNode() {
+ return this.sourceNode;
+ }
+
+ public void setSourceNode(TreeNode sourceNode) {
+ this.sourceNode = sourceNode;
+ }
+
+ public TreeNode getTargetNode() {
+ return this.targetNode;
+ }
+
+ public void setTargetNode(TreeNode targetNode) {
+ this.targetNode = targetNode;
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/TreeTreeDnDListener.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/TreeTreeDnDListener.java
new file mode 100644
index 0000000..4fc8633
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/tree/dnd/TreeTreeDnDListener.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.tree.dnd;
+
+import java.util.EventListener;
+
+/**
+ * TreeTreeDnDListener represents a listener that receives notifications when
+ * some node from a tree is moved or copied into itself (or into another tree).
+ *
+ * @author Antonio Vieiro (antonio at antonioshome.net), $Author: kirillcool $
+ */
+public interface TreeTreeDnDListener extends EventListener {
+ /**
+ * Invoked to verify that a node may be dropped into another node.
+ *
+ * @param anEvent
+ * a TreeTreeDnDEvent the event containing information about the
+ * Drag and Drop operation.
+ * @throws DnDVetoException
+ * if the drag and drop operation is not valid.
+ */
+ public void mayDrop(TreeTreeDnDEvent anEvent) throws DnDVetoException;
+
+ /**
+ * Invoked when the drop operation happens.
+ *
+ * @param anEvent
+ * a TreeTreeDnDEvent the event containing information about the
+ * Drag and Drop operation.
+ * @throws DnDVetoException
+ * if the drag and drop operation is not valid.
+ */
+ public void drop(TreeTreeDnDEvent anEvent) throws DnDVetoException;
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/DeltaQueue.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/DeltaQueue.java
new file mode 100644
index 0000000..dfe9261
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/DeltaQueue.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.utils;
+
+import java.util.*;
+
+/**
+ * Delta queue. Follows a standard approach from OS world for effeciently
+ * keeping tracks of scheduled events.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DeltaQueue {
+ /**
+ * Base class for entries in a {@link DeltaQueue}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static abstract class Deltable {
+ /**
+ * Delta in application specific units.
+ */
+ protected int delta;
+
+ /**
+ * Creates a new delta.
+ */
+ public Deltable() {
+ super();
+ }
+
+ /**
+ * Returns the current delta in application specific units.
+ *
+ * @return The current delta in application specific units.
+ */
+ public int getDelta() {
+ return delta;
+ }
+
+ /**
+ * Sets the new value of delta in application specific units.
+ *
+ * @param delta
+ * New value of delta in application specific units.
+ */
+ public void setDelta(int delta) {
+ this.delta = delta;
+ }
+
+ /**
+ * Increments the delta value by the specified amount.
+ *
+ * @param diff
+ * Amount for incrementing the delta value.
+ */
+ public void incrementDelta(int diff) {
+ this.delta += diff;
+ }
+
+ /**
+ * Decrements the delta value by the specified amount.
+ *
+ * @param diff
+ * Amount for decrementing the delta value.
+ */
+ public void decrementDelta(int diff) {
+ this.delta -= diff;
+ }
+ }
+
+ /**
+ * Interface for comparing two delta instances.
+ *
+ * @author Kirill Grouchnikov.
+ */
+ public static interface DeltaMatcher {
+ /**
+ * Returns <code>true</code> if the specified delta matches some
+ * criteria.
+ *
+ * @param deltable
+ * Delta.
+ * @return <code>true</code> if the specified delta matches some
+ * criteria, <code>false</code> otherwise.
+ */
+ public boolean matches(Deltable deltable);
+ }
+
+ /**
+ * List of entries. Contains {@link Deltable}s.
+ */
+ protected ArrayList<Deltable> queue;
+
+ /**
+ * Constructs a new empty non-blocking synchronized delta queue.
+ */
+ public DeltaQueue() {
+ this.queue = new ArrayList<Deltable>();
+ }
+
+ /**
+ * Queues the specified deltable. The specified deltable is placed somewhere
+ * in the queue based on the initial value of its delta. Note that when this
+ * method returns, the value of a {@link Deltable#getDelta()} may have
+ * changed. Do not reuse or change the passed deltable after this method
+ * returns.
+ *
+ * @param deltable
+ * Deltable.
+ */
+ public synchronized void queue(Deltable deltable) {
+ // locate the correct position in a time-difference queue
+ int currDiff = deltable.getDelta();
+ for (int i = 0; i < this.queue.size(); i++) {
+ Deltable currDeltable = this.queue.get(i);
+ currDiff -= currDeltable.getDelta();
+ if (currDiff > 0) {
+ continue;
+ }
+
+ if (currDiff == 0) {
+ // scan until the next diff is more than 0
+ deltable.setDelta(0);
+ for (int j = i + 1; j < this.queue.size(); j++) {
+ Deltable nextDeltable = this.queue.get(j);
+ if (nextDeltable.getDelta() > 0) {
+ // put just before it
+ this.queue.add(j, deltable);
+ return;
+ }
+ }
+ // if here - add at the end
+ this.queue.add(this.queue.size(), deltable);
+ return;
+ }
+
+ deltable.setDelta(currDiff + currDeltable.getDelta());
+ currDeltable.decrementDelta(deltable.getDelta());
+ this.queue.add(i, deltable);
+ return;
+ }
+
+ // put last
+ deltable.setDelta(currDiff);
+ this.queue.add(this.queue.size(), deltable);
+ }
+
+ /**
+ * Returns all deltables that have at most specified delay left. The
+ * returned list may be empty.
+ *
+ * @param delay
+ * Delay.
+ * @return The possibly empty list of all deltables that have at most
+ * specified delay left.
+ */
+ public synchronized List<Deltable> dequeue(int delay) {
+ List<Deltable> result = new LinkedList<Deltable>();
+ while (this.queue.size() > 0) {
+ Deltable next = this.queue.get(0);
+ int timeToExpire = next.getDelta();
+ next.decrementDelta(delay);
+ if (next.getDelta() > 0) {
+ break;
+ }
+ if (timeToExpire > 0)
+ delay -= timeToExpire;
+ result.add(next);
+ this.queue.remove(0);
+ }
+ return result;
+ }
+
+ /**
+ * Removes all deltas matching the specified matcher.
+ *
+ * @param matcher
+ * Delta matcher.
+ */
+ public synchronized void removeMatching(DeltaMatcher matcher) {
+ // start from the end
+ while (true) {
+ int toRemoveInd = -1;
+ Deltable toRemove = null;
+ for (int i = this.queue.size() - 1; i >= 0; i--) {
+ Deltable deltable = this.queue.get(i);
+ if (!matcher.matches(deltable))
+ continue;
+ toRemoveInd = i;
+ toRemove = deltable;
+ break;
+ }
+
+ if (toRemoveInd >= 0) {
+ if (toRemoveInd < (this.queue.size() - 1)) {
+ Deltable next = this.queue.get(toRemoveInd + 1);
+ next.incrementDelta(toRemove.getDelta());
+ }
+ this.queue.remove(toRemoveInd);
+ } else {
+ return;
+ }
+ }
+ }
+
+ /**
+ * Dumps the contents of the delta queue.
+ */
+ public void dump() {
+ System.out.println("Dump");
+ for (int i = 0; i < this.queue.size(); i++) {
+ System.out.println("\t" + this.queue.get(i));
+ }
+ }
+
+ /**
+ * Test class.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class DeltableTest extends Deltable {
+ /**
+ * ID.
+ */
+ private int id;
+
+ /**
+ * Creates a test delta.
+ *
+ * @param id
+ * ID.
+ * @param delta
+ * Delta.
+ */
+ public DeltableTest(int id, int delta) {
+ super();
+ this.id = id;
+ this.delta = delta;
+ }
+
+ public String toString() {
+ return this.id + ":" + this.delta;
+ }
+ }
+
+ /**
+ * For testing.
+ *
+ * @param args
+ * Ignored.
+ */
+ public static void main(String[] args) {
+ DeltaQueue dq = new DeltaQueue();
+ DeltableTest tpi11 = new DeltableTest(11, 100);
+ dq.queue(tpi11);
+ dq.dump();
+
+ DeltableTest tpi12 = new DeltableTest(12, 100);
+ dq.queue(tpi12);
+ dq.dump();
+
+ DeltableTest tpi21 = new DeltableTest(21, 200);
+ dq.queue(tpi21);
+ dq.dump();
+
+ DeltableTest tpi31 = new DeltableTest(31, 300);
+ dq.queue(tpi31);
+ dq.dump();
+
+ DeltableTest tpi13 = new DeltableTest(13, 100);
+ dq.queue(tpi13);
+ dq.dump();
+
+ DeltableTest tpi22 = new DeltableTest(22, 200);
+ dq.queue(tpi22);
+ dq.dump();
+
+ DeltableTest tpi25 = new DeltableTest(25, 250);
+ dq.queue(tpi25);
+ dq.dump();
+
+ DeltableTest tpi51 = new DeltableTest(51, 500);
+ dq.queue(tpi51);
+ dq.dump();
+
+ DeltableTest tpi05 = new DeltableTest(5, 50);
+ dq.queue(tpi05);
+ dq.dump();
+
+ List<Deltable> gr150 = dq.dequeue(100);
+ System.out.println("Dump 150");
+ for (int i = 0; i < gr150.size(); i++) {
+ DeltableTest tpi = (DeltableTest) gr150.get(i);
+ System.out.println("\t" + tpi);
+ }
+ dq.dump();
+
+ dq.removeMatching(new DeltaMatcher() {
+ @Override
+ public boolean matches(Deltable deltable) {
+ return ((DeltableTest) deltable).id < 30;
+ }
+ });
+ dq.dump();
+
+ TrackableThread.requestStopAllThreads();
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/LafConstants.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/LafConstants.java
new file mode 100644
index 0000000..42658d1
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/LafConstants.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.utils;
+
+import org.pushingpixels.lafwidget.animation.*;
+
+
+/**
+ * Constants for the <b>laf-widget</b> classes.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LafConstants {
+
+ /**
+ * Password strength.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class PasswordStrength {
+ /**
+ * Private constructor to prevent initialization.
+ */
+ private PasswordStrength() {
+ }
+
+ /**
+ * Weak strength.
+ */
+ public static final PasswordStrength WEAK = new PasswordStrength();
+
+ /**
+ * Medium strength.
+ */
+ public static final PasswordStrength MEDIUM = new PasswordStrength();
+
+ /**
+ * Strong strength.
+ */
+ public static final PasswordStrength STRONG = new PasswordStrength();
+ }
+
+ /**
+ * Enumerates all available kinds of tab overview dialog.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class TabOverviewKind {
+ /**
+ * Shows a grid with all tab thumbnails.
+ */
+ public static final TabOverviewKind GRID = new TabOverviewKind("grid");
+
+ /**
+ * Shows a round carousel with all tab thumbnails.
+ */
+ public static final TabOverviewKind ROUND_CAROUSEL = new TabOverviewKind(
+ "round carousel");
+
+ /**
+ * Shows a menu carousel with all tab thumbnails.
+ */
+ public static final TabOverviewKind MENU_CAROUSEL = new TabOverviewKind(
+ "menu carousel");
+
+ /**
+ * Animation kind name.
+ */
+ private String name;
+
+ /**
+ * Creates a new tab overview kind.
+ *
+ * @param name
+ * Tab overview kind name.
+ */
+ public TabOverviewKind(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the name of <code>this</code> animation.
+ *
+ * @return Name of <code>this</code> animation.
+ */
+ public String getName() {
+ return this.name;
+ }
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/LookUtils.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/LookUtils.java
new file mode 100644
index 0000000..1028f13
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/LookUtils.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright (c) 2001-2006 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.lafwidget.utils;
+
+import java.awt.*;
+import java.util.Locale;
+
+/**
+ * Provides convenience behavior used by the JGoodies Looks.
+ *
+ * @author Karsten Lentzsch
+ */
+public final class LookUtils {
+
+ // Basics System Properties **********************************************
+
+ /**
+ * The <code>java.version</code> System Property.
+ * <p>
+ *
+ * Defaults to <code>null</code> if the runtime does not have security
+ * access to read this property or the property does not exist.
+ */
+ private static final String JAVA_VERSION = getSystemProperty("java.version");
+
+ /**
+ * The <code>os.name</code> System Property. Operating system name.
+ * <p>
+ *
+ * Defaults to <code>null</code> if the runtime does not have security
+ * access to read this property or the property does not exist.
+ */
+ private static final String OS_NAME = getSystemProperty("os.name");
+
+ /**
+ * The <code>os.version</code> System Property. Operating system version.
+ * <p>
+ *
+ * Defaults to <code>null</code> if the runtime does not have security
+ * access to read this property or the property does not exist.
+ */
+ private static final String OS_VERSION = getSystemProperty("os.version");
+
+ // Requesting the Java Version ********************************************
+
+ /**
+ * True if this is Java 1.4.
+ */
+ public static final boolean IS_JAVA_1_4 = startsWith(JAVA_VERSION, "1.4");
+
+ /**
+ * True if this is Java 1.4.0_*.
+ */
+ public static final boolean IS_JAVA_1_4_0 = startsWith(JAVA_VERSION,
+ "1.4.0");
+
+ /**
+ * True if this is Java 1.4.2 or later. Since we assume Java 1.4 we just
+ * check for 1.4.0 and 1.4.1.
+ */
+ public static final boolean IS_JAVA_1_4_2_OR_LATER = !startsWith(
+ JAVA_VERSION, "1.4.0")
+ && !startsWith(JAVA_VERSION, "1.4.1");
+
+ /**
+ * True if this is Java 5.x. We check for a prefix of 1.5.
+ */
+ public static final boolean IS_JAVA_5 = startsWith(JAVA_VERSION, "1.5");
+
+ /**
+ * True if this is Java 5.x or later. Since we don't support Java 1.3, we
+ * can check that it's not 1.4.
+ */
+ public static final boolean IS_JAVA_5_OR_LATER = !IS_JAVA_1_4;
+
+ /**
+ * True if this is Java 6. We check for a prefix of 1.6.
+ */
+ public static final boolean IS_JAVA_6 = startsWith(JAVA_VERSION, "1.6");
+
+ /**
+ * True if this is Java 6.x or later. Since we don't support Java 1.3, we
+ * can check that it's neither 1.4 nor 1.5.
+ */
+ public static final boolean IS_JAVA_6_OR_LATER = !IS_JAVA_1_4 && !IS_JAVA_5;
+
+ /**
+ * True if this is Java 1.4 or Java 5.
+ */
+ public static final boolean IS_JAVA_1_4_OR_5 = IS_JAVA_1_4 || IS_JAVA_5;
+
+ // Requesting the Operating System Name ***********************************
+
+ /**
+ * True if this is FreeBSD.
+ */
+ public static final boolean IS_OS_FREEBSD = startsWithIgnoreCase(OS_NAME,
+ "FreeBSD");
+
+ /**
+ * True if this is Linux.
+ */
+ public static final boolean IS_OS_LINUX = startsWithIgnoreCase(OS_NAME,
+ "Linux");
+
+ /**
+ * True if this is OS/2.
+ */
+ public static final boolean IS_OS_OS2 = startsWith(OS_NAME, "OS/2");
+
+ /**
+ * True if this is the Mac OS X.
+ */
+ public static final boolean IS_OS_MAC = startsWith(OS_NAME, "Mac");
+
+ /**
+ * True if this is Windows.
+ */
+ public static final boolean IS_OS_WINDOWS = startsWith(OS_NAME, "Windows");
+
+ /**
+ * True if this is Windows 98/ME/2000/XP/VISTA.
+ */
+ public static final boolean IS_OS_WINDOWS_MODERN = startsWith(OS_NAME,
+ "Windows")
+ && !startsWith(OS_VERSION, "4.0");
+
+ /**
+ * True if this is Windows 95.
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_95 = startsWith(OS_NAME,
+ "Windows 9")
+ && startsWith(OS_VERSION, "4.0");
+
+ /**
+ * True if this is Windows 98.
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_98 = startsWith(OS_NAME,
+ "Windows 9")
+ && startsWith(OS_VERSION, "4.1");
+
+ /**
+ * True if this is Windows NT.
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_NT = startsWith(OS_NAME,
+ "Windows NT");
+
+ /**
+ * True if this is Windows ME.
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_ME = startsWith(OS_NAME,
+ "Windows")
+ && startsWith(OS_VERSION, "4.9");
+
+ /**
+ * True if this is Windows 2000.
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_2000 = startsWith(OS_NAME,
+ "Windows")
+ && startsWith(OS_VERSION, "5.0");
+
+ /**
+ * True if this is Windows XP.
+ */
+ public static final boolean IS_OS_WINDOWS_XP = startsWith(OS_NAME,
+ "Windows")
+ && startsWith(OS_VERSION, "5.1");
+
+ /**
+ * True if this is Windows Vista.
+ *
+ * @since 2.0
+ */
+ public static final boolean IS_OS_WINDOWS_VISTA = startsWith(OS_NAME,
+ "Windows")
+ && startsWith(OS_VERSION, "6.0");
+
+ /**
+ * True if this is Solaris.
+ */
+ public static final boolean IS_OS_SOLARIS = startsWith(OS_NAME, "Solaris");
+
+ // Other Properties *******************************************************
+
+ /**
+ * True if the Windows XP Look&Feel is enabled.
+ */
+ public static final boolean IS_LAF_WINDOWS_XP_ENABLED = isWindowsXPLafEnabled();
+
+ /**
+ * True if if the screen resolution is smaller than 120 dpi.
+ *
+ * @see Toolkit#getScreenResolution()
+ */
+ public static final boolean IS_LOW_RESOLUTION = isLowResolution();
+
+ private static boolean loggingEnabled = true;
+
+ private LookUtils() {
+ // Override default constructor; prevents instantiation.
+ }
+
+ // Accessing System Configuration *****************************************
+
+ /**
+ * Tries to look up the System property for the given key. In untrusted
+ * environments this may throw a SecurityException. In this case we catch
+ * the exception and answer <code>null</code>.
+ *
+ * @param key
+ * the name of the system property
+ * @return the system property's String value, or <code>null</code> if
+ * there's no such value, or a SecurityException has been caught
+ */
+ public static String getSystemProperty(String key) {
+ try {
+ return System.getProperty(key);
+ } catch (SecurityException e) {
+ // log("Can't read the System property " + key + ".");
+ return null;
+ }
+ }
+
+ /**
+ * Tries to look up the System property for the given key. In untrusted
+ * environments this may throw a SecurityException. In this case, we catch
+ * the exception and answer the default value.
+ *
+ * @param key
+ * the name of the system property
+ * @param defaultValue
+ * the default value if no property exists.
+ * @return the system property's String value, or the defaultValue if
+ * there's no such value, or a SecurityException has been caught
+ */
+ public static String getSystemProperty(String key, String defaultValue) {
+ try {
+ return System.getProperty(key, defaultValue);
+ } catch (SecurityException e) {
+ // log("Can't read the System property " + key + ".");
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Checks if a boolean system property has been set for the given key, and
+ * returns the associated Boolean, or <code>null</code> if no value has been
+ * set. The test for the property ignores case. If a Boolean value has been
+ * set, a message is logged with the given prefix.
+ *
+ * @param key
+ * the key used to lookup the system property value
+ * @param logMessage
+ * a prefix used when a message is logged
+ * @return <code>Boolean.TRUE</code> if the system property has been set to
+ * "true" (case ignored), <code>Boolean.FALSE</code> if it has been
+ * set to "false", <code>null</code> otherwise
+ */
+ public static Boolean getBooleanSystemProperty(String key, String logMessage) {
+ String value = getSystemProperty(key, "");
+ Boolean result;
+ if (value.equalsIgnoreCase("false"))
+ result = Boolean.FALSE;
+ else if (value.equalsIgnoreCase("true"))
+ result = Boolean.TRUE;
+ else
+ result = null;
+ if (result != null) {
+ LookUtils.log(logMessage + " have been "
+ + (result ? "en" : "dis")
+ + "abled in the system properties.");
+ }
+ return result;
+ }
+
+ /**
+ * Checks and answers whether the Windows XP style is enabled. This method
+ * is intended to be called only if a Windows look&feel is about to be
+ * installed or already active in the UIManager. The XP style of the Windows
+ * look&feel is enabled by default on Windows XP platforms since the
+ * J2SE 1.4.2; it can be disabled either in the Windows desktop as well as
+ * in the Java runtime by setting a System property.
+ * <p>
+ *
+ * First checks the platform, platform version and Java version. Then checks
+ * whether the desktop property <tt>win.xpstyle.themeActive</tt> is set or
+ * not.
+ *
+ * @return true if the Windows XP style is enabled
+ */
+ private static boolean isWindowsXPLafEnabled() {
+ return (IS_OS_WINDOWS_XP || IS_OS_WINDOWS_VISTA)
+ && IS_JAVA_1_4_2_OR_LATER
+ && Boolean.TRUE.equals(Toolkit.getDefaultToolkit()
+ .getDesktopProperty("win.xpstyle.themeActive"))
+ && getSystemProperty("swing.noxp") == null;
+ }
+
+ /**
+ * Checks and answers whether we have a true color system.
+ *
+ * @param c
+ * the component used to determine the toolkit
+ * @return true if the component's toolkit has a pixel size >= 24
+ */
+ public static boolean isTrueColor(Component c) {
+ return c.getToolkit().getColorModel().getPixelSize() >= 24;
+ }
+
+ /**
+ * Checks and answers whether this toolkit provides native drop shadows for
+ * popups such as the Mac OS X. Currently this is used to determine if the
+ * Looks' popup drop shadow feature is active or not - even if it's enabled.
+ *
+ * @return true if the toolkit provides native drop shadows
+ *
+ */
+ public static boolean getToolkitUsesNativeDropShadows() {
+ return IS_OS_MAC;
+ }
+
+ /**
+ * Computes and returns a Color that is slightly brighter than the specified
+ * Color.
+ *
+ * @param color
+ * the color used as basis for the brightened color
+ * @return a slightly brighter color
+ */
+ public static Color getSlightlyBrighter(Color color) {
+ return getSlightlyBrighter(color, 1.1f);
+ }
+
+ /**
+ * Computes and returns a Color that is slightly brighter than the specified
+ * Color.
+ *
+ * @param color
+ * the color used as basis for the brightened color
+ * @param factor
+ * the factor used to compute the brightness
+ * @return a slightly brighter color
+ */
+ public static Color getSlightlyBrighter(Color color, float factor) {
+ float[] hsbValues = new float[3];
+ Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(),
+ hsbValues);
+ float hue = hsbValues[0];
+ float saturation = hsbValues[1];
+ float brightness = hsbValues[2];
+ float newBrightness = Math.min(brightness * factor, 1.0f);
+ return Color.getHSBColor(hue, saturation, newBrightness);
+ }
+
+ // Minimal logging ******************************************************
+
+ /**
+ * Enables or disables the Looks logging.
+ *
+ * @param enabled
+ * true to enable logging, false to disable it
+ */
+ public static void setLoggingEnabled(boolean enabled) {
+ loggingEnabled = enabled;
+ }
+
+ /**
+ * Prints a new line to the console if logging is enabled.
+ */
+ public static void log() {
+ if (loggingEnabled) {
+ System.out.println();
+ }
+ }
+
+ /**
+ * Prints the given message to the console if logging is enabled.
+ *
+ * @param message
+ * the message to print
+ */
+ public static void log(String message) {
+ if (loggingEnabled) {
+ System.out.println("JGoodies Looks: " + message);
+ }
+ }
+
+ // Private Helper Methods ***********************************************
+
+ /**
+ * Checks and answers whether the screen resolution is low or high.
+ * Resolutions below 120 dpi are considere low, all others are high.
+ *
+ * @return true if the screen resolution is smaller than 120 dpi
+ */
+ private static boolean isLowResolution() {
+ return Toolkit.getDefaultToolkit().getScreenResolution() < 120;
+ }
+
+ private static boolean startsWith(String str, String prefix) {
+ return str != null && str.startsWith(prefix);
+ }
+
+ private static boolean startsWithIgnoreCase(String str, String prefix) {
+ return str != null
+ && str.toUpperCase(Locale.ENGLISH).startsWith(
+ prefix.toUpperCase(Locale.ENGLISH));
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/RenderingUtils.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/RenderingUtils.java
new file mode 100755
index 0000000..ed2b196
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/RenderingUtils.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2001-2006 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.utils;
+
+import java.awt.*;
+import java.awt.print.PrinterGraphics;
+import java.util.*;
+
+import javax.swing.CellRendererPane;
+import javax.swing.SwingUtilities;
+
+public class RenderingUtils {
+ private static final String PROP_DESKTOPHINTS = "awt.font.desktophints";
+
+ private static Map<String, Map> desktopHintsCache = new HashMap<String, Map>();
+
+ public static Map installDesktopHintsOld(Graphics2D g2, Component c) {
+ if (SwingUtilities.getAncestorOfClass(CellRendererPane.class, c) != null) {
+ return null;
+ }
+
+ Map oldRenderingHints = null;
+ Map desktopHints = desktopHints(g2);
+ if (desktopHints != null && !desktopHints.isEmpty()) {
+ oldRenderingHints = new HashMap(desktopHints.size());
+ RenderingHints.Key key;
+ for (Iterator i = desktopHints.keySet().iterator(); i.hasNext();) {
+ key = (RenderingHints.Key) i.next();
+ oldRenderingHints.put(key, g2.getRenderingHint(key));
+ }
+ g2.addRenderingHints(desktopHints);
+ if (c != null) {
+ Font font = c.getFont();
+ if (font != null) {
+ if (font.getSize() > 15) {
+ g2.setRenderingHint(
+ RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ }
+ }
+ }
+ } else {
+ // the following is temporary until the Apple VM 6.0 supports the
+ // desktop AA hinting settings.
+ if (LookUtils.IS_JAVA_6 && LookUtils.IS_OS_MAC) {
+ g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ }
+ }
+ return oldRenderingHints;
+ }
+
+ public static void installDesktopHints(Graphics2D g2, Component c) {
+ if (SwingUtilities.getAncestorOfClass(CellRendererPane.class, c) != null) {
+ return;
+ }
+
+ Map desktopHints = desktopHints(g2);
+ if (desktopHints != null && !desktopHints.isEmpty()) {
+ g2.addRenderingHints(desktopHints);
+ if (c != null) {
+ Font font = c.getFont();
+ if (font != null) {
+ if (font.getSize() > 15) {
+ g2.setRenderingHint(
+ RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ }
+ }
+ }
+ } else {
+ // the following is temporary until the Apple VM 6.0 supports the
+ // desktop AA hinting settings.
+ if (LookUtils.IS_JAVA_6 && LookUtils.IS_OS_MAC) {
+ g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ }
+ }
+ }
+
+ private static Map desktopHints(Graphics2D g2) {
+ if (isPrinting(g2)) {
+ return null;
+ }
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ String deviceId = "";
+ GraphicsConfiguration config = g2.getDeviceConfiguration();
+ if (config != null) {
+ GraphicsDevice device = config.getDevice();
+ if (device != null) {
+ deviceId = device.getIDstring();
+ }
+ }
+ if (!desktopHintsCache.containsKey(deviceId)) {
+ Map desktopHints = (Map) toolkit
+ .getDesktopProperty(PROP_DESKTOPHINTS + '.'
+ + deviceId);
+ if (desktopHints == null) {
+ desktopHints = (Map) toolkit
+ .getDesktopProperty(PROP_DESKTOPHINTS);
+ }
+ // It is possible to get a non-empty map but with disabled AA.
+ if (desktopHints != null) {
+ Object aaHint = desktopHints
+ .get(RenderingHints.KEY_TEXT_ANTIALIASING);
+ if ((aaHint == RenderingHints.VALUE_TEXT_ANTIALIAS_OFF)
+ || (aaHint == RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT)) {
+ desktopHints = null;
+ }
+ }
+
+ if (desktopHints == null)
+ desktopHints = new HashMap();
+
+ desktopHintsCache.put(deviceId, desktopHints);
+ }
+
+ return desktopHintsCache.get(deviceId);
+ }
+
+ private static boolean isPrinting(Graphics g) {
+ return g instanceof PrintGraphics || g instanceof PrinterGraphics;
+ }
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/ShadowPopupBorder.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/ShadowPopupBorder.java
new file mode 100644
index 0000000..cdd39f5
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/ShadowPopupBorder.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2001-2005 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.lafwidget.utils;
+
+import java.awt.*;
+
+import javax.swing.ImageIcon;
+import javax.swing.border.AbstractBorder;
+
+/**
+ * A border with a drop shadow intended to be used as the outer border of
+ * popups. Can paint the screen background if used with heavy-weight popup
+ * windows.
+ *
+ * @author Stefan Matthias Aust
+ * @author Karsten Lentzsch
+ * @author Andrej Golovnin
+ *
+ * see ShadowPopup
+ * see ShadowPopupFactory
+ */
+public final class ShadowPopupBorder extends AbstractBorder {
+
+ /**
+ * The drop shadow needs 5 pixels at the bottom and the right hand side.
+ */
+ private static final int SHADOW_SIZE = 5;
+
+ /**
+ * The singleton instance used to draw all borders.
+ */
+ private static ShadowPopupBorder instance = new ShadowPopupBorder();
+
+ /**
+ * The drop shadow is created from a PNG image with 8 bit alpha channel.
+ */
+ private static Image shadow = new ImageIcon(ShadowPopupBorder.class
+ .getResource("shadow.png")).getImage();
+
+ // Instance Creation *****************************************************
+
+ /**
+ * Returns the singleton instance used to draw all borders.
+ */
+ public static ShadowPopupBorder getInstance() {
+ return instance;
+ }
+
+ /**
+ * Paints the border for the specified component with the specified position
+ * and size.
+ */
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height) {
+ // fake drop shadow effect in case of heavy weight popups
+ // JComponent popup = (JComponent) c;
+ // Image hShadowBg = (Image)
+ // popup.getClientProperty(ShadowPopupFactory.PROP_HORIZONTAL_BACKGROUND);
+ // if (hShadowBg != null) {
+ // g.drawImage(hShadowBg, x, y + height - 5, c);
+ // }
+ // Image vShadowBg = (Image)
+ // popup.getClientProperty(ShadowPopupFactory.PROP_VERTICAL_BACKGROUND);
+ // if (vShadowBg != null) {
+ // g.drawImage(vShadowBg, x + width - 5, y, c);
+ // }
+
+ // draw drop shadow
+ g.drawImage(shadow, x + 5, y + height - 5, x + 10, y + height, 0, 6, 5,
+ 11, null, c);
+ g.drawImage(shadow, x + 10, y + height - 5, x + width - 5, y + height,
+ 5, 6, 6, 11, null, c);
+ g.drawImage(shadow, x + width - 5, y + 5, x + width, y + 10, 6, 0, 11,
+ 5, null, c);
+ g.drawImage(shadow, x + width - 5, y + 10, x + width, y + height - 5,
+ 6, 5, 11, 6, null, c);
+ g.drawImage(shadow, x + width - 5, y + height - 5, x + width, y
+ + height, 6, 6, 11, 11, null, c);
+ }
+
+ /**
+ * Returns the insets of the border.
+ */
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return new Insets(0, 0, SHADOW_SIZE, SHADOW_SIZE);
+ }
+
+ /**
+ * Reinitializes the insets parameter with this Border's current Insets.
+ *
+ * @param c
+ * the component for which this border insets value applies
+ * @param insets
+ * the object to be reinitialized
+ * @return the <code>insets</code> object
+ */
+ @Override
+ public Insets getBorderInsets(Component c, Insets insets) {
+ insets.left = insets.top = 0;
+ insets.right = insets.bottom = SHADOW_SIZE;
+ return insets;
+ }
+
+}
diff --git a/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/TrackableThread.java b/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/TrackableThread.java
new file mode 100644
index 0000000..1b7bbdb
--- /dev/null
+++ b/laf-widget/src/main/java/org/pushingpixels/lafwidget/utils/TrackableThread.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.lafwidget.utils;
+
+import java.util.*;
+
+/**
+ * Base class for all helper threads.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class TrackableThread extends Thread {
+ /**
+ * All helper threads.
+ */
+ private static Set<TrackableThread> threads = new HashSet<TrackableThread>();
+
+ /**
+ * Creates new instance.
+ */
+ protected TrackableThread() {
+ super();
+ TrackableThread.threads.add(this);
+ this.setDaemon(true);
+ }
+
+ /**
+ * Issues a stop request on <code>this</code> thread.
+ */
+ protected abstract void requestStop();
+
+ /**
+ * Issues a stop request on all helper threads.
+ */
+ public static void requestStopAllThreads() {
+ for (TrackableThread tt : TrackableThread.threads) {
+ tt.requestStop();
+ }
+// for (Iterator it = TrackableThread.threads.iterator(); it.hasNext();) {
+// TrackableThread thread = (TrackableThread) it.next();
+// thread.requestStop();
+// }
+ }
+}
diff --git a/laf-widget/src/main/resources/META-INF/lafwidget.properties b/laf-widget/src/main/resources/META-INF/lafwidget.properties
new file mode 100644
index 0000000..10b2d01
--- /dev/null
+++ b/laf-widget/src/main/resources/META-INF/lafwidget.properties
@@ -0,0 +1,14 @@
+org.pushingpixels.lafwidget.animation.effects.GhostAnimationWidget = javax.swing.JButton;%javax.swing.JToggleButton
+org.pushingpixels.lafwidget.desktop.DesktopIconHoverPreviewWidget = javax.swing.JInternalFrame$JDesktopIcon
+org.pushingpixels.lafwidget.menu.MenuSearchWidget = javax.swing.JMenuBar
+org.pushingpixels.lafwidget.scroll.AutoScrollWidget = javax.swing.JScrollPane
+org.pushingpixels.lafwidget.scroll.ScrollPaneSelectorWidget = javax.swing.JScrollPane
+org.pushingpixels.lafwidget.tabbed.TabHoverPreviewWidget = javax.swing.JTabbedPane
+org.pushingpixels.lafwidget.tabbed.TabOverviewDialogWidget = javax.swing.JTabbedPane
+org.pushingpixels.lafwidget.tabbed.TabPagerWidget = javax.swing.JTabbedPane
+org.pushingpixels.lafwidget.text.PasswordStrengthCheckerWidget = javax.swing.JPasswordField
+org.pushingpixels.lafwidget.text.LockBorderWidget = javax.swing.text.JTextComponent
+org.pushingpixels.lafwidget.text.SelectAllOnFocusGainWidget = javax.swing.text.JTextComponent
+org.pushingpixels.lafwidget.text.SelectOnEscapeWidget = javax.swing.text.JTextComponent
+org.pushingpixels.lafwidget.text.EditContextMenuWidget = javax.swing.text.JTextComponent
+org.pushingpixels.lafwidget.tree.dnd.TreeDragAndDropWidget = javax.swing.JTree
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/ASM.license b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/ASM.license
new file mode 100644
index 0000000..04085ce
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/ASM.license
@@ -0,0 +1,29 @@
+Copyright (c) 2000-2005 INRIA, France Telecom
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holders nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/BlogOfBug.license b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/BlogOfBug.license
new file mode 100644
index 0000000..63bfe4f
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/BlogOfBug.license
@@ -0,0 +1,11 @@
+Copyright [2006-2007] [Nigel Hughes]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/JiBX.license b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/JiBX.license
new file mode 100644
index 0000000..55726e2
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/JiBX.license
@@ -0,0 +1,25 @@
+Copyright (c) 2003-2005, Dennis M. Sosnoski
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of JiBX nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/LafWidget.license b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/LafWidget.license
new file mode 100644
index 0000000..a3453a1
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/LafWidget.license
@@ -0,0 +1,26 @@
+Copyright (c) 2005-2010, Kirill Grouchnikov and contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+ * Neither the name of the Kirill Grouchnikov and contributors nor
+the names of its contributors may be used to endorse or promote products
+derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/Looks.license b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/Looks.license
new file mode 100644
index 0000000..94a30e8
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/Looks.license
@@ -0,0 +1,26 @@
+Copyright (c) 2001-2005 JGoodies Karsten Lentzsch and contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+ * Neither the name of the JGoodies Karsten Lentzsch and contributors nor
+the names of its contributors may be used to endorse or promote products
+derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/TangoIcons.license b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/TangoIcons.license
new file mode 100644
index 0000000..0bc7785
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/TangoIcons.license
@@ -0,0 +1,260 @@
+Attribution-ShareAlike 2.5
+CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT
+PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT
+CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES
+THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO
+WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS
+LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
+
+License
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS
+CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK
+IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE
+OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT
+LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT
+AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR
+GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR
+ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
+
+1. Definitions
+
+ 1. "Collective Work" means a work, such as a periodical issue,
+ anthology or encyclopedia, in which the Work in its entirety
+ in unmodified form, along with a number of other contributions,
+ constituting separate and independent works in themselves,
+ are assembled into a collective whole. A work that constitutes
+ a Collective Work will not be considered a Derivative Work
+ (as defined below) for the purposes of this License.
+ 2. "Derivative Work" means a work based upon the Work or upon
+ the Work and other pre-existing works, such as a translation,
+ musical arrangement, dramatization, fictionalization, motion
+ picture version, sound recording, art reproduction, abridgment,
+ condensation, or any other form in which the Work may be recast,
+ transformed, or adapted, except that a work that constitutes
+ a Collective Work will not be considered a Derivative Work for
+ the purpose of this License. For the avoidance of doubt, where
+ the Work is a musical composition or sound recording, the
+ synchronization of the Work in timed-relation with a moving
+ image ("synching") will be considered a Derivative Work for
+ the purpose of this License.
+ 3. "Licensor" means the individual or entity that offers the
+ Work under the terms of this License.
+ 4. "Original Author" means the individual or entity who
+ created the Work.
+ 5. "Work" means the copyrightable work of authorship offered
+ under the terms of this License.
+ 6. "You" means an individual or entity exercising rights under
+ this License who has not previously violated the terms of this
+ License with respect to the Work, or who has received express
+ permission from the Licensor to exercise rights under this
+ License despite a previous violation.
+ 7. "License Elements" means the following high-level license
+ attributes as selected by Licensor and indicated in the title
+ of this License: Attribution, ShareAlike.
+
+2. Fair Use Rights. Nothing in this license is intended to reduce,
+limit, or restrict any rights arising from fair use, first sale
+or other limitations on the exclusive rights of the copyright
+owner under copyright law or other applicable laws.
+
+3. License Grant. Subject to the terms and conditions of this
+License, Licensor hereby grants You a worldwide, royalty-free,
+non-exclusive, perpetual (for the duration of the applicable
+copyright) license to exercise the rights in the Work as stated
+below:
+
+ 1. to reproduce the Work, to incorporate the Work into one
+ or more Collective Works, and to reproduce the Work as
+ incorporated in the Collective Works;
+ 2. to create and reproduce Derivative Works;
+ 3. to distribute copies or phonorecords of, display publicly,
+ perform publicly, and perform publicly by means of a digital
+ audio transmission the Work including as incorporated in
+ Collective Works;
+ 4. to distribute copies or phonorecords of, display publicly,
+ perform publicly, and perform publicly by means of a digital
+ audio transmission Derivative Works.
+ 5.
+
+ For the avoidance of doubt, where the work is a musical composition:
+ 1. Performance Royalties Under Blanket Licenses. Licensor
+ waives the exclusive right to collect, whether individually
+ or via a performance rights society (e.g. ASCAP, BMI,
+ SESAC), royalties for the public performance or public
+ digital performance (e.g. webcast) of the Work.
+ 2. Mechanical Rights and Statutory Royalties. Licensor
+ waives the exclusive right to collect, whether individually
+ or via a music rights society or designated agent (e.g.
+ Harry Fox Agency), royalties for any phonorecord You
+ create from the Work ("cover version") and distribute, subject
+ to the compulsory license created by 17 USC Section 115 of the
+ US Copyright Act (or the equivalent in other jurisdictions).
+ 6. Webcasting Rights and Statutory Royalties. For the avoidance
+ of doubt, where the Work is a sound recording, Licensor waives
+ the exclusive right to collect, whether individually or via a
+ performance-rights society (e.g. SoundExchange), royalties for
+ the public digital performance (e.g. webcast) of the Work, subject
+ to the compulsory license created by 17 USC Section 114 of the US
+ Copyright Act (or the equivalent in other jurisdictions).
+
+The above rights may be exercised in all media and formats whether
+now known or hereafter devised. The above rights include the right
+to make such modifications as are technically necessary to exercise
+the rights in other media and formats. All rights not expressly
+granted by Licensor are hereby reserved.
+
+4. Restrictions.The license granted in Section 3 above is expressly
+made subject to and limited by the following restrictions:
+
+ 1. You may distribute, publicly display, publicly perform,
+ or publicly digitally perform the Work only under the terms
+ of this License, and You must include a copy of, or the
+ Uniform Resource Identifier for, this License with every copy
+ or phonorecord of the Work You distribute, publicly display,
+ publicly perform, or publicly digitally perform. You may not
+ offer or impose any terms on the Work that alter or restrict
+ the terms of this License or the recipients' exercise of the
+ rights granted hereunder. You may not sublicense the Work.
+ You must keep intact all notices that refer to this License
+ and to the disclaimer of warranties. You may not distribute,
+ publicly display, publicly perform, or publicly digitally
+ perform the Work with any technological measures that control
+ access or use of the Work in a manner inconsistent with the
+ terms of this License Agreement. The above applies to the Work
+ as incorporated in a Collective Work, but this does not require
+ the Collective Work apart from the Work itself to be made
+ subject to the terms of this License. If You create a Collective
+ Work, upon notice from any Licensor You must, to the extent
+ practicable, remove from the Collective Work any credit as
+ required by clause 4(c), as requested. If You create a Derivative
+ Work, upon notice from any Licensor You must, to the extent
+ practicable, remove from the Derivative Work any credit as required
+ by clause 4(c), as requested.
+ 2. You may distribute, publicly display, publicly perform, or
+ publicly digitally perform a Derivative Work only under the
+ terms of this License, a later version of this License with
+ the same License Elements as this License, or a Creative Commons
+ iCommons license that contains the same License Elements as this
+ License (e.g. Attribution-ShareAlike 2.5 Japan). You must
+ include a copy of, or the Uniform Resource Identifier for,
+ this License or other license specified in the previous sentence
+ with every copy or phonorecord of each Derivative Work You
+ distribute, publicly display, publicly perform, or publicly
+ digitally perform. You may not offer or impose any terms on the
+ Derivative Works that alter or restrict the terms of this License
+ or the recipients' exercise of the rights granted hereunder, and
+ You must keep intact all notices that refer to this License and
+ to the disclaimer of warranties. You may not distribute, publicly
+ display, publicly perform, or publicly digitally perform the Derivative
+ Work with any technological measures that control access or use of the
+ Work in a manner inconsistent with the terms of this License Agreement.
+ The above applies to the Derivative Work as incorporated in a Collective
+ Work, but this does not require the Collective Work apart from the
+ Derivative Work itself to be made subject to the terms of this License.
+ 3. If you distribute, publicly display, publicly perform, or
+ publicly digitally perform the Work or any Derivative Works or
+ Collective Works, You must keep intact all copyright notices for
+ the Work and provide, reasonable to the medium or means You are
+ utilizing: (i) the name of the Original Author (or pseudonym, if
+ applicable) if supplied, and/or (ii) if the Original Author and/or
+ Licensor designate another party or parties (e.g. a sponsor institute,
+ publishing entity, journal) for attribution in Licensor's copyright
+ notice, terms of service or by other reasonable means, the name of
+ such party or parties; the title of the Work if supplied; to the
+ extent reasonably practicable, the Uniform Resource Identifier, if
+ any, that Licensor specifies to be associated with the Work, unless
+ such URI does not refer to the copyright notice or licensing
+ information for the Work; and in the case of a Derivative Work, a
+ credit identifying the use of the Work in the Derivative Work (e.g.,
+ "French translation of the Work by Original Author," or "Screenplay
+ based on original Work by Original Author"). Such credit may be
+ implemented in any reasonable manner; provided, however, that in
+ the case of a Derivative Work or Collective Work, at a minimum
+ such credit will appear where any other comparable authorship credit
+ appears and in a manner at least as prominent as such other comparable
+ authorship credit.
+
+
+5. Representations, Warranties and Disclaimer
+
+UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR
+OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES
+OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY
+OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE,
+MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT,
+OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE
+OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS
+DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION
+MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY
+APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON
+ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL,
+PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR
+THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+
+ 1. This License and the rights granted hereunder will
+ terminate automatically upon any breach by You of the terms
+ of this License. Individuals or entities who have received
+ Derivative Works or Collective Works from You under this
+ License, however, will not have their licenses terminated
+ provided such individuals or entities remain in full compliance
+ with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive
+ any termination of this License.
+ 2. Subject to the above terms and conditions, the license
+ granted here is perpetual (for the duration of the applicable
+ copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
+
+8. Miscellaneous
+
+ 1. Each time You distribute or publicly digitally perform
+ the Work or a Collective Work, the Licensor offers to the
+ recipient a license to the Work on the same terms and conditions
+ as the license granted to You under this License.
+ 2. Each time You distribute or publicly digitally perform a
+ Derivative Work, Licensor offers to the recipient a license
+ to the original Work on the same terms and conditions as the
+ license granted to You under this License.
+ 3. If any provision of this License is invalid or unenforceable
+ under applicable law, it shall not affect the validity or
+ enforceability of the remainder of the terms of this License,
+ and without further action by the parties to this agreement,
+ such provision shall be reformed to the minimum extent necessary
+ to make such provision valid and enforceable.
+ 4. No term or provision of this License shall be deemed waived
+ and no breach consented to unless such waiver or consent shall
+ be in writing and signed by the party to be charged with such
+ waiver or consent.
+ 5. This License constitutes the entire agreement between the
+ parties with respect to the Work licensed here. There are no
+ understandings, agreements or representations with respect to
+ the Work not specified here. Licensor shall not be bound by
+ any additional provisions that may appear in any communication
+ from You. This License may not be modified without the mutual
+ written agreement of the Licensor and You.
+
+Creative Commons is not a party to this License, and makes no
+warranty whatsoever in connection with the Work. Creative Commons
+will not be liable to You or any party on any legal theory for
+any damages whatsoever, including without limitation any general,
+special, incidental or consequential damages arising in connection
+to this license. Notwithstanding the foregoing two (2) sentences,
+if Creative Commons has expressly identified itself as the Licensor
+hereunder, it shall have all rights and obligations of Licensor.
+
+Except for the limited purpose of indicating to the public that
+the Work is licensed under the CCPL, neither party will use the
+trademark "Creative Commons" or any related trademark or logo
+of Creative Commons without the prior written consent of Creative
+Commons. Any permitted use will be in compliance with Creative
+Commons' then-current trademark usage guidelines, as may be
+published on its website or otherwise made available upon request
+from time to time.
+
+Creative Commons may be contacted at http://creativecommons.org/.
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels.properties
new file mode 100644
index 0000000..ab5811e
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels.properties
@@ -0,0 +1,25 @@
+# Default labels by Kirill Grouchnikov
+
+TabbedPane.overviewButtonTooltip=Show tab overview
+
+TabbedPane.overviewDialogTitle=Tab overview
+
+TabbedPane.overviewDialogTitleRefresh=Tab overview [refresh every {0} sec]
+
+TabbedPane.overviewWidgetTooltip=Click to close overview and select tab
+
+Tooltip.menuSearchButton=Select to view menu search panel
+
+Tooltip.menuSearchField=Enter search string and press 'Enter' button to search
+
+Tooltip.menuSearchTooltip=Click to locate menu
+
+EditMenu.copy=Copy
+
+EditMenu.cut=Cut
+
+EditMenu.paste=Paste
+
+EditMenu.delete=Delete
+
+EditMenu.selectAll=Select all
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_ar.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_ar.properties
new file mode 100644
index 0000000..417f752
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_ar.properties
@@ -0,0 +1,25 @@
+# Arabic translation by QamarAlZaman Habeek
+
+TabbedPane.overviewButtonTooltip=\u0639\u0631\u0636 \u0644\u0645\u062D\u0629 \u0639\u0646 \u0627\u0644\u062C\u062F\u0648\u0644\u0629
+
+TabbedPane.overviewDialogTitle=\u0644\u0645\u062D\u0629 \u0639\u0646 \u0627\u0644\u062C\u062F\u0648\u0644\u0629
+
+TabbedPane.overviewDialogTitleRefresh=\u0644\u0645\u062D\u0629 \u0639\u0646 \u0627\u0644\u062C\u062F\u0648\u0644\u0629 [\u062A\u062D\u062F\u064A\u062B \u0643\u0644 {0} \u062B\u0627]
+
+TabbedPane.overviewWidgetTooltip=\u0627\u0646\u0642\u0631 \u0644\u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0644\u0645\u062D\u0629 \u0648 \u0627\u0644\u0639\u0648\u062F\u0629 \u0625\u0644\u0649 \u0627\u0644\u062C\u062F\u0648\u0644\u0629
+
+Tooltip.menuSearchButton=\u0627\u0646\u0642\u0631 \u0644\u0625\u0638\u0647\u0627\u0631 \u0644\u0648\u062d\u0629 \u0627\u0644\u0628\u062d\u062b \u0641\u064a \u0627\u0644\u0642\u0648\u0627\u0626\u0645
+
+Tooltip.menuSearchField=\u0627\u062f\u062e\u0644 \u0627\u0644\u0646\u0635 \u0627\u0644\u0645\u0637\u0644\u0648\u0628 \u062b\u0645 \u0627\u0636\u063a\u0637 \u0632\u0631 "\u0627\u062f\u062e\u0644" \u0644\u0644\u0628\u062d\u062b
+
+Tooltip.menuSearchTooltip=\u0627\u0646\u0642\u0631 \u0644\u062A\u062D\u062F\u064A\u062F \u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0623\u0648\u0627\u0645\u0631
+
+EditMenu.copy=\u0646\u0633\u062E
+
+EditMenu.cut=\u0642\u0635
+
+EditMenu.paste=\u0644\u0635\u0642
+
+EditMenu.delete=\u062D\u0630\u0641
+
+EditMenu.selectAll=\u062A\u062D\u062F\u064A\u062F \u0627\u0644\u0643\u0644
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_bg.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_bg.properties
new file mode 100644
index 0000000..b85e9d7
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_bg.properties
@@ -0,0 +1,25 @@
+# Bulgarian translation by Zar Petkov
+
+TabbedPane.overviewButtonTooltip=\u041F\u043E\u043A\u0430\u0436\u0438 \u043F\u0440\u0435\u0433\u043B\u0435\u0434 \u043D\u0430 \u0435\u0442\u0438\u043A\u0435\u0442\u0438 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0438\u0442\u0435
+
+TabbedPane.overviewDialogTitle=\u041F\u0440\u0435\u0433\u043B\u0435\u0434 \u043D\u0430 \u0435\u0442\u0438\u043A\u0435\u0442\u0438 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0438\u0442\u0435
+
+TabbedPane.overviewDialogTitleRefresh=\u041F\u0440\u0435\u0433\u043B\u0435\u0434 \u043D\u0430 \u0435\u0442\u0438\u043A\u0435\u0442\u0438 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0438\u0442\u0435 [\u043E\u0431\u043D\u043E\u0432\u0438 \u043D\u0430 \u0432\u0441\u0435\u043A\u0438 {0} \u0441\u0435\u043A]
+
+TabbedPane.overviewWidgetTooltip=\u041A\u043B\u0438\u043A\u043D\u0438 \u0437\u0430 \u0434\u0430 \u0437\u0430\u0442\u0432\u043E\u0440\u0438\u0448 \u043F\u0440\u0435\u0433\u043B\u0435\u0434\u0430 \u0438 \u0438\u0437\u0431\u043E\u0440\u0430 \u043D\u0430 \u0435\u0442\u0438\u043A\u0435\u0442 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430\u0442\u0430
+
+Tooltip.menuSearchButton=\u0418\u0437\u0431\u0435\u0440\u0438 \u0437\u0430 \u0434\u0430 \u0432\u0438\u0434\u0438\u0448 \u043F\u0430\u043D\u0435\u043B\u0430 \u0437\u0430 \u0442\u044A\u0440\u0441\u0435\u043D\u0435 \u0432 \u043C\u0435\u044E\u0442\u043E
+
+Tooltip.menuSearchField=\u0412\u044A\u0432\u0435\u0434\u0438 \u043D\u0438\u0437 \u0437\u0430 \u0442\u044A\u0440\u0441\u0435\u043D\u0435 \u0438 \u043D\u0430\u0442\u0438\u0441\u043D\u0438 Enter \u0437\u0430 \u0434\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u0448 \u0442\u044A\u0440\u0441\u0435\u043D\u0435
+
+Tooltip.menuSearchTooltip=\u041A\u043B\u0438\u043A\u043D\u0438 \u0437\u0430 \u0434\u0430 \u0438\u0437\u0431\u0435\u0440\u0435\u0448 \u043C\u0435\u043D\u044E
+
+EditMenu.copy=\u041A\u043E\u043F\u0438\u0440\u0430\u0439
+
+EditMenu.cut=\u041E\u0442\u0440\u0435\u0436\u0438
+
+EditMenu.paste=\u0412\u043C\u044A\u043A\u043D\u0438
+
+EditMenu.delete=\u0418\u0437\u0442\u0440\u0438\u0439
+
+EditMenu.selectAll=\u0418\u0437\u0431\u0435\u0440\u0438 \u0432\u0441\u0438\u0447\u043A\u043E
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_cs.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_cs.properties
new file mode 100644
index 0000000..84687bb
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_cs.properties
@@ -0,0 +1,25 @@
+# Czech translation by Milan Misak
+
+TabbedPane.overviewButtonTooltip=Uka\u017E p\u0159ehled z\u00E1lo\u017Eek
+
+TabbedPane.overviewDialogTitle=P\u0159ehled z\u00E1lo\u017Eek
+
+TabbedPane.overviewDialogTitleRefresh=P\u0159ehled z\u00E1lo\u017Eek [obnov ka\u017Ed\u00FDch {0} s]
+
+TabbedPane.overviewWidgetTooltip=Klikni pro zav\u0159en\u00ED p\u0159ehledu a zvolen\u00ED z\u00E1lo\u017Eky
+
+Tooltip.menuSearchButton=Klikni pro prohled\u00E1v\u00E1n\u00ED menu
+
+Tooltip.menuSearchField=Vlo\u017E hledan\u00FD \u0159et\u011Bzec a stiskni Enter
+
+Tooltip.menuSearchTooltip=Zam\u011B\u0159it polo\u017Eku
+
+EditMenu.copy=Kop\u00EDrovat
+
+EditMenu.cut=Vyjmout
+
+EditMenu.paste=Vlo\u017Eit
+
+EditMenu.delete=Smazat
+
+EditMenu.selectAll=Vybrat v\u0161e
\ No newline at end of file
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_da.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_da.properties
new file mode 100644
index 0000000..c5ef9a8
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_da.properties
@@ -0,0 +1,25 @@
+# Danish translation by Carsten O. Madsen
+
+TabbedPane.overviewButtonTooltip=Vis tab overblik
+
+TabbedPane.overviewDialogTitle=Tab overblik
+
+TabbedPane.overviewDialogTitleRefresh=Tab overblik [opdater hvert {0} sek]
+
+TabbedPane.overviewWidgetTooltip=Klik for at lukke overblik og v\u00E6lge tab
+
+Tooltip.menuSearchButton=V\u00E6lg for at vise s\u00F8g efter menu panelet
+
+Tooltip.menuSearchField=Indtast s\u00F8ge streng og tryk p\u00E5 'Enter' for at s\u00F8ge
+
+Tooltip.menuSearchTooltip=Klik for at finde menu
+
+EditMenu.copy=Kopier
+
+EditMenu.cut=Klip
+
+EditMenu.paste=Inds\u00E6t
+
+EditMenu.delete=Slet
+
+EditMenu.selectAll=Marker alt
\ No newline at end of file
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_de.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_de.properties
new file mode 100644
index 0000000..80c5e0f
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_de.properties
@@ -0,0 +1,25 @@
+# German translation by Andreas Golchert
+
+TabbedPane.overviewButtonTooltip=Tab\u00FCbersicht anzeigen
+
+TabbedPane.overviewDialogTitle=Tab\u00FCbersicht
+
+TabbedPane.overviewDialogTitleRefresh=Tab\u00FCbersicht [Alle {0} Sek neu laden]
+
+TabbedPane.overviewWidgetTooltip=Klicken Sie hier, um die Tab\u00FCbersicht zu schlie\u00DFen und den Tab zu selektieren
+
+Tooltip.menuSearchButton=Klicken Sie hier, um die Men\u00fcsuchmaske anzuzeigen
+
+Tooltip.menuSearchField=Geben Sie den Suchbegriff ein und dr\u00FCcken Sie 'Enter'
+
+Tooltip.menuSearchTooltip=Klicken Sie hier, um das Men\u00FC zu finden
+
+EditMenu.copy=Kopieren
+
+EditMenu.cut=Ausschneiden
+
+EditMenu.paste=Einf\u00FCgen
+
+EditMenu.delete=L\u00F6schen
+
+EditMenu.selectAll=Alles ausw\u00E4hlen
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_el.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_el.properties
new file mode 100644
index 0000000..a18f403
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_el.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03c3\u03ba\u03cc\u03c0\u03b7\u03c3\u03b7\u03c2 \u03ba\u03b1\u03c1\u03c4\u03b5\u03bb\u03ce\u03bd
+
+TabbedPane.overviewDialogTitle=\u0395\u03c0\u03b9\u03c3\u03ba\u03cc\u03c0\u03b7\u03c3\u03b7 \u03ba\u03b1\u03c1\u03c4\u03b5\u03bb\u03ce\u03bd
+
+TabbedPane.overviewDialogTitleRefresh=\u0395\u03c0\u03b9\u03c3\u03ba\u03cc\u03c0\u03b7\u03c3\u03b7 \u03ba\u03b1\u03c1\u03c4\u03b5\u03bb\u03ce\u03bd [\u03b1\u03bd\u03b1\u03bd\u03ad\u03c9\u03c3\u03b7 \u03ba\u03ac\u03b8\u03b5 {0} \u03b4\u03b5\u03c5\u03c4.]
+
+TabbedPane.overviewWidgetTooltip=\u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03ba\u03bb\u03b5\u03af\u03c3\u03b5\u03b9 \u03b7 \u03b5\u03c0\u03b9\u03c3\u03ba\u03cc\u03c0\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03ba\u03b1\u03c1\u03c4\u03ad\u03bb\u03b1
+
+Tooltip.menuSearchButton=\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03bd\u03b1 \u03b3\u03af\u03bd\u03b5\u03b9 \u03c0\u03c1\u03bf\u03b2\u03bf\u03bb\u03ae \u03c4\u03bf\u03c5 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf \u03bc\u03b5\u03bd\u03bf\u03cd
+
+Tooltip.menuSearchField=\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03b5\u03af\u03bc\u03b5\u03bd\u03bf \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03c0\u03b9\u03ad\u03c3\u03c4\u03b5 \u03c4\u03bf \u03c0\u03bb\u03ae\u03ba\u03c4\u03c1\u03bf \'Enter\' \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b3\u03af\u03bd\u03b5\u03b9 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7
+
+Tooltip.menuSearchTooltip=\u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03bd\u03c4\u03bf\u03c0\u03af\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03bc\u03b5\u03bd\u03bf\u03cd
+
+EditMenu.copy=\u0391\u03bd\u03c4\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae
+
+EditMenu.cut=\u0391\u03c0\u03bf\u03ba\u03bf\u03c0\u03ae
+
+EditMenu.paste=\u0395\u03c0\u03b9\u03ba\u03cc\u03bb\u03bb\u03b7\u03c3\u03b7
+
+EditMenu.delete=\u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae
+
+EditMenu.selectAll=\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03cc\u03bb\u03c9\u03bd
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_en_GB.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_en_GB.properties
new file mode 100644
index 0000000..6c23143
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_en_GB.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=Show tab overview
+
+TabbedPane.overviewDialogTitle=Tab overview
+
+TabbedPane.overviewDialogTitleRefresh=Tab overview [refresh every {0} sec]
+
+TabbedPane.overviewWidgetTooltip=Click to close overview and select tab
+
+Tooltip.menuSearchButton=Select to view menu search panel
+
+Tooltip.menuSearchField=Enter search string and press \'Enter\' button to search
+
+Tooltip.menuSearchTooltip=Click to locate menu
+
+EditMenu.copy=Copy
+
+EditMenu.cut=Cut
+
+EditMenu.paste=Paste
+
+EditMenu.delete=Delete
+
+EditMenu.selectAll=Select all
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_en_US.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_en_US.properties
new file mode 100644
index 0000000..dbc4133
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_en_US.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=Show tab overview
+
+TabbedPane.overviewDialogTitle=Tab Overview
+
+TabbedPane.overviewDialogTitleRefresh=Tab Overview [refresh every {0} sec]
+
+TabbedPane.overviewWidgetTooltip=Click to close overview and select tab
+
+Tooltip.menuSearchButton=Select to view menu search panel
+
+Tooltip.menuSearchField=Enter search string and press \'Enter\' button to search
+
+Tooltip.menuSearchTooltip=Click to locate menu
+
+EditMenu.copy=Copy
+
+EditMenu.cut=Cut
+
+EditMenu.paste=Paste
+
+EditMenu.delete=Delete
+
+EditMenu.selectAll=Select all
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_es.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_es.properties
new file mode 100644
index 0000000..899468f
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_es.properties
@@ -0,0 +1,14 @@
+# Danish translation by David �lvarez Le�n
+
+TabbedPane.overviewButtonTooltip=Muestra los paneles pesta\u00F1a
+
+TabbedPane.overviewDialogTitle=Muestra de paneles pesta\u00F1a
+
+TabbedPane.overviewWidgetTooltip=Pulse para cerrar y seleccionar el panel
+
+Tooltip.menuSearchButton=Selecciona para ver el panel de b\u00FAsqueda.
+
+Tooltip.menuSearchField=Escriba el t\u00E9rmino de b\u00FAsqueda y presione el bot\u00F3n "ENTER" para buscar.
+
+Tooltip.menuSearchTooltip=Pulse para situar el menu
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_es_AR.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_es_AR.properties
new file mode 100644
index 0000000..b0eb94e
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_es_AR.properties
@@ -0,0 +1,25 @@
+# Spanish (Argentina) translation by Iv�n Ridao Freitas
+
+TabbedPane.overviewButtonTooltip=Mostrar visi\u00f3n general de las pesta\u00F1as
+
+TabbedPane.overviewDialogTitle=Visi\u00f3n general de las pesta\u00F1as
+
+TabbedPane.overviewDialogTitleRefresh=Visi\u00f3n general de las pesta\u00F1as [actualizar cada {0} seg.]
+
+TabbedPane.overviewWidgetTooltip=Haga clic para cerrar la visi\u00f3n general y seleccionar la pesta\u00F1a
+
+Tooltip.menuSearchButton=Seleccione para ver el panel de b\u00fasqueda del men\u00fa
+
+Tooltip.menuSearchField=Ingrese la cadena de b\u00fasqueda y presione la tecla 'Enter' para buscar
+
+Tooltip.menuSearchTooltip=Haga clic para ubicar el men\u00fa
+
+EditMenu.copy=Copiar
+
+EditMenu.cut=Cortar
+
+EditMenu.paste=Pegar
+
+EditMenu.delete=Eliminar
+
+EditMenu.selectAll=Seleccionar todo
\ No newline at end of file
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_es_MX.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_es_MX.properties
new file mode 100644
index 0000000..cbe7666
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_es_MX.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=Mostrar descripci\u00f3n general de ficha
+
+TabbedPane.overviewDialogTitle=Descripci\u00f3n general de ficha
+
+TabbedPane.overviewDialogTitleRefresh=Descripci\u00f3n general de ficha [actualizar cada {0} seg.]
+
+TabbedPane.overviewWidgetTooltip=Haga clic para cerrar descripci\u00f3n general y seleccionar ficha
+
+Tooltip.menuSearchButton=Seleccione para ver el panel de b\u00fasqueda del men\u00fa
+
+Tooltip.menuSearchField=Ingrese la cadena de b\u00fasqueda y presione la tecla \'Intro\' para buscar
+
+Tooltip.menuSearchTooltip=Haga clic para ubicar el men\u00fa
+
+EditMenu.copy=Copiar
+
+EditMenu.cut=Cortar
+
+EditMenu.paste=Pegar
+
+EditMenu.delete=Eliminar
+
+EditMenu.selectAll=Todas
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_fi.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_fi.properties
new file mode 100644
index 0000000..92ea5eb
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_fi.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=N\u00e4yt\u00e4 v\u00e4lilehden yhteenveto
+
+TabbedPane.overviewDialogTitle=V\u00e4lilehden yhteenveto
+
+TabbedPane.overviewDialogTitleRefresh=V\u00e4lilehden yhteenveto [p\u00e4ivitys {0} sekunnin v\u00e4lein]
+
+TabbedPane.overviewWidgetTooltip=Sulje yhteenveto ja valitse v\u00e4lilehti napsauttamalla
+
+Tooltip.menuSearchButton=Tarkastele valikon hakupaneelia valitsemalla
+
+Tooltip.menuSearchField=Etsi m\u00e4\u00e4ritt\u00e4m\u00e4ll\u00e4 hakujono ja paina Enter-n\u00e4pp\u00e4int\u00e4
+
+Tooltip.menuSearchTooltip=Etsi valikko napsauttamalla
+
+EditMenu.copy=Kopioi
+
+EditMenu.cut=Leikkaa
+
+EditMenu.paste=Liit\u00e4
+
+EditMenu.delete=Poista
+
+EditMenu.selectAll=Valitse kaikki
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_fr.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_fr.properties
new file mode 100644
index 0000000..3780912
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_fr.properties
@@ -0,0 +1,26 @@
+# French translation by Alois Cochard
+
+TabbedPane.overviewButtonTooltip=Afficher la vue d'ensemble des onglets
+
+TabbedPane.overviewDialogTitle=Vue d'ensemble des onglets
+
+TabbedPane.overviewDialogTitleRefresh=Vue d''ensemble des onglets [rafraichir toute les {0} sec]
+
+TabbedPane.overviewWidgetTooltip=Cliquer pour fermer la vue d'ensemble et s\u00E9l\u00E9ctionner un onglet
+
+Tooltip.menuSearchButton=Voir le panel du menu de recherche
+
+Tooltip.menuSearchField=Entrer le texte a rechercher puis presser 'Enter' pour rechercher.
+
+Tooltip.menuSearchTooltip=Cliquer pour localiser le menu
+
+EditMenu.copy=Copier
+
+EditMenu.cut=Couper
+
+EditMenu.paste=Coller
+
+EditMenu.delete=Effacer
+
+EditMenu.selectAll=S�lectionner Tout
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_fr_CA.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_fr_CA.properties
new file mode 100644
index 0000000..0046741
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_fr_CA.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=Afficher l\'aper\u00e7u des onglets
+
+TabbedPane.overviewDialogTitle=Aper\u00e7u des onglets
+
+TabbedPane.overviewDialogTitleRefresh=Aper\u00e7u des onglets [actualiser toutes les {0} sec]
+
+TabbedPane.overviewWidgetTooltip=Cliquez pour fermer l\'aper\u00e7u et s\u00e9lectionner l\'onglet
+
+Tooltip.menuSearchButton=S\u00e9lectionnez pour afficher le panneau de recherche de menu
+
+Tooltip.menuSearchField=Entrez la cha\u00eene de recherche et appuyez sur \'Entr\u00e9e\' pour effectuer la recherche
+
+Tooltip.menuSearchTooltip=Cliquez pour rep\u00e9rer le menu
+
+EditMenu.copy=Copier
+
+EditMenu.cut=Couper
+
+EditMenu.paste=Coller
+
+EditMenu.delete=Supprimer
+
+EditMenu.selectAll=S\u00e9lectionner tout
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_hu.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_hu.properties
new file mode 100644
index 0000000..7d4d263
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_hu.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=Lapf\u00fclek \u00e1ttekint\u00e9s\u00e9nek megjelen\u00edt\u00e9se
+
+TabbedPane.overviewDialogTitle=Lapf\u00fclek \u00e1ttekint\u00e9se
+
+TabbedPane.overviewDialogTitleRefresh=Lapf\u00fclek \u00e1ttekint\u00e9se [friss\u00edt\u00e9s {0} m\u00e1sodpercenk\u00e9nt]
+
+TabbedPane.overviewWidgetTooltip=Kattintson ide az \u00e1ttekint\u00e9s bez\u00e1r\u00e1s\u00e1hoz \u00e9s lapf\u00fcl kiv\u00e1laszt\u00e1s\u00e1hoz.
+
+Tooltip.menuSearchButton=V\u00e1lassza ezt a lehet\u00f5s\u00e9get a men\u00fckeres\u00e9si panel megjelen\u00edt\u00e9s\u00e9hez.
+
+Tooltip.menuSearchField=\u00cdrja be a keresett kifejez\u00e9st, majd a keres\u00e9shez nyomja meg az \u201eEnter\u201d billenty\u00fbt.
+
+Tooltip.menuSearchTooltip=Kattintson ide a men\u00fc megkeres\u00e9s\u00e9hez.
+
+EditMenu.copy=M\u00e1sol\u00e1s
+
+EditMenu.cut=Kiv\u00e1g\u00e1s
+
+EditMenu.paste=Beilleszt\u00e9s
+
+EditMenu.delete=T\u00f6rl\u00e9s
+
+EditMenu.selectAll=Az \u00f6sszes kijel\u00f6l\u00e9se
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_it.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_it.properties
new file mode 100644
index 0000000..9710b4a
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_it.properties
@@ -0,0 +1,26 @@
+# Italian translation by Sandro Martini
+
+TabbedPane.overviewButtonTooltip=Visualizza sommario dei tab
+
+TabbedPane.overviewDialogTitle=Sommario dei tab
+
+TabbedPane.overviewDialogTitleRefresh=Sommario dei Tab [aggiorna ogni {0} sec]
+
+TabbedPane.overviewWidgetTooltip=Clicca per chiudere il sommario e selezionare il tab
+
+Tooltip.menuSearchButton=Seleziona per visualizzare il menu di ricerca
+
+Tooltip.menuSearchField=Inserisci la stringa di ricerca, poi premi il tasto 'Invio'
+
+Tooltip.menuSearchTooltip=Clicca per trovare il menu
+
+EditMenu.copy=Copia
+
+EditMenu.cut=Taglia
+
+EditMenu.paste=Incolla
+
+EditMenu.delete=Cancella
+
+EditMenu.selectAll=Seleziona tutto
+
\ No newline at end of file
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_iw.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_iw.properties
new file mode 100644
index 0000000..186088a
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_iw.properties
@@ -0,0 +1,25 @@
+# Hebrew translation by Kirill Grouchnikov
+
+TabbedPane.overviewButtonTooltip=\u05D4\u05E8\u05D0\u05D4 \u05E1\u05E7\u05D9\u05E8\u05EA \u05DC\u05E9\u05D5\u05E0\u05D9\u05D5\u05EA
+
+TabbedPane.overviewDialogTitle=\u05E1\u05E7\u05D9\u05E8\u05EA \u05DC\u05E9\u05D5\u05E0\u05D9\u05D5\u05EA
+
+TabbedPane.overviewDialogTitleRefresh=\u05E1\u05E7\u05D9\u05E8\u05EA \u05DC\u05E9\u05D5\u05E0\u05D9\u05D5\u05EA [\u05E8\u05E2\u05E0\u05D5\u05DF \u05DB\u05DC {0} \u05E9]
+
+TabbedPane.overviewWidgetTooltip=\u05DC\u05D7\u05E5 \u05E1\u05D2\u05D9\u05E8\u05EA \u05E1\u05E7\u05D9\u05E8\u05D4 \u05D5\u05D1\u05D7\u05D9\u05E8\u05EA \u05DC\u05E9\u05D5\u05E0\u05D9\u05EA
+
+Tooltip.menuSearchButton=\u05DC\u05D7\u05E5 \u05DC\u05D4\u05E6\u05D2\u05EA \u05E4\u05D0\u05E0\u05DC \u05D7\u05D9\u05E4\u05D5\u05E9 \u05D1\u05EA\u05E4\u05E8\u05D9\u05D8\u05D9\u05DD
+
+Tooltip.menuSearchField=\u05D4\u05DB\u05E0\u05E1 \u05DE\u05D7\u05E8\u05D5\u05D6\u05EA \u05D5\u05DC\u05D7\u05E5 'Enter' \u05DC\u05D7\u05D9\u05E4\u05D5\u05E9
+
+Tooltip.menuSearchTooltip=\u05DC\u05D7\u05E5 \u05DC\u05D0\u05D9\u05EA\u05D5\u05E8 \u05EA\u05E4\u05E8\u05D9\u05D8
+
+EditMenu.copy=\u05D4\u05D3\u05D1\u05E7
+
+EditMenu.cut=\u05D2\u05D6\u05D5\u05E8
+
+EditMenu.paste=\u05D4\u05E2\u05EA\u05E7
+
+EditMenu.delete=\u05DE\u05D7\u05E7
+
+EditMenu.selectAll=\u05D1\u05D7\u05E8 \u05D4\u05DB\u05DC
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_ja.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_ja.properties
new file mode 100644
index 0000000..802e3cc
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_ja.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=\u30bf\u30d6\u306e\u6982\u8981\u3092\u8868\u793a
+
+TabbedPane.overviewDialogTitle=\u30bf\u30d6\u306e\u6982\u8981
+
+TabbedPane.overviewDialogTitleRefresh=\u30bf\u30d6\u306e\u6982\u8981 [{0} \u79d2\u3054\u3068\u306b\u518d\u8868\u793a]
+
+TabbedPane.overviewWidgetTooltip=\u30af\u30ea\u30c3\u30af\u3057\u3066\u6982\u8981\u3092\u9589\u3058\u3001\u30bf\u30d6\u3092\u9078\u629e\u3057\u307e\u3059
+
+Tooltip.menuSearchButton=\u30e1\u30cb\u30e5\u30fc\u691c\u7d22\u30d1\u30cd\u30eb\u3092\u8868\u793a\u3059\u308b\u306b\u306f\u3001\u9078\u629e\u3057\u307e\u3059
+
+Tooltip.menuSearchField=\u691c\u7d22\u6587\u5b57\u5217\u3092\u5165\u529b\u3057\u3001[Enter] \u30dc\u30bf\u30f3\u3092\u62bc\u3057\u3066\u691c\u7d22\u3057\u307e\u3059
+
+Tooltip.menuSearchTooltip=\u30e1\u30cb\u30e5\u30fc\u3092\u691c\u7d22\u3059\u308b\u306b\u306f\u3001\u30af\u30ea\u30c3\u30af\u3057\u307e\u3059
+
+EditMenu.copy=\u30b3\u30d4\u30fc
+
+EditMenu.cut=\u5207\u308a\u53d6\u308a
+
+EditMenu.paste=\u8cbc\u308a\u4ed8\u3051
+
+EditMenu.delete=\u524a\u9664
+
+EditMenu.selectAll=\u3059\u3079\u3066\u9078\u629e
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_nl.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_nl.properties
new file mode 100644
index 0000000..eb39517
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_nl.properties
@@ -0,0 +1,25 @@
+# Dutch translation by Ilana Paktor
+
+TabbedPane.overviewButtonTooltip=Toon het tab overzicht
+
+TabbedPane.overviewDialogTitle=Tab overzicht
+
+TabbedPane.overviewDialogTitleRefresh=Tab overzicht (verfrist elke {0} sec)
+
+TabbedPane.overviewWidgetTooltip=Klik om het overzich te sluiten en een tab te kiezen
+
+Tooltip.menuSearchButton=Kies om het menu search panel te zien
+
+Tooltip.menuSearchField=Vul zoek string in and klik 'Enter' om te zoeken
+
+Tooltip.menuSearchTooltip=Klik om het menu te vinden
+
+EditMenu.copy=Kopieer
+
+EditMenu.cut=Knip
+
+EditMenu.paste=Plak
+
+EditMenu.delete=Wis
+
+EditMenu.selectAll=Selecteer Alles
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_no.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_no.properties
new file mode 100644
index 0000000..3d8322e
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_no.properties
@@ -0,0 +1,7 @@
+# Norwegian translation by Nils-Morten Nilssen
+
+Tooltip.heapStatusPanel=Heap status. Klikk for \u00E5 kj\u00F8re garbage collector
+
+Tooltip.menuSearchButton=Klikk for \u00E5 se menys\u00F8kepanelet
+
+Tooltip.menuSearchField=Skriv s\u00F8kestrengen og trykk 'Enter' knappen for \u00E5 s\u00F8ke
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_pl.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_pl.properties
new file mode 100644
index 0000000..fb7bd6d
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_pl.properties
@@ -0,0 +1,26 @@
+# Polish translation by Maciej Zwirski
+
+TabbedPane.overviewButtonTooltip=Poka\u017C przegl\u0105d zak\u0142adek
+
+TabbedPane.overviewDialogTitle=Przegl\u0105d zak\u0142adek
+
+TabbedPane.overviewDialogTitleRefresh=Przegl\u0105d zak\u0142adek [od\u015Bwie\u017Canie co {0} sek]
+
+TabbedPane.overviewWidgetTooltip=Kliknij, aby zamkn\u0105\u0107 przegl\u0105d i wybra\u0107 zak\u0142adk\u0119
+
+Tooltip.menuSearchButton=Wybierz, aby wy\u015Bwietli\u0107 panel wyszukiwania menu
+
+Tooltip.menuSearchField=Wpisz s\u0142owo kluczowe i naci\u015Bnij przycisk 'Enter' aby wyszuka\u0107
+
+Tooltip.menuSearchTooltip=Kliknij, aby zlokalizowa\u0107 menu
+
+EditMenu.copy=Kopiuj
+
+EditMenu.cut=Wytnij
+
+EditMenu.paste=Wklej
+
+EditMenu.delete=Usu\u0144
+
+EditMenu.selectAll=Zaznacz wszystko
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_pt.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_pt.properties
new file mode 100644
index 0000000..2695365
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_pt.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=Mostrar vis\u00e3o geral da guia
+
+TabbedPane.overviewDialogTitle=Vis\u00e3o geral da guia
+
+TabbedPane.overviewDialogTitleRefresh=Vis\u00e3o geral da guia [atualizar a cada {0} s]
+
+TabbedPane.overviewWidgetTooltip=Clicar para fechar a vis\u00e3o geral e selecionar a guia
+
+Tooltip.menuSearchButton=Selecionar para exibir o painel de procura de menus
+
+Tooltip.menuSearchField=Digitar string de procura e pressionar o bot\u00e3o \'Enter\' para procurar
+
+Tooltip.menuSearchTooltip=Clicar para localizar o menu
+
+EditMenu.copy=Copiar
+
+EditMenu.cut=Recortar
+
+EditMenu.paste=Colar
+
+EditMenu.delete=Apagar
+
+EditMenu.selectAll=Selecionar tudo
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_pt_BR.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_pt_BR.properties
new file mode 100644
index 0000000..5298dd6
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_pt_BR.properties
@@ -0,0 +1,25 @@
+# Portuguese (Brazil) translation by Claudio de Oliveira Miranda
+
+TabbedPane.overviewButtonTooltip=Mostrar aba de vis\u00e3o geral
+
+TabbedPane.overviewDialogTitle=Aba de vis\u00e3o geral
+
+TabbedPane.overviewDialogTitleRefresh=Aba de vis\u00e3o geral [atualiza\u00e7\u00e3o a cada {0} s]
+
+TabbedPane.overviewWidgetTooltip=Clique aqui para fechar a aba de vis\u00e3o geral e sele\u00e7\u00e3o
+
+Tooltip.menuSearchButton=Selecione, para efetuar pesquisa na estrutura de menus
+
+Tooltip.menuSearchField=Coloque o nome de item de menu a ser pesquisado e pressione o bot\u00e3o 'Enter'
+
+Tooltip.menuSearchTooltip=Clique aqui para localizar o menu
+
+EditMenu.copy=Copiar
+
+EditMenu.cut=Recortar
+
+EditMenu.paste=Colar
+
+EditMenu.delete=Remover
+
+EditMenu.selectAll=Selecionar todos
\ No newline at end of file
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_ro.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_ro.properties
new file mode 100644
index 0000000..d582454
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_ro.properties
@@ -0,0 +1,25 @@
+# Romanian translation by Sergiu Nicolae Nacu and Ran Locar
+
+TabbedPane.overviewButtonTooltip=Arat\u0103 descrierea tabului
+
+TabbedPane.overviewDialogTitle=Descriere tab
+
+TabbedPane.overviewDialogTitleRefresh=Descriere tab [actualizare la fiecare {0} sec]
+
+TabbedPane.overviewWidgetTooltip=Apas\u0103 pentru a \u00EEnchide fereastra cu detalii \u015Fi selecteaz\u0103 tab
+
+Tooltip.menuSearchButton=Selecteaza pentru a viziona meniul panoului de c\u0103utare
+
+Tooltip.menuSearchField=Introduce\u0163i textul de c\u0103utat \u015Fi ap\u0103sa\u0163i butonul 'Enter' pentru a porni cautarea
+
+Tooltip.menuSearchTooltip=Apas\u0103 pentru a localiza meniul
+
+EditMenu.copy=Copiaz\u0103
+
+EditMenu.cut=Taie
+
+EditMenu.paste=Lipe\u015Fte
+
+EditMenu.delete=\u015Eterge
+
+EditMenu.selectAll=Selecteaz\u0103 tot
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_ru.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_ru.properties
new file mode 100644
index 0000000..f45e438
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_ru.properties
@@ -0,0 +1,25 @@
+# Russian translation by Kirill Grouchnikov
+
+TabbedPane.overviewButtonTooltip=\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043E\u0431\u0437\u043E\u0440 \u0442\u0430\u0431\u043E\u0432
+
+TabbedPane.overviewDialogTitle=\u041E\u0431\u0437\u043E\u0440 \u0442\u0430\u0431\u043E\u0432
+
+TabbedPane.overviewDialogTitleRefresh=\u041E\u0431\u0437\u043E\u0440 \u0442\u0430\u0431\u043E\u0432 [\u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u043A\u0430\u0436\u0434\u044B\u0435 {0} \u0441\u0435\u043A]
+
+TabbedPane.overviewWidgetTooltip=\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u043E\u0431\u0437\u043E\u0440 \u0438 \u0432\u044B\u0431\u0440\u0430\u0442\u044C \u0442\u0430\u0431
+
+Tooltip.menuSearchButton=\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043F\u0430\u043D\u0435\u043B\u044C \u043F\u043E\u0438\u0441\u043A\u0430 \u043C\u0435\u043D\u044E
+
+Tooltip.menuSearchField=\u0412\u0432\u0435\u0441\u0442\u0438 \u0441\u0442\u0440\u043E\u043A\u0443 \u043F\u043E\u0438\u0441\u043A\u0430 \u0438 \u043D\u0430\u0436\u0430\u0442\u044C Enter \u0434\u043B\u044F \u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u043F\u043E\u0438\u0441\u043A\u0430
+
+Tooltip.menuSearchTooltip=\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043C\u0435\u043D\u044E
+
+EditMenu.copy=\u041A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C
+
+EditMenu.cut=\u0412\u044B\u0440\u0435\u0437\u0430\u0442\u044C
+
+EditMenu.paste=\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044C
+
+EditMenu.delete=\u0423\u0434\u0430\u043B\u0438\u0442\u044C
+
+EditMenu.selectAll=\u0412\u044B\u0434\u0435\u043B\u0438\u0442\u044C \u0432\u0441\u0451
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_sv.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_sv.properties
new file mode 100644
index 0000000..e629b68
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_sv.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=Visa flik\u00f6versikt
+
+TabbedPane.overviewDialogTitle=Flik\u00f6versikt
+
+TabbedPane.overviewDialogTitleRefresh=Flik\u00f6versikt [uppdatera var {0} sek]
+
+TabbedPane.overviewWidgetTooltip=Klicka h\u00e4r om du vill st\u00e4nga \u00f6versikten och v\u00e4lja flik
+
+Tooltip.menuSearchButton=Markera f\u00f6r att visa menys\u00f6kpanelen
+
+Tooltip.menuSearchField=Ange s\u00f6kstr\u00e4ngen och tryck p\u00e5 Retur f\u00f6r att s\u00f6ka
+
+Tooltip.menuSearchTooltip=Klicka f\u00f6r att hitta meny
+
+EditMenu.copy=Kopiera
+
+EditMenu.cut=Klipp ut
+
+EditMenu.paste=Klistra in
+
+EditMenu.delete=Ta bort
+
+EditMenu.selectAll=Markera alla
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_th.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_th.properties
new file mode 100644
index 0000000..5cd83ea
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_th.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=\u0e41\u0e2a\u0e14\u0e07\u0e20\u0e32\u0e1e\u0e23\u0e27\u0e21\u0e41\u0e17\u0e47\u0e1a
+
+TabbedPane.overviewDialogTitle=\u0e20\u0e32\u0e1e\u0e23\u0e27\u0e21\u0e41\u0e17\u0e47\u0e1a
+
+TabbedPane.overviewDialogTitleRefresh=\u0e20\u0e32\u0e1e\u0e23\u0e27\u0e21\u0e41\u0e17\u0e47\u0e1a [\u0e23\u0e35\u0e40\u0e1f\u0e23\u0e0a\u0e17\u0e38\u0e01\u0e46 {0} \u0e27\u0e34\u0e19\u0e32\u0e17\u0e35]
+
+TabbedPane.overviewWidgetTooltip=\u0e04\u0e25\u0e34\u0e01\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e1b\u0e34\u0e14\u0e20\u0e32\u0e1e\u0e23\u0e27\u0e21\u0e41\u0e25\u0e30\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e41\u0e17\u0e47\u0e1a
+
+Tooltip.menuSearchButton=\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e14\u0e39\u0e41\u0e1c\u0e07\u0e04\u0e49\u0e19\u0e2b\u0e32\u0e40\u0e21\u0e19\u0e39
+
+Tooltip.menuSearchField=\u0e1b\u0e49\u0e2d\u0e19\u0e2a\u0e15\u0e23\u0e34\u0e07\u0e04\u0e49\u0e19\u0e2b\u0e32\u0e41\u0e25\u0e30\u0e01\u0e14\u0e1b\u0e38\u0e48\u0e21 \'Enter\' \u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e04\u0e49\u0e19\u0e2b\u0e32
+
+Tooltip.menuSearchTooltip=\u0e04\u0e25\u0e34\u0e01\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e04\u0e49\u0e19\u0e2b\u0e32\u0e40\u0e21\u0e19\u0e39
+
+EditMenu.copy=\u0e04\u0e31\u0e14\u0e25\u0e2d\u0e01
+
+EditMenu.cut=\u0e15\u0e31\u0e14
+
+EditMenu.paste=\u0e27\u0e32\u0e07
+
+EditMenu.delete=\u0e25\u0e1a
+
+EditMenu.selectAll=\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e17\u0e31\u0e49\u0e07\u0e2b\u0e21\u0e14
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_tr.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_tr.properties
new file mode 100644
index 0000000..b4abedb
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_tr.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=Sekmeye genel bak\u0131\u015f\u0131 g\u00f6ster
+
+TabbedPane.overviewDialogTitle=Sekmeye genel bak\u0131\u015f
+
+TabbedPane.overviewDialogTitleRefresh=Sekmeye genel bak\u0131\u015f [{0} saniyede bir yenile]
+
+TabbedPane.overviewWidgetTooltip=Genel bak\u0131\u015f\u0131 kapatmak ve sekmeyi se\u00e7mek i\u00e7in t\u0131klay\u0131n
+
+Tooltip.menuSearchButton=Men\u00fc arama panelini g\u00f6r\u00fcnt\u00fclemek i\u00e7in t\u0131klay\u0131n
+
+Tooltip.menuSearchField=Arama dizisini girin ve aramaya ba\u015flamak i\u00e7in \"Enter\" d\u00fc\u011fmesine bas\u0131n
+
+Tooltip.menuSearchTooltip=Men\u00fcy\u00fc bulmak i\u00e7in t\u0131klay\u0131n
+
+EditMenu.copy=Kopyala
+
+EditMenu.cut=Kes
+
+EditMenu.paste=Yap\u0131\u015ft\u0131r
+
+EditMenu.delete=Sil
+
+EditMenu.selectAll=T\u00fcm\u00fcn\u00fc se\u00e7
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_vi.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_vi.properties
new file mode 100644
index 0000000..9b8a8e3
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_vi.properties
@@ -0,0 +1,16 @@
+# Vietnamese translation by Thang Nguyen
+
+EditMenu.copy = Sao ch\u00E9p
+EditMenu.cut = C\u1EAFt
+EditMenu.delete = X\u00F3a
+EditMenu.paste = D\u00E1n
+EditMenu.selectAll = Ch\u1ECDn t\u1EA5t c\u1EA3
+
+TabbedPane.overviewButtonTooltip = Hi\u1EC3n th\u1ECB c\u1EEDa s\u1ED5 xem t\u1ED5ng qu\u00E1t
+TabbedPane.overviewDialogTitle = Xem t\u1ED5ng qu\u00E1t c\u00E1c tab
+TabbedPane.overviewDialogTitleRefresh = Xem t\u1ED5ng qu\u00E1t c\u00E1c tab [l\u00E0m t\u01B0\u01A1i m\u1ED7i {0} gi\u00E2y]
+TabbedPane.overviewWidgetTooltip = Nh\u1EA5p chu\u1ED9t \u0111\u1EC3 \u0111\u00F3ng c\u1EEDa s\u1ED5 t\u1ED5ng qu\u00E1t v\u00E0 ch\u1ECDn tab
+
+Tooltip.menuSearchButton = Ch\u1ECDn xem menu t\u00ECm ki\u1EBFm
+Tooltip.menuSearchField = Nh\u1EADp chu\u1ED7i c\u1EA7n t\u00ECm v\u00E0 nh\u1EA5n 'Enter' \u0111\u1EC3 t\u00ECm
+Tooltip.menuSearchTooltip = Nh\u1EA5p chu\u1ED9t \u0111\u1EC3 \u0111\u1ECBnh v\u1ECB menu
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_zh_CN.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_zh_CN.properties
new file mode 100644
index 0000000..f0f1876
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_zh_CN.properties
@@ -0,0 +1,24 @@
+# Chinese translation by Pprun
+
+TabbedPane.overviewButtonTooltip=\u663e\u793a\u9875\u7f29\u7565\u56fe
+
+TabbedPane.overviewDialogTitle=\u9875\u7f29\u7565\u56fe
+
+TabbedPane.overviewWidgetTooltip=\u5355\u51fb\u5173\u95ed\u9884\u89c8\u5e76\u8fdb\u5165\u8be5\u9875
+
+Tooltip.menuSearchButton=\u641c\u7d22\u9762\u677f
+
+Tooltip.menuSearchField=\u8f93\u5165\u641c\u7d22\u5b57\u7b26\u4e32\u540e\u6309\u2018Enter\u2019\u8fdb\u884c\u641c\u7d22
+
+Tooltip.menuSearchTooltip=\u5355\u51fb\u8fdb\u884c\u83dc\u5355\u5b9a\u4f4d
+
+EditMenu.copy=\u590d\u5236
+
+EditMenu.cut=\u526a\u5207
+
+EditMenu.paste=\u7c98\u8d34
+
+EditMenu.delete=\u5220\u9664
+
+EditMenu.selectAll=\u5168\u9009
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_zh_HK.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_zh_HK.properties
new file mode 100644
index 0000000..9e14dfa
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_zh_HK.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=\u986f\u793a\u6b04\u76ee\u6982\u89bd
+
+TabbedPane.overviewDialogTitle=\u6b04\u76ee\u6982\u89bd
+
+TabbedPane.overviewDialogTitleRefresh=\u6b04\u76ee\u6982\u89bd [\u6bcf {0} \u79d2\u66f4\u65b0]
+
+TabbedPane.overviewWidgetTooltip=\u9ede\u64ca\u4f86\u95dc\u9589\u6982\u89bd\u53ca\u9078\u64c7\u6b04\u76ee
+
+Tooltip.menuSearchButton=\u9078\u64c7\u4f86\u67e5\u770b\u76ee\u9304\u641c\u5c0b\u9762\u677f
+
+Tooltip.menuSearchField=\u8f38\u5165\u641c\u5c0b\u5b57\u4e32\u53ca\u6309\u300c\u8f38\u5165\u300d\u6309\u9215\u4f86\u641c\u5c0b
+
+Tooltip.menuSearchTooltip=\u9ede\u64ca\u4f86\u627e\u51fa\u76ee\u9304\u7684\u4f4d\u7f6e
+
+EditMenu.copy=\u8907\u88fd
+
+EditMenu.cut=\u526a\u4e0b
+
+EditMenu.paste=\u8cbc\u4e0a
+
+EditMenu.delete=\u522a\u9664
+
+EditMenu.selectAll=\u9078\u64c7\u5168\u90e8
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_zh_TW.properties b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_zh_TW.properties
new file mode 100644
index 0000000..4f4edbb
--- /dev/null
+++ b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/resources/Labels_zh_TW.properties
@@ -0,0 +1,24 @@
+TabbedPane.overviewButtonTooltip=\u986f\u793a\u9078\u9805\u6a19\u7c64\u6982\u89bd
+
+TabbedPane.overviewDialogTitle=\u9078\u9805\u6a19\u7c64\u6982\u89bd
+
+TabbedPane.overviewDialogTitleRefresh=\u9078\u9805\u6a19\u7c64\u6982\u89bd [\u6bcf\u3000{0}\u3000\u79d2\u66f4\u65b0\u4e00\u6b21]
+
+TabbedPane.overviewWidgetTooltip=\u9ede\u9078\u672c\u9215\uff0c\u95dc\u9589\u6982\u89bd\u5f8c\u9078\u64c7\u9078\u9805\u6a19\u7c64
+
+Tooltip.menuSearchButton=\u9078\u64c7\u672c\u9215\uff0c\u6aa2\u5f0f\u76ee\u9304\u641c\u5c0b\u8996\u6846\u3002
+
+Tooltip.menuSearchField=\u8f38\u5165\u641c\u5c0b\u5b57\u4e32\uff0c\u6309 \'Enter\' \u9375\u958b\u59cb\u641c\u5c0b
+
+Tooltip.menuSearchTooltip=\u9ede\u9078\u672c\u9215\u9078\u64c7\u76ee\u9304
+
+EditMenu.copy=\u8907\u88fd
+
+EditMenu.cut=\u526a\u4e0b
+
+EditMenu.paste=\u8cbc\u4e0a
+
+EditMenu.delete=\u522a\u9664
+
+EditMenu.selectAll=\u9078\u64c7\u5168\u90e8
+
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/scroll/resource/autoscroll_all.png b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/scroll/resource/autoscroll_all.png
new file mode 100644
index 0000000..69132d8
Binary files /dev/null and b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/scroll/resource/autoscroll_all.png differ
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/scroll/resource/autoscroll_h.png b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/scroll/resource/autoscroll_h.png
new file mode 100644
index 0000000..281268e
Binary files /dev/null and b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/scroll/resource/autoscroll_h.png differ
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/scroll/resource/autoscroll_v.png b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/scroll/resource/autoscroll_v.png
new file mode 100644
index 0000000..efd55c3
Binary files /dev/null and b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/scroll/resource/autoscroll_v.png differ
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-copy.png b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-copy.png
new file mode 100644
index 0000000..8dd48c4
Binary files /dev/null and b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-copy.png differ
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-cut.png b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-cut.png
new file mode 100644
index 0000000..dc9eb9a
Binary files /dev/null and b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-cut.png differ
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-delete.png b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-delete.png
new file mode 100644
index 0000000..ea03150
Binary files /dev/null and b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-delete.png differ
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-paste.png b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-paste.png
new file mode 100644
index 0000000..24588a3
Binary files /dev/null and b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-paste.png differ
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-select-all.png b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-select-all.png
new file mode 100644
index 0000000..f4b0b19
Binary files /dev/null and b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/text/edit-select-all.png differ
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/tree/dnd/icons/drop-not-allowed.png b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/tree/dnd/icons/drop-not-allowed.png
new file mode 100644
index 0000000..88652e3
Binary files /dev/null and b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/tree/dnd/icons/drop-not-allowed.png differ
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/tree/dnd/icons/drop-on-leaf.png b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/tree/dnd/icons/drop-on-leaf.png
new file mode 100644
index 0000000..6ce938d
Binary files /dev/null and b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/tree/dnd/icons/drop-on-leaf.png differ
diff --git a/laf-widget/src/main/resources/org/pushingpixels/lafwidget/utils/shadow.png b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/utils/shadow.png
new file mode 100644
index 0000000..2bf27f2
Binary files /dev/null and b/laf-widget/src/main/resources/org/pushingpixels/lafwidget/utils/shadow.png differ
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..313c9a3
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include "trident", "laf-widget", "laf-plugin", "substance", "flamingo", "substance-flamingo", "substance-swingx"
diff --git a/substance-flamingo/build.gradle b/substance-flamingo/build.gradle
new file mode 100755
index 0000000..229100b
--- /dev/null
+++ b/substance-flamingo/build.gradle
@@ -0,0 +1,153 @@
+configurations {
+ testCompile { extendsFrom compile }
+ toolsCompile { extendsFrom compile }
+}
+
+sourceSets {
+ main
+ test
+ tools {
+ compileClasspath = sourceSets.main.output + sourceSets.test.output + configurations.toolsCompile + configurations.testCompile
+ }
+}
+
+dependencies {
+ compile project(":substance")
+ compile project(":flamingo")
+ testCompile project(":substance").sourceSets.test.output
+ testCompile project(":flamingo").sourceSets.test.output
+ testCompile group: 'com.jgoodies', name: 'forms', version: '1.2.0'
+ toolsCompile project(":substance")
+ toolsCompile group: 'org.easytesting', name: 'fest-swing', version: '1.2.1'
+ toolsCompile group: 'asm', name: 'asm-all', version: '2.2.3'
+}
+
+task augmentation(dependsOn: classes) {
+ description = "Performs code augmentaiton for the laf-plugin and laf-widget libraries on the substance jar classes"
+
+ doLast {
+ def augmentClassPath = configurations.toolsCompile.asPath + File.pathSeparator + configurations.compile.asPath
+
+ ant.taskdef(name: 'delegate-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentTask", classpath: augmentClassPath)
+ ant.taskdef(name: 'delegate-update-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentUpdateTask", classpath: augmentClassPath)
+ ant.taskdef(name: 'laf-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentMainTask", classpath: augmentClassPath)
+ ant.taskdef(name: 'icon-ghosting-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentIconGhostingTask", classpath: augmentClassPath)
+ ant.taskdef(name: 'container-ghosting-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentContainerGhostingTask", classpath: augmentClassPath)
+
+ def verboseAugmentation = false
+
+ // Delegate augmentation
+ ant.'delegate-update-augment'(verbose: verboseAugmentation, pattern: ".*UI\u002Eclass") {
+ classpathset(dir: sourceSets.main.output.classesDir)
+ }
+
+ ant.'delegate-augment'(verbose: verboseAugmentation, pattern: ".*UI\u002Eclass") {
+ classpathset(dir: sourceSets.main.output.classesDir)
+ }
+
+ // Container ghosting augmentation
+ ant.'container-ghosting-augment'(verbose: verboseAugmentation) {
+ classpathset(dir: sourceSets.main.output.classesDir)
+ containerghosting(className: "org.pushingpixels.substance.flamingo.ribbon.ui.SubstanceRibbonGalleryUI", toInjectAfterOriginal: "true")
+ containerghosting(className: "org.pushingpixels.substance.flamingo.ribbon.ui.SubstanceRibbonButtonUI", toInjectAfterOriginal: "true")
+ }
+ }
+}
+
+jar {
+ dependsOn augmentation
+ //dependsOn toolsClasses
+
+ manifest {
+ attributes(
+ "Substance-Version": version,
+ "Flamingo-Version": version,
+ "Substance-VersionName": versionKey,
+ "Flamingo-VersionName": versionKey,
+ )
+ }
+
+}
+
+task liteJar(type: Jar) {
+ dependsOn augmentation
+
+ classifier = 'lite'
+
+ manifest {
+ attributes(
+ "Substance-Version": version,
+ "Flamingo-Version": version,
+ "Substance-VersionName": versionKey,
+ "Flamingo-VersionName": versionKey,
+ )
+ }
+}
+
+task testJar(type: Jar) {
+ classifier = 'tst'
+
+ from sourceSets.test.output
+
+ manifest {
+ attributes(
+ "Substance-Version": version,
+ "Flamingo-Version": version,
+ "Substance-VersionName": versionKey,
+ "Flamingo-VersionName": versionKey,
+ )
+ }
+}
+
+artifacts {
+ archives liteJar
+ distro liteJar
+}
+
+uploadPublished {
+ repositories {
+ mavenDeployer {
+ configurePOM(pom)
+ }
+ }
+}
+
+install {
+ configurePOM(repositories.mavenInstaller.pom)
+}
+
+private def configurePOM(def pom) {
+ configureBasePom(pom)
+ pom.project {
+ name "substance-flamingo"
+ description "A fork of @kirilcool's substance project"
+ url "http://insubstantial.github.com/insubstantial/substance-flamingo/"
+ licenses {
+ license {
+ name 'BSD License'
+ url 'http://www.opensource.org/licenses/bsd-license.php'
+ distribution 'repo'
+ comments "Does not cover the Xoetrope Color Wheel"
+ }
+ license {
+ name 'Mozilla Public License 1.1'
+ url 'http://www.opensource.org/licenses/mozilla1.1'
+ distribution 'repo'
+ comments "Covers the Xoetrope Color Wheel"
+ }
+ }
+ }
+}
+
+task testSubstanceRibbon(type: JavaExec) {
+ main = 'test.substance.ribbon.NewCheckRibbon'
+ debug = Boolean.valueOf(System.getProperty('debug', 'false'))
+ classpath = sourceSets.test.runtimeClasspath
+}
+
+task testSubstanceRibbonRTL(type: JavaExec) {
+ main = 'test.substance.ribbon.NewCheckRibbon'
+ debug = Boolean.valueOf(System.getProperty('debug', 'false'))
+ classpath = sourceSets.test.runtimeClasspath
+ systemProperty "user.language", "iw"
+}
diff --git a/substance-flamingo/doc-robot-skins.sh b/substance-flamingo/doc-robot-skins.sh
new file mode 100755
index 0000000..ecdc652
--- /dev/null
+++ b/substance-flamingo/doc-robot-skins.sh
@@ -0,0 +1,29 @@
+set JAVA="/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/bin/java"
+
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Autumn
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Business
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.BusinessBlackSteel
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.BusinessBlueSteel
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.ChallengerDeep
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.CeruleanSkin
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Creme
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.CremeCoffee
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Dust
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.DustCoffee
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.EmeraldDusk
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Gemini
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Graphite
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.GraphiteAqua
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.GraphiteGlass
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Magellan
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.MistSilver
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.MistAqua
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Moderate
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Nebula
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.NebulaBrickWall
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.OfficeBlack2007
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.OfficeBlue2007
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.OfficeSilver2007
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Raven
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Sahara
+JAVA -cp ../substance/drop/substance-flamingo-tools.jar:../substance/lib/build/forms-1.2.0.jar:../flamingo/drop/flamingo.jar:../flamingo/drop/flamingo-tst.jar:../trident/drop/trident.jar:../substance/drop/substance.jar:./drop/substance-flamingo.jar:./drop/substance-flamingo-tst.jar:../substance/lib/test/fest-swing-1.2.jar:../substance/lib/test/fest-reflect-1.2.jar:../substance/lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Twilight
diff --git a/substance-flamingo/settings.gradle b/substance-flamingo/settings.gradle
new file mode 100755
index 0000000..6757b4a
--- /dev/null
+++ b/substance-flamingo/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'substance-flamingo'
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/FlamingoPlugin.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/FlamingoPlugin.java
new file mode 100644
index 0000000..5d9e569
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/FlamingoPlugin.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo;
+
+import java.awt.*;
+
+import javax.swing.SwingConstants;
+import javax.swing.border.Border;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.flamingo.api.bcb.JBreadcrumbBar;
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.popup.JCommandPopupMenu;
+import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
+import org.pushingpixels.flamingo.api.ribbon.*;
+import org.pushingpixels.flamingo.internal.ui.common.JRichTooltipPanel;
+import org.pushingpixels.flamingo.internal.ui.common.popup.JColorSelectorPanel;
+import org.pushingpixels.flamingo.internal.ui.ribbon.*;
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuButton;
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuPopupPanel;
+import org.pushingpixels.lafplugin.LafComponentPlugin;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.fonts.FontSet;
+import org.pushingpixels.substance.flamingo.ribbon.ui.SubstanceRibbonBandBorder;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.border.SubstanceBorder;
+
+/**
+ * Plugin for Flamingo components.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class FlamingoPlugin implements LafComponentPlugin {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafplugin.LafComponentPlugin#getDefaults(java.lang.
+ * Object)
+ */
+ @Override
+ public Object[] getDefaults(Object mSkin) {
+ String UI_COMMON_CLASSNAME_PREFIX = "org.pushingpixels.substance.flamingo.common.ui.Substance";
+
+ String UI_RIBBON_CLASSNAME_PREFIX = "org.pushingpixels.substance.flamingo.ribbon.ui.Substance";
+
+ String UI_BCB_CLASSNAME_PREFIX = "org.pushingpixels.substance.flamingo.bcb.ui.Substance";
+
+ FontSet fontSet = SubstanceLookAndFeel.getFontPolicy().getFontSet(
+ "Substance", null);
+ Font controlFont = fontSet.getControlFont();
+
+ SubstanceSkin skin = (SubstanceSkin) mSkin;
+ Border textBorder = new BorderUIResource(new SubstanceBorder());
+
+ SubstanceColorScheme mainActiveScheme = skin
+ .getActiveColorScheme(DecorationAreaType.NONE);
+ Color backgroundColor = new ColorUIResource(mainActiveScheme
+ .getBackgroundFillColor());
+ Color disabledForegroundColor = SubstanceColorUtilities
+ .getForegroundColor(mainActiveScheme);
+
+ Object[] defaults = new Object[] {
+ JCommandButtonPanel.uiClassID,
+ UI_COMMON_CLASSNAME_PREFIX + "CommandButtonPanelUI",
+
+ JCommandButton.uiClassID,
+ UI_COMMON_CLASSNAME_PREFIX + "CommandButtonUI",
+
+ JCommandMenuButton.uiClassID,
+ UI_COMMON_CLASSNAME_PREFIX + "CommandMenuButtonUI",
+
+ JCommandToggleButton.uiClassID,
+ UI_COMMON_CLASSNAME_PREFIX + "CommandToggleButtonUI",
+
+ JCommandToggleMenuButton.uiClassID,
+ UI_COMMON_CLASSNAME_PREFIX + "CommandToggleMenuButtonUI",
+
+ JPopupPanel.uiClassID,
+ UI_COMMON_CLASSNAME_PREFIX + "PopupPanelUI",
+
+ JScrollablePanel.uiClassID,
+ UI_COMMON_CLASSNAME_PREFIX + "ScrollablePanelUI",
+
+ JCommandPopupMenu.uiClassID,
+ UI_COMMON_CLASSNAME_PREFIX + "CommandPopupMenuUI",
+
+ JColorSelectorPanel.uiClassID,
+ UI_COMMON_CLASSNAME_PREFIX + "ColorSelectorPanelUI",
+
+ JBandControlPanel.uiClassID,
+ UI_RIBBON_CLASSNAME_PREFIX + "BandControlPanelUI",
+
+ JFlowBandControlPanel.uiClassID,
+ UI_RIBBON_CLASSNAME_PREFIX + "FlowBandControlPanelUI",
+
+ JRibbon.uiClassID,
+ UI_RIBBON_CLASSNAME_PREFIX + "RibbonUI",
+
+ JRibbonRootPane.uiClassID,
+ UI_RIBBON_CLASSNAME_PREFIX + "RibbonRootPaneUI",
+
+ JRibbonBand.uiClassID,
+ UI_RIBBON_CLASSNAME_PREFIX + "RibbonBandUI",
+
+ JRibbonGallery.uiClassID,
+ UI_RIBBON_CLASSNAME_PREFIX + "RibbonGalleryUI",
+
+ JRibbonApplicationMenuButton.uiClassID,
+ UI_RIBBON_CLASSNAME_PREFIX + "RibbonApplicationMenuButtonUI",
+
+ JRibbonApplicationMenuPopupPanel.uiClassID,
+ UI_RIBBON_CLASSNAME_PREFIX
+ + "RibbonApplicationMenuPopupPanelUI",
+
+ JRibbonTaskToggleButton.uiClassID,
+ UI_RIBBON_CLASSNAME_PREFIX + "RibbonTaskToggleButtonUI",
+
+ JRichTooltipPanel.uiClassID,
+ UI_RIBBON_CLASSNAME_PREFIX + "RichTooltipPanelUI",
+
+ JRibbonComponent.uiClassID,
+ UI_RIBBON_CLASSNAME_PREFIX + "RibbonComponentUI",
+
+ JBreadcrumbBar.uiClassID,
+ UI_BCB_CLASSNAME_PREFIX + "BreadcrumbBarUI",
+
+ "BreadcrumbBar.font",
+ controlFont,
+
+ "IconPanel.font",
+ fontSet.getTitleFont(),
+
+ "CommandButtonPanel.font",
+ controlFont.deriveFont(Font.BOLD, controlFont.getSize() + 1),
+
+ "Ribbon.font",
+ controlFont,
+
+ "ControlPanel.border",
+ null,
+
+ "ControlPanel.background",
+ backgroundColor,
+
+ "CommandButton.popupActionIcon",
+ new IconUIResource(SubstanceImageCreator
+ .getDoubleArrowIconDelta(SubstanceSizeUtils
+ .getControlFontSize(), -2, -1, -0.5f,
+ SwingConstants.SOUTH, mainActiveScheme)),
+
+ "PopupPanel.border", textBorder,
+
+ "PopupGallery.background", backgroundColor,
+
+ "Ribbon.border",
+ new BorderUIResource.EmptyBorderUIResource(1, 1, 1, 1),
+
+ "RibbonBand.border", new SubstanceRibbonBandBorder(),
+
+ "RibbonBand.background", backgroundColor,
+
+ "RibbonGallery.border",
+ new BorderUIResource.EmptyBorderUIResource(2, 2, 2, 2),
+
+ "RibbonGallery.margin", new Insets(3, 3, 3, 3),
+
+ "ToggleButton.background", backgroundColor,
+
+ "ToggleButton.disabledText", disabledForegroundColor };
+ return defaults;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafplugin.LafComponentPlugin#uninitialize()
+ */
+ @Override
+ public void uninitialize() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafplugin.LafComponentPlugin#initialize()
+ */
+ @Override
+ public void initialize() {
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/bcb/ui/SubstanceBreadcrumbBarUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/bcb/ui/SubstanceBreadcrumbBarUI.java
new file mode 100644
index 0000000..a0b8885
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/bcb/ui/SubstanceBreadcrumbBarUI.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.bcb.ui;
+
+import java.awt.Color;
+import java.awt.Graphics;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.flamingo.api.bcb.JBreadcrumbBar;
+import org.pushingpixels.flamingo.internal.ui.bcb.BasicBreadcrumbBarUI;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI delegate for breadcrumb bar.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceBreadcrumbBarUI extends BasicBreadcrumbBarUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceBreadcrumbBarUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.bcb.ui.BasicBreadcrumbBarUI#installDefaults(org.jvnet
+ * .flamingo.bcb.JBreadcrumbBar)
+ */
+ @Override
+ protected void installDefaults(JBreadcrumbBar bar) {
+ super.installDefaults(bar);
+ Color backgr = bar.getBackground();
+ if ((backgr == null) || (backgr instanceof UIResource)) {
+ backgr = SubstanceColorSchemeUtilities.getColorScheme(bar,
+ ComponentState.ENABLED).getBackgroundFillColor();
+ bar.setBackground(new ColorUIResource(backgr));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.bcb.ui.BasicBreadcrumbBarUI#uninstallDefaults(org.
+ * jvnet.flamingo.bcb.BreadcrumbBar)
+ */
+ @Override
+ protected void uninstallDefaults(JBreadcrumbBar bar) {
+ DecorationPainterUtils.clearDecorationType(bar);
+ super.uninstallDefaults(bar);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ BackgroundPaintingUtils.update(g, c, false);
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/TransitionAwareResizableIcon.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/TransitionAwareResizableIcon.java
new file mode 100644
index 0000000..5789a63
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/TransitionAwareResizableIcon.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.common;
+
+import java.awt.AlphaComposite;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.utils.HashMapKey;
+import org.pushingpixels.substance.internal.utils.LazyResettableHashMap;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAware;
+
+/**
+ * Icon with transition-aware capabilities. Has a delegate that does the actual
+ * painting based on the transition themes. This class is used heavily on
+ * Substance-provided icons, such as title pane button icons, arrow icons on
+ * scroll bars and combos etc.
+ *
+ * @author Kirill Grouchnikov
+ */
+ at TransitionAware
+public class TransitionAwareResizableIcon implements ResizableIcon {
+ /**
+ * The width of the rendered image.
+ */
+ protected int width;
+
+ /**
+ * The height of the rendered image.
+ */
+ protected int height;
+
+ /**
+ * The delegate needs to implement the method in this interface based on the
+ * provided theme. The theme is computed based on the transitions that are
+ * happening on the associated button.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static interface Delegate {
+ /**
+ * Returns the icon that matches the specified theme.
+ *
+ * @param scheme
+ * Color scheme.
+ * @param width
+ * Icon width.
+ * @param height
+ * Icon height.
+ * @return Icon that matches the specified theme.
+ */
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme, int width,
+ int height);
+ }
+
+ /**
+ * The associated component.
+ */
+ private JComponent comp;
+
+ private StateTransitionTrackerDelegate stateTransitionTrackerDelegate;
+
+ /**
+ * Delegate to compute the actual icons.
+ */
+ private Delegate delegate;
+
+ /**
+ * Icon cache to speed up the subsequent icon painting. The basic assumption
+ * is that the {@link #delegate} returns an icon that paints the same for
+ * the same parameters.
+ */
+ private LazyResettableHashMap<Icon> iconMap;
+
+ public static interface StateTransitionTrackerDelegate {
+ public StateTransitionTracker getStateTransitionTracker();
+ }
+
+ /**
+ * Creates a new transition-aware icon.
+ *
+ * @param button
+ * Associated command button.
+ * @param delegate
+ * Delegate to compute the actual icons.
+ * @param initialDim
+ * Initial icon dimension.
+ */
+ public TransitionAwareResizableIcon(AbstractCommandButton button,
+ StateTransitionTrackerDelegate stateTransitionTrackerDelegate,
+ Delegate delegate, Dimension initialDim) {
+ this.comp = button;
+ this.stateTransitionTrackerDelegate = stateTransitionTrackerDelegate;
+ this.delegate = delegate;
+ this.iconMap = new LazyResettableHashMap<Icon>(
+ "TransitionAwareResizableIcon");
+ this.width = initialDim.width;
+ this.height = initialDim.height;
+ }
+
+ /**
+ * Returns the current icon to paint.
+ *
+ * @return Icon to paint.
+ */
+ private Icon getIconToPaint() {
+ StateTransitionTracker stateTransitionTracker = this.stateTransitionTrackerDelegate
+ .getStateTransitionTracker();
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ ComponentState currState = modelStateInfo.getCurrModelState();
+
+ SubstanceColorScheme baseScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.comp, ColorSchemeAssociationKind.MARK,
+ currState);
+ float baseAlpha = SubstanceColorSchemeUtilities.getAlpha(this.comp,
+ currState);
+
+ HashMapKey keyBase = SubstanceCoreUtilities.getHashKey(baseScheme
+ .getDisplayName(), baseAlpha, this.width, this.height);
+ // System.out.println(key);
+ Icon layerBase = this.iconMap.get(keyBase);
+ if (layerBase == null) {
+ Icon baseFullOpacity = this.delegate.getColorSchemeIcon(baseScheme,
+ width, height);
+ if (baseAlpha == 1.0f) {
+ layerBase = baseFullOpacity;
+ iconMap.put(keyBase, layerBase);
+ } else {
+ BufferedImage baseImage = SubstanceCoreUtilities.getBlankImage(
+ baseFullOpacity.getIconWidth(), baseFullOpacity
+ .getIconHeight());
+ Graphics2D g2base = baseImage.createGraphics();
+ g2base.setComposite(AlphaComposite.SrcOver.derive(baseAlpha));
+ baseFullOpacity.paintIcon(this.comp, g2base, 0, 0);
+ g2base.dispose();
+ layerBase = new ImageIcon(baseImage);
+ iconMap.put(keyBase, layerBase);
+ }
+ }
+ if (currState.isDisabled() || (activeStates.size() == 1)) {
+ return layerBase;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(layerBase
+ .getIconWidth(), layerBase.getIconHeight());
+ Graphics2D g2d = result.createGraphics();
+ // draw the base layer
+ layerBase.paintIcon(this.comp, g2d, 0, 0);
+
+ // draw the other active layers
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ // System.out.println("Painting state " + activeState + "[curr is "
+ // + currState + "] with " + activeEntry.getValue());
+ if (activeState == currState)
+ continue;
+
+ float stateContribution = activeEntry.getValue().getContribution();
+ if (stateContribution > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(stateContribution));
+
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.comp,
+ ColorSchemeAssociationKind.MARK, activeState);
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(this.comp,
+ activeState);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(scheme
+ .getDisplayName(), alpha, this.width, this.height);
+ Icon layer = iconMap.get(key);
+ if (layer == null) {
+ Icon fullOpacity = this.delegate.getColorSchemeIcon(scheme,
+ width, height);
+ if (alpha == 1.0f) {
+ layer = fullOpacity;
+ iconMap.put(key, layer);
+ } else {
+ BufferedImage image = SubstanceCoreUtilities
+ .getBlankImage(fullOpacity.getIconWidth(),
+ fullOpacity.getIconHeight());
+ Graphics2D g2layer = image.createGraphics();
+ g2layer.setComposite(AlphaComposite.SrcOver
+ .derive(alpha));
+ fullOpacity.paintIcon(this.comp, g2layer, 0, 0);
+ g2layer.dispose();
+ layer = new ImageIcon(image);
+ iconMap.put(key, layer);
+ }
+ }
+ layer.paintIcon(this.comp, g2d, 0, 0);
+ }
+ }
+ g2d.dispose();
+ return new ImageIcon(result);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ this.getIconToPaint().paintIcon(c, g, x, y);
+ }
+
+ @Override
+ public void setDimension(Dimension newDimension) {
+ this.width = newDimension.width;
+ this.height = newDimension.height;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return this.height;
+ }
+
+ @Override
+ public int getIconWidth() {
+ return this.width;
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/ActionPopupTransitionAwareUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/ActionPopupTransitionAwareUI.java
new file mode 100644
index 0000000..ce0475e
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/ActionPopupTransitionAwareUI.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.common.ui;
+
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+
+public interface ActionPopupTransitionAwareUI extends TransitionAwareUI {
+ public StateTransitionTracker getActionTransitionTracker();
+
+ public StateTransitionTracker getPopupTransitionTracker();
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceColorSelectorPanelUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceColorSelectorPanelUI.java
new file mode 100644
index 0000000..7ff1f7c
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceColorSelectorPanelUI.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.common.ui;
+
+import java.awt.*;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.internal.ui.common.popup.BasicColorSelectorPanelUI;
+import org.pushingpixels.flamingo.internal.ui.common.popup.JColorSelectorPanel;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI for {@link JColorSelectorPanel} components in <b>Substance</b> look and
+ * feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceColorSelectorPanelUI extends BasicColorSelectorPanelUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceColorSelectorPanelUI();
+ }
+
+ @Override
+ protected void paintCaptionBackground(Graphics g, int x, int y, int width,
+ int height) {
+ SubstanceColorScheme bgFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.colorSelectorPanel,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ ComponentState.ENABLED);
+ SubstanceCoreUtilities.getFillPainter(this.colorSelectorPanel)
+ .paintContourBackground(g, this.colorSelectorPanel, width,
+ height, new Rectangle(x, y, width, height), false,
+ bgFillScheme, false);
+
+ SubstanceColorScheme bgBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.colorSelectorPanel,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ ComponentState.ENABLED);
+ Color borderColor = bgBorderScheme.getLineColor();
+ g.setColor(borderColor);
+ g.drawLine(x, y, x + width, y);
+ g.drawLine(x, y + height - 1, x + width, y + height - 1);
+ }
+
+ @Override
+ protected void paintBottomDivider(Graphics g, int x, int y, int width,
+ int height) {
+ SubstanceColorScheme bgBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.colorSelectorPanel,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ ComponentState.ENABLED);
+ Color borderColor = bgBorderScheme.getLineColor();
+ g.setColor(borderColor);
+ g.drawLine(x, y + height - 1, x + width, y + height - 1);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ BackgroundPaintingUtils.updateIfOpaque(g, c);
+ this.paint(g, c);
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandButtonPanelUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandButtonPanelUI.java
new file mode 100644
index 0000000..cac5d3c
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandButtonPanelUI.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.common.ui;
+
+import java.awt.*;
+import java.util.EnumSet;
+import java.util.Set;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.JCommandButtonPanel;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonPanelUI;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.SubstanceConstants.Side;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * UI for {@link JCommandButtonPanel} components in <b>Substance</b> look and
+ * feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceCommandButtonPanelUI extends BasicCommandButtonPanelUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceCommandButtonPanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonPanelUI#getGroupInsets()
+ */
+ @Override
+ protected Insets getGroupInsets() {
+ int extraPadding = SubstanceSizeUtils
+ .getExtraPadding(SubstanceSizeUtils
+ .getComponentFontSize(this.buttonPanel));
+ Insets result = super.getGroupInsets();
+ return new Insets(result.top + extraPadding,
+ result.left + extraPadding, result.bottom + extraPadding,
+ result.right + extraPadding);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonPanelUI#getGroupTitleHeight
+ * (int)
+ */
+ @Override
+ protected int getGroupTitleHeight(int groupIndex) {
+ int extraPadding = SubstanceSizeUtils
+ .getExtraPadding(SubstanceSizeUtils
+ .getComponentFontSize(this.buttonPanel));
+ return super.getGroupTitleHeight(groupIndex) + 2 * extraPadding;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonPanelUI#paintGroupBackground
+ * (java.awt.Graphics, int, int, int, int, int)
+ */
+ @Override
+ protected void paintGroupBackground(Graphics g, int groupIndex, int x,
+ int y, int width, int height) {
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(buttonPanel, ComponentState.ENABLED);
+ Color background = scheme.getBackgroundFillColor();
+ if (groupIndex % 2 == 1)
+ background = SubstanceColorUtilities.getDarkerColor(background,
+ 0.06);
+
+ BackgroundPaintingUtils.fillAndWatermark(g, this.buttonPanel,
+ background, new Rectangle(x, y, width, height));
+ }
+
+ @Override
+ protected void paintGroupTitleBackground(Graphics g, int groupIndex, int x,
+ int y, int width, int height) {
+ Set<SubstanceConstants.Side> openSides = EnumSet.of(Side.LEFT,
+ Side.RIGHT);
+ if (groupIndex == 0)
+ openSides.add(Side.TOP);
+ SubstanceColorScheme bgFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.buttonPanel,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ ComponentState.ENABLED);
+ SubstanceColorScheme bgBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.buttonPanel,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ ComponentState.ENABLED);
+ HighlightPainterUtils.paintHighlight(g, null, this.buttonPanel,
+ new Rectangle(x, y, width, height), 1.0f, openSides,
+ bgFillScheme, bgBorderScheme);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ BackgroundPaintingUtils.updateIfOpaque(g, c);
+ this.paint(g, c);
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandButtonUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandButtonUI.java
new file mode 100644
index 0000000..8ed17f5
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandButtonUI.java
@@ -0,0 +1,767 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.common.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.flamingo.api.bcb.JBreadcrumbBar;
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.CommandButtonLayoutManager.CommandButtonSeparatorOrientation;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
+import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonPopupOrientationKind;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.common.model.PopupButtonModel;
+import org.pushingpixels.flamingo.api.common.popup.JCommandPopupMenu;
+import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonUI;
+import org.pushingpixels.flamingo.internal.ui.common.ResizableIconUIResource;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.effects.GhostPaintingUtils;
+import org.pushingpixels.lafwidget.animation.effects.GhostingListener;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.flamingo.common.TransitionAwareResizableIcon;
+import org.pushingpixels.substance.flamingo.utils.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.ModelStateInfo;
+import org.pushingpixels.substance.internal.painter.SeparatorPainterUtils;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAware;
+
+/**
+ * UI for command buttons {@link JCommandButton} in <b>Substance </b> look and
+ * feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceCommandButtonUI extends BasicCommandButtonUI implements
+ ActionPopupTransitionAwareUI {
+ /**
+ * Delegate for painting the background.
+ */
+ protected ButtonBackgroundDelegate backgroundDelegate;
+
+ /**
+ * Property change listener. Listens on changes to
+ * {@link AbstractButton#MODEL_CHANGED_PROPERTY} property.
+ */
+ protected PropertyChangeListener substancePropertyListener;
+
+ /**
+ * Model change listener for ghost image effects.
+ */
+ private GhostingListener substanceModelChangeListener;
+
+ /**
+ * Tracker for visual state transitions.
+ */
+ protected CommandButtonVisualStateTracker substanceVisualStateTracker;
+
+ private ButtonModel overallRolloverModel;
+
+ protected RolloverControlListener substanceOverallRolloverListener;
+
+ protected StateTransitionTracker overallStateTransitionTracker;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceCommandButtonUI((JCommandButton) comp);
+ }
+
+ /**
+ * Creates a new UI delegate for ribbon button.
+ */
+ public SubstanceCommandButtonUI(JCommandButton button) {
+ super();
+ this.backgroundDelegate = new ButtonBackgroundDelegate();
+
+ this.overallRolloverModel = new DefaultButtonModel();
+ this.overallRolloverModel.setArmed(false);
+ this.overallRolloverModel.setSelected(false);
+ this.overallRolloverModel.setPressed(false);
+ this.overallRolloverModel.setRollover(false);
+ this.overallRolloverModel.setEnabled(button.isEnabled());
+
+ this.overallStateTransitionTracker = new StateTransitionTracker(button,
+ this.overallRolloverModel);
+
+ this.substanceVisualStateTracker = new CommandButtonVisualStateTracker();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasiccommandButtonUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ this.commandButton.putClientProperty(
+ SubstanceLookAndFeel.BUTTON_SHAPER_PROPERTY,
+ ClassicButtonShaper.INSTANCE);
+
+ this.commandButton.setOpaque(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.ui.BasicCommandButtonUI#updateBorder()
+ */
+ @Override
+ protected void updateBorder() {
+ Border currBorder = this.commandButton.getBorder();
+ if ((currBorder == null) || (currBorder instanceof UIResource)) {
+ Insets extra = SubstanceSizeUtils
+ .getDefaultBorderInsets(SubstanceSizeUtils
+ .getComponentFontSize(this.commandButton));
+ double hgapScaleFactor = this.commandButton.getHGapScaleFactor();
+ double vgapScaleFactor = this.commandButton.getVGapScaleFactor();
+
+ int top = 1 + (int) (vgapScaleFactor * extra.top);
+ int left = 2 + (int) (hgapScaleFactor * (1 + extra.left));
+ int bottom = 0 + (int) (vgapScaleFactor * extra.bottom);
+ int right = 2 + (int) (hgapScaleFactor * (1 + extra.right));
+ this.commandButton
+ .setBorder(new BorderUIResource.EmptyBorderUIResource(top,
+ left, bottom, right));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasiccommandButtonUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substanceVisualStateTracker.installListeners(this.commandButton);
+
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("actionModel".equals(evt.getPropertyName())) {
+ if (substanceModelChangeListener != null)
+ substanceModelChangeListener.unregisterListeners();
+ substanceModelChangeListener = new GhostingListener(
+ commandButton, commandButton.getActionModel());
+ substanceModelChangeListener.registerListeners();
+ }
+ if ("enabled".equals(evt.getPropertyName())) {
+ overallRolloverModel.setEnabled(commandButton.isEnabled());
+ }
+ }
+ };
+ this.commandButton
+ .addPropertyChangeListener(this.substancePropertyListener);
+
+ this.substanceModelChangeListener = new GhostingListener(
+ this.commandButton, this.commandButton.getActionModel());
+ this.substanceModelChangeListener.registerListeners();
+
+ this.substanceOverallRolloverListener = new RolloverControlListener(
+ this, this.overallRolloverModel);
+ this.commandButton
+ .addMouseListener(this.substanceOverallRolloverListener);
+ this.commandButton
+ .addMouseMotionListener(this.substanceOverallRolloverListener);
+
+ this.overallStateTransitionTracker.registerModelListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasiccommandButtonUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.substanceVisualStateTracker.uninstallListeners(this.commandButton);
+ this.substanceVisualStateTracker = null;
+
+ this.commandButton
+ .removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+
+ this.substanceModelChangeListener.unregisterListeners();
+ this.substanceModelChangeListener = null;
+
+ this.commandButton
+ .removeMouseListener(this.substanceOverallRolloverListener);
+ this.commandButton
+ .removeMouseMotionListener(this.substanceOverallRolloverListener);
+ this.substanceOverallRolloverListener = null;
+
+ this.overallStateTransitionTracker.unregisterModelListeners();
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#paintButtonBackground
+ * (java.awt.Graphics, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintButtonBackground(Graphics graphics, Rectangle toFill) {
+ if (SubstanceCoreUtilities.isButtonNeverPainted(this.commandButton))
+ return;
+
+ ButtonModel actionModel = this.commandButton.getActionModel();
+ PopupButtonModel popupModel = ((JCommandButton) this.commandButton)
+ .getPopupModel();
+ Rectangle actionArea = this.getLayoutInfo().actionClickArea;
+ Rectangle popupArea = this.getLayoutInfo().popupClickArea;
+
+ BufferedImage fullAlphaBackground = CommandButtonBackgroundDelegate
+ .getCombinedCommandButtonBackground(this.commandButton,
+ actionModel, actionArea, popupModel, popupArea);
+
+ // Two special cases here:
+ // 1. Button has flat appearance and doesn't show the popup
+ // 2. Button is disabled.
+ // For both cases, we need to set custom translucency.
+ boolean isFlat = this.commandButton.isFlat()
+ && !((JCommandButton) this.commandButton).getPopupModel()
+ .isPopupShowing();
+ boolean isSpecial = isFlat || !this.commandButton.isEnabled();
+ float extraAlpha = 1.0f;
+ if (isSpecial) {
+ if (isFlat) {
+ float extraActionAlpha = 0.0f;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : getActionTransitionTracker()
+ .getModelStateInfo().getStateContributionMap()
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState.isDisabled())
+ continue;
+ if (activeState == ComponentState.ENABLED)
+ continue;
+ extraActionAlpha += activeEntry.getValue()
+ .getContribution();
+ }
+ float extraPopupAlpha = 0.0f;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : getPopupTransitionTracker()
+ .getModelStateInfo().getStateContributionMap()
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState.isDisabled())
+ continue;
+ if (activeState == ComponentState.ENABLED)
+ continue;
+ extraPopupAlpha += activeEntry.getValue().getContribution();
+ }
+ extraAlpha = Math.max(extraActionAlpha, extraPopupAlpha);
+ } else {
+ ComponentState actionAreaState = ComponentState.getState(
+ actionModel, this.commandButton);
+ if (actionAreaState.isDisabled()) {
+ extraAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ this.commandButton, actionAreaState);
+ }
+ }
+ }
+ // System.out.println(extraAlpha);
+ extraAlpha = Math.min(1.0f, extraAlpha);
+ if (extraAlpha > 0.0f) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.commandButton, extraAlpha, graphics));
+ g2d.drawImage(fullAlphaBackground, 0, 0, null);
+ g2d.dispose();
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#paintButtonIcon(java
+ * .awt.Graphics, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintButtonIcon(Graphics g, Rectangle iconRect) {
+ JCommandButton jcb = (JCommandButton) this.commandButton;
+ Icon regular = jcb.getIcon();
+ if (toUseDisabledIcon()
+ && (jcb.getDisabledIcon() != null)
+ && ((regular != null) && !regular.getClass()
+ .isAnnotationPresent(TransitionAware.class)))
+ regular = jcb.getDisabledIcon();
+
+ if ((iconRect == null) || (regular == null) || (iconRect.width == 0)
+ || (iconRect.height == 0)) {
+ return;
+ }
+
+ boolean useThemed = SubstanceCoreUtilities
+ .useThemedDefaultIcon(this.commandButton);
+ if (regular != null) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ GhostPaintingUtils.paintGhostIcon(g2d, jcb, regular, iconRect);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(jcb, g));
+
+ if (!useThemed) {
+ regular.paintIcon(jcb, g2d, iconRect.x, iconRect.y);
+ } else {
+ StateTransitionTracker tracker = this.substanceVisualStateTracker
+ .getActionStateTransitionTracker();
+ ButtonModel model = commandButton.getActionModel();
+ if (jcb.getCommandButtonKind() == CommandButtonKind.POPUP_ONLY) {
+ tracker = this.substanceVisualStateTracker
+ .getPopupStateTransitionTracker();
+ model = jcb.getPopupModel();
+ }
+ CommandButtonBackgroundDelegate.paintThemedCommandButtonIcon(
+ g2d, iconRect, jcb, regular, model, tracker);
+ }
+ g2d.dispose();
+ }
+ }
+
+ @Override
+ protected void paintButtonHorizontalSeparator(Graphics graphics,
+ Rectangle separatorArea) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.translate(0, separatorArea.y);
+
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.commandButton,
+ ColorSchemeAssociationKind.SEPARATOR, ComponentState
+ .getState(this.commandButton.getActionModel(),
+ this.commandButton));
+
+ float fadeAlpha = this.getSeparatorAlpha();
+ g2d.setComposite(AlphaComposite.SrcOver.derive(fadeAlpha));
+
+ SeparatorPainterUtils.paintSeparator(this.commandButton, g2d,
+ colorScheme, this.commandButton.getWidth(), 1,
+ JSlider.HORIZONTAL, true, 4, 4, true);
+
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.jvnet.flamingo.common.ui.BasicCommandButtonUI#
+ * paintButtonVerticalSeparator(java.awt.Graphics, int)
+ */
+ @Override
+ protected void paintButtonVerticalSeparator(Graphics graphics,
+ Rectangle separatorArea) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.translate(separatorArea.x, 0);
+
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.commandButton,
+ ColorSchemeAssociationKind.SEPARATOR, ComponentState
+ .getState(this.commandButton.getActionModel(),
+ this.commandButton));
+
+ float fadeAlpha = this.getSeparatorAlpha();
+ g2d.setComposite(AlphaComposite.SrcOver.derive(fadeAlpha));
+
+ SeparatorPainterUtils.paintSeparator(this.commandButton, g2d,
+ colorScheme, 1, this.commandButton.getHeight(),
+ JSlider.VERTICAL, true, 4, 4, true);
+
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#isPaintingBackground()
+ */
+ @Override
+ protected boolean isPaintingBackground() {
+ if (super.isPaintingBackground())
+ return true;
+ return (this.overallStateTransitionTracker
+ .getFacetStrength(ComponentStateFacet.ROLLOVER) > 0.0f);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#isPaintingSeparators()
+ */
+ @Override
+ protected boolean isPaintingSeparators() {
+ if (super.isPaintingSeparators())
+ return true;
+ boolean hasIcon = (this.commandButton.getIcon() != null);
+ return hasIcon
+ && (this.overallStateTransitionTracker
+ .getFacetStrength(ComponentStateFacet.ROLLOVER) > 0.0f);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#createPopupActionIcon()
+ */
+ @Override
+ protected ResizableIcon createPopupActionIcon() {
+ final int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(this.commandButton);
+ int arrowIconHeight = (int) SubstanceSizeUtils
+ .getArrowIconHeight(fontSize);
+ int arrowIconWidth = (int) SubstanceSizeUtils
+ .getArrowIconWidth(fontSize);
+ ResizableIcon icon = new TransitionAwareResizableIcon(
+ this.commandButton,
+ new TransitionAwareResizableIcon.StateTransitionTrackerDelegate() {
+ @Override
+ public StateTransitionTracker getStateTransitionTracker() {
+ return getPopupTransitionTracker();
+ }
+ }, new TransitionAwareResizableIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme,
+ int width, int height) {
+ CommandButtonPopupOrientationKind orientation = ((JCommandButton) commandButton)
+ .getPopupOrientationKind();
+ int direction = (orientation == CommandButtonPopupOrientationKind.DOWNWARD) ? SwingConstants.SOUTH
+ : (commandButton.getComponentOrientation()
+ .isLeftToRight() ? SwingConstants.EAST
+ : SwingConstants.WEST);
+ // System.out.println(direction + ":" + width + ":"
+ // + height);
+ Icon result = SubstanceImageCreator.getArrowIcon(width,
+ height, SubstanceSizeUtils
+ .getArrowStrokeWidth(fontSize) - 0.5f,
+ direction, scheme);
+ // System.out.println(" --> " + result.getIconWidth()
+ // + "*" + result.getIconHeight());
+ return result;
+ }
+ }, new Dimension(arrowIconWidth, arrowIconHeight));
+ return icon;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#paint(java.awt.Graphics
+ * , javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setFont(FlamingoUtilities.getFont(this.commandButton,
+ "Ribbon.font", "Button.font", "Panel.font"));
+
+ this.layoutInfo = this.layoutManager.getLayoutInfo(this.commandButton,
+ g);
+ commandButton.putClientProperty("icon.bounds", layoutInfo.iconRect);
+ commandButton.putClientProperty("icon", commandButton.getIcon());
+
+ if (this.isPaintingBackground()) {
+ this.paintButtonBackground(g2d, new Rectangle(0, 0, c.getWidth(), c
+ .getHeight()));
+ }
+
+ // decide which command button model should be used to
+ // compute the foreground color of the command button's text
+ boolean useActionAreaForFg = layoutInfo.isTextInActionArea;
+ StateTransitionTracker transitionTrackerForFg = useActionAreaForFg ? this
+ .getActionTransitionTracker()
+ : this.getPopupTransitionTracker();
+ ModelStateInfo modelStateInfoForFg = transitionTrackerForFg
+ .getModelStateInfo();
+ ComponentState currStateForFg = modelStateInfoForFg.getCurrModelState();
+ Color fgColor = this.commandButton.getForeground();
+
+ if (fgColor instanceof UIResource) {
+ float buttonAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ this.commandButton, currStateForFg);
+ fgColor = SubstanceTextUtilities.getForegroundColor(
+ this.commandButton, this.commandButton.getText(),
+ modelStateInfoForFg, buttonAlpha);
+ }
+
+ if (layoutInfo.textLayoutInfoList != null) {
+ for (CommandButtonLayoutManager.TextLayoutInfo mainTextLayoutInfo : layoutInfo.textLayoutInfoList) {
+ if (mainTextLayoutInfo.text != null) {
+ SubstanceTextUtilities.paintText(g2d, c,
+ mainTextLayoutInfo.textRect,
+ mainTextLayoutInfo.text, -1, g2d.getFont(),
+ fgColor, g2d.getClipBounds());
+ }
+ }
+ }
+
+ if (layoutInfo.extraTextLayoutInfoList != null) {
+ Color disabledFgColor = SubstanceColorSchemeUtilities
+ .getColorScheme(this.commandButton,
+ ComponentState.DISABLED_UNSELECTED)
+ .getForegroundColor();
+ float buttonAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ this.commandButton, ComponentState.DISABLED_UNSELECTED);
+ if (buttonAlpha < 1.0f) {
+ Color bgFillColor = SubstanceColorUtilities
+ .getBackgroundFillColor(this.commandButton);
+ disabledFgColor = SubstanceColorUtilities.getInterpolatedColor(
+ disabledFgColor, bgFillColor, buttonAlpha);
+ }
+ if (currStateForFg.isDisabled()) {
+ disabledFgColor = SubstanceColorUtilities.getInterpolatedColor(
+ disabledFgColor, SubstanceColorUtilities
+ .getBackgroundFillColor(c), 0.5);
+ }
+ for (CommandButtonLayoutManager.TextLayoutInfo extraTextLayoutInfo : layoutInfo.extraTextLayoutInfoList) {
+ if (extraTextLayoutInfo.text != null) {
+ SubstanceTextUtilities.paintText(g2d, c,
+ extraTextLayoutInfo.textRect,
+ extraTextLayoutInfo.text, -1, g2d.getFont(),
+ disabledFgColor, g2d.getClipBounds());
+ }
+ }
+ }
+
+ if (layoutInfo.iconRect != null) {
+ this.paintButtonIcon(g2d, layoutInfo.iconRect);
+ }
+ if (layoutInfo.popupActionRect.getWidth() > 0) {
+ paintPopupActionIcon(g2d, layoutInfo.popupActionRect);
+ }
+
+ if (this.isPaintingSeparators() && (layoutInfo.separatorArea != null)) {
+ if (layoutInfo.separatorOrientation == CommandButtonSeparatorOrientation.HORIZONTAL) {
+ this.paintButtonHorizontalSeparator(g2d,
+ layoutInfo.separatorArea);
+ } else {
+ this
+ .paintButtonVerticalSeparator(g2d,
+ layoutInfo.separatorArea);
+ }
+ }
+
+ // g2d.setColor(Color.red);
+ // g2d.draw(layoutInfo.iconRect);
+ // g2d.setColor(Color.blue);
+ // if (layoutInfo.textLayoutInfoList != null) {
+ // for (CommandButtonLayoutManager.TextLayoutInfo mainTextLayoutInfo :
+ // layoutInfo.textLayoutInfoList) {
+ // if (mainTextLayoutInfo.text != null) {
+ // g2d.draw(mainTextLayoutInfo.textRect);
+ // }
+ // }
+ // }
+ // g2d.setColor(Color.magenta);
+ // if (layoutInfo.extraTextLayoutInfoList != null) {
+ // for (CommandButtonLayoutManager.TextLayoutInfo extraTextLayoutInfo :
+ // layoutInfo.extraTextLayoutInfoList) {
+ // if (extraTextLayoutInfo.text != null) {
+ // g2d.draw(extraTextLayoutInfo.textRect);
+ // }
+ // }
+ // }
+ // g2d.setColor(Color.green);
+ // g2d.draw(layoutInfo.popupActionRect);
+
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#paintPopupActionIcon
+ * (java.awt.Graphics, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintPopupActionIcon(Graphics g, Rectangle popupActionRect) {
+ int width = popupActionRect.width;
+ int height = popupActionRect.height;
+ if (((JCommandButton) this.commandButton).getPopupOrientationKind() == CommandButtonPopupOrientationKind.DOWNWARD) {
+ width += 2;
+ if (width % 2 == 0)
+ width++;
+ height = height / 2 - 1;
+ } else {
+ height /= 2;
+ width++;
+ if (width % 2 == 0)
+ width++;
+ }
+ popupActionIcon.setDimension(new Dimension(width, height));
+ popupActionIcon.paintIcon(this.commandButton, g, popupActionRect.x
+ + (popupActionRect.width - width) / 2, popupActionRect.y
+ + (popupActionRect.height - height) / 2);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#getPreferredSize(javax
+ * .swing.JComponent)
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ AbstractCommandButton button = (AbstractCommandButton) c;
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(button);
+
+ Dimension superPref = super.getPreferredSize(button);
+ if (superPref == null)
+ return null;
+
+ if (shaper == null)
+ return superPref;
+
+ // fix for issue 35 on Flamingo - do not enforce
+ // min size on buttons in the ribbon
+ // Additional fix - buttons with popup action should
+ // not have min size enforced as well
+ // Additional fix - buttons in popup menus and breadcrumb bars should
+ // not have min size enforced
+ if ((button.getDisplayState() == CommandButtonDisplayState.MEDIUM)
+ && (SwingUtilities.getAncestorOfClass(AbstractRibbonBand.class,
+ button) == null)
+ && (SwingUtilities.getAncestorOfClass(JBreadcrumbBar.class,
+ button) == null)
+ && (SwingUtilities.getAncestorOfClass(JCommandPopupMenu.class,
+ button) == null)) {
+ JButton dummy = new JButton(button.getText(), button.getIcon());
+ Dimension result = shaper.getPreferredSize(dummy, superPref);
+ if (FlamingoUtilities.hasPopupAction(button)) {
+ result.width = superPref.width;
+ }
+ return result;
+ }
+ return superPref;
+ }
+
+ /**
+ * Computes the alpha value for painting the separators.
+ *
+ * @return Alpha value for painting the separators.
+ */
+ private float getSeparatorAlpha() {
+ ComponentState actionAreaState = this.getActionTransitionTracker()
+ .getModelStateInfo().getCurrModelState();
+
+ if (!actionAreaState.isFacetActive(ComponentStateFacet.SELECTION)
+ && !actionAreaState.isDisabled()) {
+ float actionRolloverCycle = this.getActionTransitionTracker()
+ .getFacetStrength(ComponentStateFacet.ROLLOVER);
+ float popupRolloverCycle = this.getPopupTransitionTracker()
+ .getFacetStrength(ComponentStateFacet.ROLLOVER);
+ return Math.min(1.0f, actionRolloverCycle + popupRolloverCycle);
+ }
+ return 1.0f;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.ui.BasicCommandButtonUI#syncDisabledIcon()
+ */
+ @Override
+ protected void syncDisabledIcon() {
+ ResizableIcon currDisabledIcon = this.commandButton.getDisabledIcon();
+ ResizableIcon icon = this.commandButton.getIcon();
+ if ((currDisabledIcon == null)
+ || ((currDisabledIcon instanceof UIResource) && !currDisabledIcon
+ .getClass().isAnnotationPresent(TransitionAware.class))) {
+ if (icon != null) {
+ this.commandButton.setDisabledIcon(new ResizableIconUIResource(
+ new SubstanceDisabledResizableIcon(icon)));
+ } else {
+ this.commandButton.setDisabledIcon(null);
+ }
+ } else {
+ // disabled icon coming from app code
+ if (icon != null) {
+ this.commandButton.getDisabledIcon()
+ .setDimension(
+ new Dimension(icon.getIconWidth(), icon
+ .getIconHeight()));
+ }
+ }
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.overallStateTransitionTracker;
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ boolean inAction = (this.layoutInfo.actionClickArea != null)
+ && this.layoutInfo.actionClickArea.contains(me.getPoint());
+ boolean inPopup = (this.layoutInfo.popupClickArea != null)
+ && this.layoutInfo.popupClickArea.contains(me.getPoint());
+ return inAction || inPopup;
+ }
+
+ @Override
+ public StateTransitionTracker getActionTransitionTracker() {
+ return this.substanceVisualStateTracker
+ .getActionStateTransitionTracker();
+ }
+
+ @Override
+ public StateTransitionTracker getPopupTransitionTracker() {
+ return this.substanceVisualStateTracker
+ .getPopupStateTransitionTracker();
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandMenuButtonUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandMenuButtonUI.java
new file mode 100644
index 0000000..338fb72
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandMenuButtonUI.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.common.ui;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.JCommandMenuButton;
+import org.pushingpixels.flamingo.api.common.RolloverActionListener;
+import org.pushingpixels.flamingo.internal.utils.KeyTipRenderingUtilities;
+
+/**
+ * UI for {@link JCommandMenuButton} components in <b>Substance</b> look and
+ * feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceCommandMenuButtonUI extends SubstanceCommandButtonUI {
+ /**
+ * Rollover menu mouse listener.
+ */
+ protected MouseListener rolloverMenuMouseListener;
+
+ public static ComponentUI createUI(JComponent c) {
+ return new SubstanceCommandMenuButtonUI((JCommandMenuButton)c);
+ }
+
+ public SubstanceCommandMenuButtonUI(JCommandMenuButton button) {
+ super(button);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.ui.BasicCommandButtonUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.rolloverMenuMouseListener = new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (commandButton.isEnabled()) {
+ int modifiers = 0;
+ AWTEvent currentEvent = EventQueue.getCurrentEvent();
+ if (currentEvent instanceof InputEvent) {
+ modifiers = ((InputEvent) currentEvent).getModifiers();
+ } else if (currentEvent instanceof ActionEvent) {
+ modifiers = ((ActionEvent) currentEvent).getModifiers();
+ }
+ fireRolloverActionPerformed(new ActionEvent(this,
+ ActionEvent.ACTION_PERFORMED, commandButton
+ .getActionModel().getActionCommand(),
+ EventQueue.getMostRecentEventTime(), modifiers));
+
+ processPopupAction();
+ }
+ }
+ };
+ this.commandButton.addMouseListener(this.rolloverMenuMouseListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.commandButton.removeMouseListener(this.rolloverMenuMouseListener);
+ this.rolloverMenuMouseListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /**
+ * Fires the rollover action on all registered handlers.
+ *
+ * @param e
+ * Event object.
+ */
+ protected void fireRolloverActionPerformed(ActionEvent e) {
+ // Guaranteed to return a non-null array
+ RolloverActionListener[] listeners = commandButton
+ .getListeners(RolloverActionListener.class);
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ for (int i = listeners.length - 1; i >= 0; i--) {
+ (listeners[i]).actionPerformed(e);
+ }
+ }
+
+ @Override
+ public void update(Graphics g, JComponent c) {
+ super.update(g, c);
+
+ JCommandMenuButton menuButton = (JCommandMenuButton) c;
+ KeyTipRenderingUtilities.renderMenuButtonKeyTips(g, menuButton,
+ layoutManager);
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandPopupMenuUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandPopupMenuUI.java
new file mode 100644
index 0000000..07b3d85
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandPopupMenuUI.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.common.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.popup.JCommandPopupMenu;
+import org.pushingpixels.flamingo.internal.ui.common.popup.BasicCommandPopupMenuUI;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceConstants.MenuGutterFillKind;
+import org.pushingpixels.substance.internal.painter.SeparatorPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.border.SubstanceBorder;
+
+/**
+ * UI for {@link JCommandPopupMenu} components in <b>Substance</b> look and
+ * feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceCommandPopupMenuUI extends BasicCommandPopupMenuUI {
+ public static ComponentUI createUI(JComponent c) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(c);
+ return new SubstanceCommandPopupMenuUI();
+ }
+
+ @Override
+ protected JPanel createMenuPanel() {
+ return new SubstanceMenuPanel();
+ }
+
+ protected static class SubstanceMenuPanel extends MenuPanel {
+ @Override
+ protected void paintIconGutterSeparator(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ int sepX = this.getSeparatorX();
+ g2d.translate(sepX, 0);
+ SeparatorPainterUtils.paintSeparator(this, g2d, 2,
+ this.getHeight(), JSeparator.VERTICAL, true, 0, 0, false);
+ g2d.dispose();
+ }
+
+ @Override
+ protected void paintIconGutterBackground(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ MenuGutterFillKind fillKind = SubstanceCoreUtilities
+ .getMenuGutterFillKind();
+ if (fillKind != MenuGutterFillKind.NONE) {
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this, ComponentState.ENABLED);
+ Color leftColor = ((fillKind == MenuGutterFillKind.SOFT_FILL) || (fillKind == MenuGutterFillKind.HARD)) ? scheme
+ .getUltraLightColor()
+ : scheme.getLightColor();
+ Color rightColor = ((fillKind == MenuGutterFillKind.SOFT_FILL) || (fillKind == MenuGutterFillKind.SOFT)) ? scheme
+ .getUltraLightColor()
+ : scheme.getLightColor();
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this,
+ 0.7f, g));
+
+ int sepX = this.getSeparatorX();
+ if (this.getComponentOrientation().isLeftToRight()) {
+ GradientPaint gp = new GradientPaint(0, 0, leftColor,
+ sepX + 2, 0, rightColor);
+ g2d.setPaint(gp);
+ g2d.fillRect(0, 0, sepX, this.getHeight());
+ } else {
+ GradientPaint gp = new GradientPaint(sepX, 0, leftColor,
+ this.getWidth(), 0, rightColor);
+ g2d.setPaint(gp);
+ g2d.fillRect(sepX + 2, 0, this.getWidth() - sepX, this
+ .getHeight());
+ }
+ }
+ g2d.dispose();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.ui.BasicCommandPopupMenuUI#
+ * createScrollableButtonPanel()
+ */
+ @Override
+ protected ScrollableCommandButtonPanel createScrollableButtonPanel() {
+ ScrollableCommandButtonPanel result = super
+ .createScrollableButtonPanel();
+ result.setBorder(new SubstanceBorder(new Insets(0, 0, 1, 0)));
+ return result;
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandToggleButtonUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandToggleButtonUI.java
new file mode 100644
index 0000000..f4e79c7
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandToggleButtonUI.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.common.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.flamingo.api.common.*;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
+import org.pushingpixels.flamingo.internal.ui.common.BasicCommandToggleButtonUI;
+import org.pushingpixels.flamingo.internal.ui.common.ResizableIconUIResource;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.effects.GhostPaintingUtils;
+import org.pushingpixels.lafwidget.animation.effects.GhostingListener;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.flamingo.utils.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.ModelStateInfo;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAware;
+
+/**
+ * UI for command buttons {@link JCommandToggleButton} in <b>Substance </b> look
+ * and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceCommandToggleButtonUI extends BasicCommandToggleButtonUI
+ implements ActionPopupTransitionAwareUI {
+ /**
+ * Delegate for painting the background.
+ */
+ protected ButtonBackgroundDelegate backgroundDelegate;
+
+ /**
+ * Property change listener. Listens on changes to
+ * {@link AbstractButton#MODEL_CHANGED_PROPERTY} property.
+ */
+ protected PropertyChangeListener substancePropertyListener;
+
+ /**
+ * Model change listener for ghost image effects.
+ */
+ private GhostingListener substanceModelChangeListener;
+
+ /**
+ * Tracker for visual state transitions.
+ */
+ protected CommandButtonVisualStateTracker substanceVisualStateTracker;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceCommandToggleButtonUI();
+ }
+
+ /**
+ * Creates a new UI delegate for ribbon button.
+ */
+ public SubstanceCommandToggleButtonUI() {
+ super();
+ this.backgroundDelegate = new ButtonBackgroundDelegate();
+
+ this.substanceVisualStateTracker = new CommandButtonVisualStateTracker();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasiccommandButtonUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ this.commandButton.putClientProperty(
+ SubstanceLookAndFeel.BUTTON_SHAPER_PROPERTY,
+ ClassicButtonShaper.INSTANCE);
+
+ this.commandButton.setOpaque(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.ui.BasicCommandButtonUI#updateBorder()
+ */
+ @Override
+ protected void updateBorder() {
+ Border currBorder = this.commandButton.getBorder();
+ if ((currBorder == null) || (currBorder instanceof UIResource)) {
+ Insets extra = SubstanceSizeUtils
+ .getDefaultBorderInsets(SubstanceSizeUtils
+ .getComponentFontSize(this.commandButton));
+ double hgapScaleFactor = this.commandButton.getHGapScaleFactor();
+ double vgapScaleFactor = this.commandButton.getVGapScaleFactor();
+
+ int top = 1 + (int) (vgapScaleFactor * extra.top);
+ int left = 2 + (int) (hgapScaleFactor * (1 + extra.left));
+ int bottom = 0 + (int) (vgapScaleFactor * extra.bottom);
+ int right = 2 + (int) (hgapScaleFactor * (1 + extra.right));
+ this.commandButton
+ .setBorder(new BorderUIResource.EmptyBorderUIResource(top,
+ left, bottom, right));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasiccommandButtonUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substanceVisualStateTracker.installListeners(this.commandButton);
+
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (AbstractButton.MODEL_CHANGED_PROPERTY.equals(evt
+ .getPropertyName())) {
+ if (substanceModelChangeListener != null)
+ substanceModelChangeListener.unregisterListeners();
+ substanceModelChangeListener = new GhostingListener(
+ commandButton, commandButton.getActionModel());
+ substanceModelChangeListener.registerListeners();
+ }
+ }
+ };
+ this.commandButton
+ .addPropertyChangeListener(this.substancePropertyListener);
+
+ this.substanceModelChangeListener = new GhostingListener(
+ this.commandButton, this.commandButton.getActionModel());
+ this.substanceModelChangeListener.registerListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasiccommandButtonUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.substanceVisualStateTracker.uninstallListeners(this.commandButton);
+ this.substanceVisualStateTracker = null;
+
+ this.commandButton
+ .removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+
+ this.substanceModelChangeListener.unregisterListeners();
+ this.substanceModelChangeListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#paintButtonIcon(java
+ * .awt.Graphics, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintButtonIcon(Graphics g, Rectangle iconRect) {
+ JCommandToggleButton jctb = (JCommandToggleButton) this.commandButton;
+ Icon regular = jctb.getIcon();
+ if (toUseDisabledIcon()
+ && (jctb.getDisabledIcon() != null)
+ && ((regular != null) && !regular.getClass()
+ .isAnnotationPresent(TransitionAware.class))) {
+ regular = jctb.getDisabledIcon();
+ }
+
+ boolean useThemed = SubstanceCoreUtilities
+ .useThemedDefaultIcon(this.commandButton);
+
+ if ((iconRect == null) || (regular == null) || (iconRect.width == 0)
+ || (iconRect.height == 0)) {
+ return;
+ }
+
+ if (regular != null) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ GhostPaintingUtils.paintGhostIcon(g2d, jctb, regular, iconRect);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(jctb, g));
+
+ if (!useThemed) {
+ regular.paintIcon(jctb, g2d, iconRect.x, iconRect.y);
+ } else {
+ CommandButtonBackgroundDelegate.paintThemedCommandButtonIcon(
+ g2d, iconRect, jctb, regular, jctb.getActionModel(),
+ this.substanceVisualStateTracker
+ .getPopupStateTransitionTracker());
+ }
+ g2d.dispose();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#isPaintingBackground()
+ */
+ @Override
+ protected boolean isPaintingBackground() {
+ if (super.isPaintingBackground())
+ return true;
+ return (this.getActionTransitionTracker().getFacetStrength(
+ ComponentStateFacet.ROLLOVER) > 0.0f);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#isPaintingSeparators()
+ */
+ @Override
+ protected boolean isPaintingSeparators() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#paint(java.awt.Graphics
+ * , javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setFont(FlamingoUtilities.getFont(this.commandButton,
+ "Ribbon.font", "Button.font", "Panel.font"));
+
+ this.layoutInfo = this.layoutManager.getLayoutInfo(this.commandButton,
+ g);
+ commandButton.putClientProperty("icon.bounds", layoutInfo.iconRect);
+ commandButton.putClientProperty("icon", commandButton.getIcon());
+
+ if (this.isPaintingBackground()) {
+ this.paintButtonBackground(g2d, new Rectangle(0, 0, c.getWidth(), c
+ .getHeight()));
+ }
+
+ // action model should be used to
+ // compute the foreground color of the command button's text
+ ModelStateInfo modelStateInfo = this.substanceVisualStateTracker
+ .getActionStateTransitionTracker().getModelStateInfo();
+ Color fgColor = this.getForegroundColor(modelStateInfo);
+
+ if (layoutInfo.textLayoutInfoList != null) {
+ for (CommandButtonLayoutManager.TextLayoutInfo mainTextLayoutInfo : layoutInfo.textLayoutInfoList) {
+ if (mainTextLayoutInfo.text != null) {
+ SubstanceTextUtilities.paintText(g2d, c,
+ mainTextLayoutInfo.textRect,
+ mainTextLayoutInfo.text, -1, g2d.getFont(),
+ fgColor, g2d.getClipBounds());
+ }
+ }
+ }
+
+ if (layoutInfo.extraTextLayoutInfoList != null) {
+ Color disabledFgColor = SubstanceColorSchemeUtilities
+ .getColorScheme(this.commandButton,
+ ComponentState.DISABLED_UNSELECTED)
+ .getForegroundColor();
+ float buttonAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ this.commandButton, ComponentState.DISABLED_UNSELECTED);
+ if (buttonAlpha < 1.0f) {
+ Color bgFillColor = SubstanceColorUtilities
+ .getBackgroundFillColor(this.commandButton);
+ disabledFgColor = SubstanceColorUtilities.getInterpolatedColor(
+ disabledFgColor, bgFillColor, buttonAlpha);
+ }
+ if (modelStateInfo.getCurrModelState().isDisabled()) {
+ disabledFgColor = SubstanceColorUtilities.getInterpolatedColor(
+ disabledFgColor, SubstanceColorUtilities
+ .getBackgroundFillColor(c), 0.5);
+ }
+ for (CommandButtonLayoutManager.TextLayoutInfo extraTextLayoutInfo : layoutInfo.extraTextLayoutInfoList) {
+ if (extraTextLayoutInfo.text != null) {
+ SubstanceTextUtilities.paintText(g2d, c,
+ extraTextLayoutInfo.textRect,
+ extraTextLayoutInfo.text, -1, g2d.getFont(),
+ disabledFgColor, g2d.getClipBounds());
+ }
+ }
+ }
+
+ if (layoutInfo.iconRect != null) {
+ this.paintButtonIcon(g2d, layoutInfo.iconRect);
+ }
+
+ g2d.dispose();
+ }
+
+ protected Color getForegroundColor(ModelStateInfo modelStateInfo) {
+ Color fgColor = this.commandButton.getForeground();
+ if (fgColor instanceof UIResource) {
+ float buttonAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ this.commandButton, modelStateInfo.getCurrModelState());
+ fgColor = SubstanceTextUtilities.getForegroundColor(
+ this.commandButton, this.commandButton.getText(),
+ modelStateInfo, buttonAlpha);
+ }
+ return fgColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#getPreferredSize(javax
+ * .swing.JComponent)
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ AbstractCommandButton button = (AbstractCommandButton) c;
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(button);
+
+ Dimension superPref = super.getPreferredSize(button);
+ if (superPref == null)
+ return null;
+
+ if (shaper == null)
+ return superPref;
+
+ // fix for issue 35 on Flamingo - do not enforce
+ // min size on buttons in the ribbon
+ if ((button.getDisplayState() == CommandButtonDisplayState.MEDIUM)
+ && (SwingUtilities.getAncestorOfClass(AbstractRibbonBand.class,
+ button) == null)) {
+ JButton dummy = new JButton(button.getText(), button.getIcon());
+ return shaper.getPreferredSize(dummy, superPref);
+ }
+ return superPref;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#paintButtonBackground
+ * (java.awt.Graphics, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintButtonBackground(Graphics graphics, Rectangle toFill) {
+ if (SubstanceCoreUtilities.isButtonNeverPainted(this.commandButton))
+ return;
+
+ ButtonModel actionModel = this.commandButton.getActionModel();
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(this.commandButton);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(this.commandButton);
+
+ boolean ignoreSelections = this.commandButton instanceof JCommandToggleMenuButton;
+ BufferedImage fullAlphaBackground = CommandButtonBackgroundDelegate
+ .getFullAlphaBackground(this.commandButton, actionModel,
+ fillPainter, borderPainter, this.commandButton
+ .getWidth(), this.commandButton.getHeight(),
+ this.getActionTransitionTracker(), ignoreSelections);
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = getActionTransitionTracker()
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ignoreSelections ? modelStateInfo
+ .getStateNoSelectionContributionMap()
+ : modelStateInfo.getStateContributionMap();
+
+ // Two special cases here:
+ // 1. Button has flat appearance.
+ // 2. Button is disabled.
+ // For both cases, we need to set custom translucency.
+ boolean isFlat = this.commandButton.isFlat();
+ boolean isSpecial = isFlat || !this.commandButton.isEnabled();
+ float extraAlpha = 1.0f;
+ if (isSpecial) {
+ if (isFlat) {
+ extraAlpha = 0.0f;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState.isDisabled())
+ continue;
+ if (activeState == ComponentState.ENABLED)
+ continue;
+ extraAlpha += activeEntry.getValue().getContribution();
+ }
+ } else {
+ ComponentState actionAreaState = ComponentState.getState(
+ actionModel, this.commandButton);
+ if (actionAreaState.isDisabled()) {
+ extraAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ this.commandButton, actionAreaState);
+ }
+ }
+ }
+ // System.out.println(extraAlpha);
+ extraAlpha = Math.min(1.0f, extraAlpha);
+ if (extraAlpha > 0.0f) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.commandButton, extraAlpha, graphics));
+ g2d.drawImage(fullAlphaBackground, 0, 0, null);
+ g2d.dispose();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.ui.BasicCommandButtonUI#syncDisabledIcon()
+ */
+ @Override
+ protected void syncDisabledIcon() {
+ ResizableIcon currDisabledIcon = this.commandButton.getDisabledIcon();
+ ResizableIcon icon = this.commandButton.getIcon();
+ if ((currDisabledIcon == null)
+ || ((currDisabledIcon instanceof UIResource) && !currDisabledIcon
+ .getClass().isAnnotationPresent(TransitionAware.class))) {
+ if (icon != null) {
+ this.commandButton.setDisabledIcon(new ResizableIconUIResource(
+ new SubstanceDisabledResizableIcon(icon)));
+ } else {
+ this.commandButton.setDisabledIcon(null);
+ }
+ } else {
+ // disabled icon coming from app code
+ if (icon != null) {
+ this.commandButton.getDisabledIcon()
+ .setDimension(
+ new Dimension(icon.getIconWidth(), icon
+ .getIconHeight()));
+ }
+ }
+ }
+
+ @Override
+ public StateTransitionTracker getActionTransitionTracker() {
+ return this.substanceVisualStateTracker
+ .getActionStateTransitionTracker();
+ }
+
+ @Override
+ public StateTransitionTracker getPopupTransitionTracker() {
+ return this.substanceVisualStateTracker
+ .getPopupStateTransitionTracker();
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.substanceVisualStateTracker
+ .getActionStateTransitionTracker();
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return this.getLayoutInfo().actionClickArea.contains(me.getPoint());
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandToggleMenuButtonUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandToggleMenuButtonUI.java
new file mode 100644
index 0000000..7c0d99d
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceCommandToggleMenuButtonUI.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.common.ui;
+
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+import java.util.Map;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.common.JCommandToggleMenuButton;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.ModelStateInfo;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * UI for {@link JCommandToggleMenuButton} components in <b>Substance</b> look
+ * and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceCommandToggleMenuButtonUI extends
+ SubstanceCommandToggleButtonUI {
+ public static ComponentUI createUI(JComponent c) {
+ return new SubstanceCommandToggleMenuButtonUI();
+ }
+
+ @Override
+ protected void paintButtonIcon(Graphics g, Rectangle iconRect) {
+ boolean isSelected = this.commandButton.getActionModel().isSelected();
+ if (isSelected) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ Rectangle extended = new Rectangle(iconRect.x - 1, iconRect.y - 1,
+ iconRect.width + 1, iconRect.height + 1);
+
+ ComponentState currState = this.commandButton.getActionModel()
+ .isEnabled() ? ComponentState.SELECTED
+ : ComponentState.DISABLED_SELECTED;
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.commandButton,
+ ColorSchemeAssociationKind.HIGHLIGHT, currState);
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(this.commandButton);
+ fillPainter.paintContourBackground(g2d, this.commandButton,
+ extended.width, extended.height, extended, false,
+ fillScheme, false);
+
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.commandButton,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ currState);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(this.commandButton);
+ borderPainter.paintBorder(g2d, this.commandButton, extended.width,
+ extended.height, extended, null, borderScheme);
+
+ g2d.dispose();
+ }
+ super.paintButtonIcon(g, iconRect);
+ // does it actually have an icon?
+ Icon iconToPaint = this.getIconToPaint();
+ if (isSelected && (iconToPaint == null)) {
+ // draw a checkmark
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ ComponentState currState = this.commandButton.getActionModel()
+ .isEnabled() ? ComponentState.SELECTED
+ : ComponentState.DISABLED_SELECTED;
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.commandButton,
+ ColorSchemeAssociationKind.HIGHLIGHT, currState);
+ g2d.setColor(fillScheme.getForegroundColor());
+
+ int iw = iconRect.width;
+ int ih = iconRect.height;
+ GeneralPath path = new GeneralPath();
+
+ path.moveTo(0.2f * iw, 0.5f * ih);
+ path.lineTo(0.42f * iw, 0.8f * ih);
+ path.lineTo(0.8f * iw, 0.2f * ih);
+ g2d.translate(iconRect.x, iconRect.y);
+ Stroke stroke = new BasicStroke((float) 0.12 * iw,
+ BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+ g2d.setStroke(stroke);
+ g2d.draw(path);
+
+ g2d.dispose();
+ }
+ }
+
+ @Override
+ protected boolean isPaintingBackground() {
+ boolean isActionRollover = this.commandButton.getActionModel()
+ .isRollover();
+
+ if (isActionRollover || !this.commandButton.isFlat()) {
+ return true;
+ }
+
+ return (this.getActionTransitionTracker().getFacetStrength(
+ ComponentStateFacet.ROLLOVER) > 0.0f);
+ }
+
+ @Override
+ protected Color getForegroundColor(ModelStateInfo modelStateInfo) {
+ Color fgColor = this.commandButton.getForeground();
+ if (fgColor instanceof UIResource) {
+ float buttonAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ this.commandButton, modelStateInfo.getCurrModelState());
+
+ fgColor = getMenuButtonForegroundColor(this.commandButton,
+ modelStateInfo);
+
+ if (buttonAlpha < 1.0f) {
+ Color bgFillColor = SubstanceColorUtilities
+ .getBackgroundFillColor(this.commandButton);
+ fgColor = SubstanceColorUtilities.getInterpolatedColor(fgColor,
+ bgFillColor, buttonAlpha);
+ }
+ }
+ return fgColor;
+ }
+
+ private static Color getMenuButtonForegroundColor(
+ AbstractCommandButton menuButton,
+ StateTransitionTracker.ModelStateInfo modelStateInfo) {
+ ComponentState currState = modelStateInfo
+ .getCurrModelStateNoSelection();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateNoSelectionContributionMap();
+
+ ColorSchemeAssociationKind currAssocKind = ColorSchemeAssociationKind.FILL;
+ // use HIGHLIGHT on active and non-rollover menu items
+ if (!currState.isDisabled() && (currState != ComponentState.ENABLED)
+ && !currState.isFacetActive(ComponentStateFacet.ROLLOVER))
+ currAssocKind = ColorSchemeAssociationKind.HIGHLIGHT;
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(menuButton, currAssocKind, currState);
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ return colorScheme.getForegroundColor();
+ }
+
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ float alpha = activeEntry.getValue().getContribution();
+ ColorSchemeAssociationKind assocKind = ColorSchemeAssociationKind.FILL;
+ // use HIGHLIGHT on active and non-rollover menu items
+ if (!activeState.isDisabled()
+ && (activeState != ComponentState.ENABLED)
+ && !activeState.isFacetActive(ComponentStateFacet.ROLLOVER))
+ assocKind = ColorSchemeAssociationKind.HIGHLIGHT;
+ SubstanceColorScheme activeColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(menuButton, assocKind, activeState);
+ Color activeForeground = activeColorScheme.getForegroundColor();
+ aggrRed += alpha * activeForeground.getRed();
+ aggrGreen += alpha * activeForeground.getGreen();
+ aggrBlue += alpha * activeForeground.getBlue();
+ }
+ return new Color((int) aggrRed, (int) aggrGreen, (int) aggrBlue);
+ }
+
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstancePopupPanelUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstancePopupPanelUI.java
new file mode 100644
index 0000000..59cc8c3
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstancePopupPanelUI.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.common.ui;
+
+import java.awt.Graphics;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
+import org.pushingpixels.flamingo.internal.ui.common.popup.BasicPopupPanelUI;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI for {@link JPopupPanel} components in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstancePopupPanelUI extends BasicPopupPanelUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstancePopupPanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ if (!c.isShowing()) {
+ return;
+ }
+ synchronized (c) {
+ if (c.isOpaque()) {
+ BackgroundPaintingUtils.update(g, c, false);
+ super.paint(g, c);
+ } else {
+ super.paint(g, c);
+ }
+ }
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceScrollablePanelUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceScrollablePanelUI.java
new file mode 100644
index 0000000..706b7fd
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/common/ui/SubstanceScrollablePanelUI.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.common.ui;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.util.EnumSet;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.flamingo.api.common.JScrollablePanel;
+import org.pushingpixels.flamingo.api.common.JScrollablePanel.ScrollType;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.ui.common.BasicScrollablePanelUI;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.flamingo.common.TransitionAwareResizableIcon;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * UI for {@link JScrollablePanel} components in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceScrollablePanelUI extends BasicScrollablePanelUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceScrollablePanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ if (!c.isShowing()) {
+ return;
+ }
+ synchronized (c) {
+ if (c.isOpaque()) {
+ BackgroundPaintingUtils.update(g, c, false);
+ super.paint(g, c);
+ } else {
+ super.paint(g, c);
+ }
+ }
+ }
+
+ @Override
+ public JCommandButton createLeadingScroller() {
+ final JCommandButton result = super.createLeadingScroller();
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(result);
+ int arrowIconHeight = (int) SubstanceSizeUtils
+ .getSmallArrowIconHeight(fontSize) + 3;
+ int arrowIconWidth = (int) SubstanceSizeUtils
+ .getSmallArrowIconWidth(fontSize);
+ if (arrowIconHeight % 2 == 0)
+ arrowIconHeight++;
+ ResizableIcon arrowIcon = new TransitionAwareResizableIcon(
+ result,
+ new TransitionAwareResizableIcon.StateTransitionTrackerDelegate() {
+ @Override
+ public StateTransitionTracker getStateTransitionTracker() {
+ return ((ActionPopupTransitionAwareUI) result.getUI())
+ .getActionTransitionTracker();
+ }
+ }, new TransitionAwareResizableIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme,
+ int width, int height) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(result);
+ float arrowStrokeWidth = SubstanceSizeUtils
+ .getDoubleArrowStrokeWidth(fontSize) - 0.3f;
+ if (scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY) {
+ width--;
+ }
+ Icon doubleArrowIcon = SubstanceImageCreator
+ .getDoubleArrowIcon(
+ SubstanceSizeUtils
+ .getComponentFontSize(result),
+ width,
+ height,
+ arrowStrokeWidth,
+ (scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY) ? SwingUtilities.WEST
+ : SwingUtilities.NORTH, scheme);
+ return doubleArrowIcon;
+ }
+ }, new Dimension(arrowIconHeight, arrowIconWidth));
+ result.setIcon(arrowIcon);
+ result
+ .putClientProperty(
+ SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY,
+ (scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY) ? EnumSet
+ .of(SubstanceConstants.Side.RIGHT)
+ : EnumSet.of(SubstanceConstants.Side.BOTTOM));
+ result.setHorizontalAlignment(SwingConstants.CENTER);
+ result.setFlat(true);
+ return result;
+ }
+
+ @Override
+ public JCommandButton createTrailingScroller() {
+ final JCommandButton result = super.createTrailingScroller();
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(result);
+ int arrowIconHeight = (int) SubstanceSizeUtils
+ .getSmallArrowIconHeight(fontSize) + 3;
+ int arrowIconWidth = (int) SubstanceSizeUtils
+ .getSmallArrowIconWidth(fontSize);
+ if (arrowIconHeight % 2 == 0)
+ arrowIconHeight++;
+ ResizableIcon arrowIcon = new TransitionAwareResizableIcon(
+ result,
+ new TransitionAwareResizableIcon.StateTransitionTrackerDelegate() {
+ @Override
+ public StateTransitionTracker getStateTransitionTracker() {
+ return ((ActionPopupTransitionAwareUI) result.getUI())
+ .getActionTransitionTracker();
+ }
+ }, new TransitionAwareResizableIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme,
+ int width, int height) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(result);
+ float arrowStrokeWidth = SubstanceSizeUtils
+ .getDoubleArrowStrokeWidth(fontSize) - 0.3f;
+ if (scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY) {
+ width--;
+ }
+ Icon doubleArrowIcon = SubstanceImageCreator
+ .getDoubleArrowIcon(
+ SubstanceSizeUtils
+ .getComponentFontSize(result),
+ width,
+ height,
+ arrowStrokeWidth,
+ (scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY) ? SwingUtilities.EAST
+ : SwingUtilities.SOUTH, scheme);
+ return doubleArrowIcon;
+ }
+ }, new Dimension(arrowIconHeight, arrowIconWidth));
+ result.setIcon(arrowIcon);
+ result
+ .putClientProperty(
+ SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY,
+ (scrollablePanel.getScrollType() == ScrollType.HORIZONTALLY) ? EnumSet
+ .of(SubstanceConstants.Side.LEFT)
+ : EnumSet.of(SubstanceConstants.Side.TOP));
+ result.setHorizontalAlignment(SwingConstants.CENTER);
+ result.setFlat(true);
+ return result;
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/RibbonBackgroundDelegate.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/RibbonBackgroundDelegate.java
new file mode 100644
index 0000000..523c1c6
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/RibbonBackgroundDelegate.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon;
+
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+import java.awt.image.BufferedImage;
+import java.util.*;
+
+import javax.swing.AbstractButton;
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.flamingo.api.ribbon.*;
+import org.pushingpixels.flamingo.internal.ui.ribbon.JRibbonTaskToggleButton;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.SubstanceConstants.Side;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.flamingo.ribbon.ui.RibbonBorderShaper;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.colorscheme.ShiftColorScheme;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Delegate class for painting backgrounds of buttons in Ribbon plugin.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RibbonBackgroundDelegate {
+ /**
+ * Cache for background images of ribbon buttons. Each time
+ * {@link #getTaskToggleButtonBackground(org.pushingpixels.flamingo.internal.ui.ribbon.JRibbonTaskToggleButton, int, int)} is
+ * called with <code>isRoundCorners</code> equal to <code>true</code>, it
+ * checks <code>this</code> map to see if it already contains such
+ * background. If so, the background from the map is returned.
+ */
+ private static LazyResettableHashMap<BufferedImage> imageCache = new LazyResettableHashMap<BufferedImage>(
+ "Substance.Flamingo.RibbonBackgroundDelegate");
+
+ /**
+ * Retrieves background image for the specified ribbon button.
+ *
+ * @param button
+ * Button.
+ * @param width
+ * Button width.
+ * @param height
+ * Button height.
+ * @return Button background image.
+ */
+ private static synchronized BufferedImage getTaskToggleButtonBackground(
+ JRibbonTaskToggleButton button, int width, int height) {
+ JRibbon ribbon = (JRibbon) SwingUtilities.getAncestorOfClass(
+ JRibbon.class, button);
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) button
+ .getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+ ComponentState currState = ComponentState.getState(button
+ .getActionModel(), button);
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(button);
+
+ SubstanceColorScheme baseFillScheme = skin.getColorScheme(button,
+ ColorSchemeAssociationKind.TAB, currState);
+ SubstanceColorScheme baseBorderScheme = skin.getColorScheme(ribbon,
+ ColorSchemeAssociationKind.TAB_BORDER, currState);
+
+ SubstanceFillPainter fillPainter = skin.getFillPainter();
+ SubstanceBorderPainter borderPainter = skin.getBorderPainter();
+
+ JRibbon parent = (JRibbon) SwingUtilities.getAncestorOfClass(
+ JRibbon.class, button);
+ RibbonTask selectedTask = parent.getSelectedTask();
+ AbstractRibbonBand band = (selectedTask.getBandCount() == 0) ? null
+ : selectedTask.getBand(0);
+ Color bgColor = (band != null) ? band.getBackground() : parent
+ .getBackground();
+
+ HashMapKey baseKey = SubstanceCoreUtilities.getHashKey(width, height,
+ baseFillScheme.getDisplayName(), baseBorderScheme
+ .getDisplayName(), fillPainter.getDisplayName(),
+ borderPainter.getDisplayName(), button.getParent()
+ .getBackground().getRGB(), button.getActionModel()
+ .isSelected(), button.getContextualGroupHueColor(),
+ button.getActionModel().isSelected(), ribbon.isMinimized(),
+ skin.getSelectedTabFadeStart(), skin.getSelectedTabFadeEnd(),
+ bgColor);
+ BufferedImage baseLayer = imageCache.get(baseKey);
+ if (baseLayer == null) {
+ baseLayer = getSingleLayer(button, width, height, ribbon,
+ baseFillScheme, baseBorderScheme, fillPainter,
+ borderPainter);
+
+ imageCache.put(baseKey, baseLayer);
+ }
+
+ if (currState.isDisabled() || (activeStates.size() == 1)) {
+ return baseLayer;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D g2d = result.createGraphics();
+
+ g2d.drawImage(baseLayer, 0, 0, null);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, ColorSchemeAssociationKind.TAB,
+ activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(ribbon,
+ ColorSchemeAssociationKind.TAB_BORDER, activeState);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
+ fillScheme.getDisplayName(), borderScheme.getDisplayName(),
+ fillPainter.getDisplayName(), borderPainter
+ .getDisplayName(), button.getParent()
+ .getBackground().getRGB(), button.getActionModel()
+ .isSelected(), button.getContextualGroupHueColor(),
+ button.getActionModel().isSelected(), ribbon.isMinimized(),
+ skin.getSelectedTabFadeStart(), skin
+ .getSelectedTabFadeEnd(), bgColor);
+
+ BufferedImage layer = imageCache.get(key);
+ if (layer == null) {
+ layer = getSingleLayer(button, width, height, ribbon,
+ fillScheme, borderScheme, fillPainter, borderPainter);
+
+ imageCache.put(key, layer);
+ }
+
+ g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
+ g2d.drawImage(layer, 0, 0, null);
+ }
+
+ g2d.dispose();
+ return result;
+ }
+
+ private static BufferedImage getSingleLayer(JRibbonTaskToggleButton button,
+ int width, int height, JRibbon ribbon,
+ SubstanceColorScheme fillScheme, SubstanceColorScheme borderScheme,
+ SubstanceFillPainter fillPainter,
+ SubstanceBorderPainter borderPainter) {
+ Set<Side> bottom = new HashSet<Side>();
+ bottom.add(Side.BOTTOM);
+
+ Color contextualGroupHueColor = button.getContextualGroupHueColor();
+ if (contextualGroupHueColor != null) {
+ fillScheme = ShiftColorScheme.getShiftedScheme(fillScheme,
+ contextualGroupHueColor,
+ RibbonContextualTaskGroup.HUE_ALPHA, null, 0.0f);
+ }
+
+ float radius = RibbonBorderShaper.getRibbonToggleButtonRadius(button);
+ int borderDelta = (int) Math.ceil(2.0 * SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(button)));
+ int borderInsets = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(button)) / 2.0);
+ GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(width,
+ height + 2 + borderDelta, radius, bottom, borderInsets);
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height + 2);
+ Graphics2D graphics = result.createGraphics();
+ fillPainter.paintContourBackground(graphics, button, width, height + 2
+ + borderDelta, contour, false, fillScheme, true);
+
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(button));
+ GeneralPath contourInner = SubstanceOutlineUtilities.getBaseOutline(
+ width, height + 2 + borderDelta, radius, bottom,
+ borderThickness + borderInsets);
+
+ borderPainter.paintBorder(graphics, button, width, height + 2, contour,
+ contourInner, borderScheme);
+ graphics.dispose();
+
+ if (button.getActionModel().isSelected()
+ && (button.getContextualGroupHueColor() == null)) {
+ int fw = result.getWidth();
+ int fh = result.getHeight();
+ BufferedImage fade = SubstanceCoreUtilities.getBlankImage(fw, fh);
+ Graphics2D fadeGraphics = fade.createGraphics();
+ JRibbon parent = (JRibbon) SwingUtilities.getAncestorOfClass(
+ JRibbon.class, button);
+ RibbonTask selectedTask = parent.getSelectedTask();
+ AbstractRibbonBand band = (selectedTask.getBandCount() == 0) ? null
+ : selectedTask.getBand(0);
+ if (band != null) {
+ fadeGraphics.setColor(band.getBackground());
+ } else {
+ fadeGraphics.setColor(parent.getBackground());
+ }
+ fadeGraphics.fillRect(0, 0, fw, fh);
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(button);
+ if (skin.getWatermark() != null)
+ skin.getWatermark().drawWatermarkImage(fadeGraphics, button, 0,
+ 0, fw, fh);
+
+ borderPainter.paintBorder(fadeGraphics, button, width, height + 2,
+ contour, contourInner, borderScheme);
+
+ result = SubstanceCoreUtilities.blendImagesVertical(result, fade,
+ skin.getSelectedTabFadeStart(), skin
+ .getSelectedTabFadeEnd());
+ }
+ return result;
+ }
+
+ /**
+ * Updates background of the specified button.
+ *
+ * @param g
+ * Graphic context.
+ * @param button
+ * Button to update.
+ */
+ public void updateTaskToggleButtonBackground(Graphics g,
+ JRibbonTaskToggleButton button) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ int width = button.getWidth();
+ int height = button.getHeight();
+
+ BufferedImage ribbonBackground = getTaskToggleButtonBackground(button,
+ width, height);
+
+ TransitionAwareUI ui = (TransitionAwareUI) button.getUI();
+ StateTransitionTracker stateTransitionTracker = ui
+ .getTransitionTracker();
+
+ float extraActionAlpha = 0.0f;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : stateTransitionTracker
+ .getModelStateInfo().getStateContributionMap().entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState.isDisabled())
+ continue;
+ if (activeState == ComponentState.ENABLED)
+ continue;
+ extraActionAlpha += activeEntry.getValue().getContribution();
+ }
+
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(button,
+ extraActionAlpha, g));
+ g2d.drawImage(ribbonBackground, 0, 0, null);
+
+ g2d.dispose();
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/ColorSchemeResizableIcon.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/ColorSchemeResizableIcon.java
new file mode 100644
index 0000000..c88438a
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/ColorSchemeResizableIcon.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.gallery.oob;
+
+import java.awt.*;
+import java.awt.geom.Ellipse2D;
+
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+
+/**
+ * Resizable icon for <b>Substance</b> themes.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ColorSchemeResizableIcon implements ResizableIcon {
+ /**
+ * Current width in pixels.
+ */
+ private int currWidth;
+
+ /**
+ * Current height in pixels.
+ */
+ private int currHeight;
+
+ /**
+ * Associated color scheme. Can be <code>null</code>.
+ */
+ private SubstanceColorScheme scheme;
+
+ /**
+ * Creates a new icon.
+ *
+ * @param scheme
+ * Associated color scheme (can be <code>null</code>).
+ * @param startWidth
+ * Original width in pixels.
+ * @param startHeight
+ * Original heigth in pixels.
+ */
+ public ColorSchemeResizableIcon(SubstanceColorScheme scheme,
+ int startWidth, int startHeight) {
+ this.scheme = scheme;
+ this.currWidth = startWidth;
+ this.currHeight = startHeight;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ResizableIcon#setDimension(java.awt.Dimension)
+ */
+ @Override
+ public void setDimension(Dimension newDimension) {
+ this.currWidth = newDimension.width;
+ this.currHeight = newDimension.height;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.currHeight;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.currWidth;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ graphics.translate(x, y);
+
+ double R = Math.max(3.0, this.currHeight / 10.0);
+ double r = R / 2.0;
+ double s = (this.currHeight - 2 * R) / 2.0;
+ double cx = this.currWidth / 2.0;
+ // double cy = this.currHeight / 2.0;
+ double dx = s * Math.sqrt(3.0) / 2.0;
+ // double ss = (this.currHeight - 2 * r) / 2.0;
+ // double dxs = ss * Math.sqrt(3.0) / 2.0;
+
+ R = Math.floor(R);
+
+ Color color1 = (this.scheme == null) ? Color.red : this.scheme
+ .getUltraDarkColor();
+ Color color2 = (this.scheme == null) ? Color.yellow : this.scheme
+ .getDarkColor();
+ Color color3 = (this.scheme == null) ? Color.green : this.scheme
+ .getMidColor();
+ Color color4 = (this.scheme == null) ? Color.cyan : this.scheme
+ .getLightColor();
+ Color color5 = (this.scheme == null) ? Color.blue : this.scheme
+ .getExtraLightColor();
+ Color color6 = (this.scheme == null) ? Color.magenta : this.scheme
+ .getUltraLightColor();
+
+ this.paintCircle(graphics, cx, R, R, color1, true);
+ if (this.currHeight > 16)
+ this.paintCircle(graphics, cx + dx, R + 0.5 * s, R, color2, true);
+ this.paintCircle(graphics, cx + dx, R + 1.5 * s, R, color3, true);
+ if (this.currHeight > 16)
+ this.paintCircle(graphics, cx, this.currHeight - 1 - R, R, color4,
+ true);
+ this.paintCircle(graphics, cx + 1 - dx, R + 1.5 * s, R, color5, true);
+ if (this.currHeight > 16)
+ this.paintCircle(graphics, cx + 1 - dx, R + 0.5 * s, R, color6,
+ true);
+
+ if (r >= 2.0) {
+ this.paintCircle(graphics, cx + 0.5 + dx / 2.0, R + 0.25 * s, r,
+ color1, false);
+ this.paintCircle(graphics, cx + dx, R + s, r, color2, false);
+ this.paintCircle(graphics, cx + 0.5 + dx / 2.0, R
+ + (this.currHeight - 1 + 1.5 * s) / 2.0, r, color3, false);
+ this.paintCircle(graphics, cx - 0.5 - dx / 2.0, R
+ + (this.currHeight - 1 + 1.5 * s) / 2.0, r, color4, false);
+ this.paintCircle(graphics, cx - dx, R + s, r, color5, false);
+ this.paintCircle(graphics, cx - 0.5 - dx / 2.0, R + 0.25 * s, r,
+ color6, false);
+ }
+
+ graphics.dispose();
+ }
+
+ /**
+ * Paints a single circle.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param xc
+ * X coordinate of the circle center.
+ * @param yc
+ * Y coordinate of the circle center.
+ * @param r
+ * Circle radius.
+ * @param color
+ * Circle color.
+ * @param toDrawOutline
+ * Indication whether the circle outline should be drawn.
+ */
+ private void paintCircle(Graphics2D graphics, double xc, double yc,
+ double r, Color color, boolean toDrawOutline) {
+ Shape shape = new Ellipse2D.Double(xc - r, yc - r, 2 * r, 2 * r);
+ this.paintShape(graphics, shape, color, toDrawOutline);
+ }
+
+ /**
+ * Paints shape.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param shape
+ * Shape to draw.
+ * @param color
+ * Circle color.
+ * @param toDrawOutline
+ * Indication whether the shape outline should be drawn.
+ */
+ private void paintShape(Graphics2D graphics, Shape shape, Color color,
+ boolean toDrawOutline) {
+ graphics.setColor(color);
+ graphics.fill(shape);
+ if (toDrawOutline) {
+ graphics.setColor(color.darker());
+ graphics.draw(shape);
+ }
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/SkinResizableIcon.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/SkinResizableIcon.java
new file mode 100644
index 0000000..0404423
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/SkinResizableIcon.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.gallery.oob;
+
+import org.pushingpixels.flamingo.api.common.icon.LayeredIcon;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceSkin;
+
+/**
+ * Resizable icon for <b>Substance</b> skins.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SkinResizableIcon extends LayeredIcon {
+ /**
+ * Creates a new icon.
+ *
+ * @param skin
+ * Associated skin.
+ * @param startWidth
+ * Original width in pixels.
+ * @param startHeight
+ * Original heigth in pixels.
+ */
+ public SkinResizableIcon(SubstanceSkin skin, int startWidth, int startHeight) {
+ super(new WatermarkResizableIcon(skin.getWatermark(), startWidth,
+ startHeight), new ColorSchemeResizableIcon(skin
+ .getActiveColorScheme(DecorationAreaType.NONE), startWidth,
+ startHeight));
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/SubstanceRibbonTask.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/SubstanceRibbonTask.java
new file mode 100644
index 0000000..1dac4ba
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/SubstanceRibbonTask.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.gallery.oob;
+
+import org.pushingpixels.flamingo.api.common.icon.LayeredIcon;
+import org.pushingpixels.flamingo.api.ribbon.JRibbonBand;
+import org.pushingpixels.flamingo.api.ribbon.RibbonTask;
+
+/**
+ * Ribbon task for Substance-related controls (skins, themes, watermarks).
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRibbonTask {
+ /**
+ * Returns a new ribbon task for Substance-related control.
+ *
+ * @return Ribbon task for switching Substance skins.
+ */
+ public static RibbonTask getSubstanceRibbonTask() {
+ // Skin selection
+ JRibbonBand skinBand = new JRibbonBand("Skin", new LayeredIcon(
+ new WatermarkResizableIcon(null, 32, 32),
+ new ColorSchemeResizableIcon(null, 32, 32)), null);
+ SubstanceSkinRibbonGallery.addSkinGallery(skinBand);
+ RibbonTask task = new RibbonTask("Look & Feel", skinBand);
+ return task;
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/SubstanceSkinRibbonGallery.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/SubstanceSkinRibbonGallery.java
new file mode 100644
index 0000000..25d2017
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/SubstanceSkinRibbonGallery.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.gallery.oob;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.*;
+
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.flamingo.api.common.JCommandToggleButton;
+import org.pushingpixels.flamingo.api.common.StringValuePair;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.ribbon.JRibbonBand;
+import org.pushingpixels.flamingo.api.ribbon.RibbonElementPriority;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.skin.SkinInfo;
+
+/**
+ * In-ribbon gallery that contains all available <b>Substance</b> skins.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceSkinRibbonGallery {
+ /**
+ * Adds an in-ribbon gallery that contains all available <b>Substance</b>
+ * skins to the specified ribbon band.
+ *
+ * @param ribbonBand
+ * Ribbon band that will contain the newly added skin gallery.
+ */
+ public static void addSkinGallery(JRibbonBand ribbonBand) {
+ Map<RibbonElementPriority, Integer> prefWidths = new HashMap<RibbonElementPriority, Integer>();
+ prefWidths.put(RibbonElementPriority.LOW, 2);
+ prefWidths.put(RibbonElementPriority.MEDIUM, 4);
+ prefWidths.put(RibbonElementPriority.TOP, 8);
+
+ List<StringValuePair<List<JCommandToggleButton>>> skinGroups = new ArrayList<StringValuePair<List<JCommandToggleButton>>>();
+ List<JCommandToggleButton> skinButtons = new ArrayList<JCommandToggleButton>();
+
+ Map<String, SkinInfo> skins = SubstanceLookAndFeel.getAllSkins();
+ for (Map.Entry<String, SkinInfo> entry : skins.entrySet()) {
+ try {
+ final SubstanceSkin skin = (SubstanceSkin) Class.forName(
+ entry.getValue().getClassName()).newInstance();
+ ResizableIcon icon = new SkinResizableIcon(skin, 60, 40);
+ JCommandToggleButton skinButton = new JCommandToggleButton(skin
+ .getDisplayName(), icon);
+ skinButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ SubstanceLookAndFeel.setSkin(skin);
+ }
+ });
+ }
+ });
+ skinButtons.add(skinButton);
+ } catch (Exception exc) {
+ }
+ }
+
+ skinGroups.add(new StringValuePair<List<JCommandToggleButton>>("Skins",
+ skinButtons));
+
+ ribbonBand.addRibbonGallery("Skins", skinGroups, prefWidths, 5, 3,
+ RibbonElementPriority.TOP);
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/WatermarkResizableIcon.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/WatermarkResizableIcon.java
new file mode 100644
index 0000000..eb608f7
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/gallery/oob/WatermarkResizableIcon.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.gallery.oob;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Composite;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.awt.image.ConvolveOp;
+import java.awt.image.Kernel;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Resizable icon for <b>Substance </b> watermarks.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class WatermarkResizableIcon implements ResizableIcon {
+ /**
+ * Current width in pixels.
+ */
+ private int currWidth;
+
+ /**
+ * Current height in pixels.
+ */
+ private int currHeight;
+
+ /**
+ * Associated watermark. Can be <code>null</code>.
+ */
+ private SubstanceWatermark watermark;
+
+ /**
+ * Images for water signs.
+ */
+ private static Map<Integer, BufferedImage> waterSigns = new HashMap<Integer, BufferedImage>();
+
+ /**
+ * Creates a new icon.
+ *
+ * @param watermark
+ * Associated watermark (can be <code>null</code>).
+ * @param startWidth
+ * Original width in pixels.
+ * @param startHeight
+ * Original heigth in pixels.
+ */
+ public WatermarkResizableIcon(SubstanceWatermark watermark, int startWidth,
+ int startHeight) {
+ this.watermark = watermark;
+ this.currWidth = startWidth;
+ this.currHeight = startHeight;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ResizableIcon#setDimension(java.awt.Dimension)
+ */
+ @Override
+ public void setDimension(Dimension newDimension) {
+ this.currWidth = newDimension.width;
+ this.currHeight = newDimension.height;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.currHeight;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.currWidth;
+ }
+
+ /**
+ * Returns a watermark sign image.
+ *
+ * @param size
+ * Image size.
+ * @return Watermark sign image.
+ */
+ private static BufferedImage getWatermarkSign(int size) {
+ if (waterSigns.containsKey(size))
+ return waterSigns.get(size);
+
+ BufferedImage blurred = SubstanceCoreUtilities
+ .getBlankImage(size, size);
+ Font font = new Font("Tahoma", size > 15 ? Font.PLAIN : Font.BOLD, size);
+ Graphics2D blurredGraphics = (Graphics2D) blurred.getGraphics()
+ .create();
+ blurredGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ blurredGraphics.setColor(new Color(255, 255, 255, 96));
+ blurredGraphics.fillRect(0, 0, size, size);
+ blurredGraphics.setColor(Color.white);
+ blurredGraphics.setFont(font);
+ blurredGraphics.drawString("\u2248", 1, size - 1);
+
+ float data[] = { 0.0625f, 0.125f, 0.0625f, 0.125f, 0.25f, 0.125f,
+ 0.0625f, 0.125f, 0.0625f };
+ Kernel kernel = new Kernel(3, 3, data);
+
+ ConvolveOp convolve = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP,
+ null);
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(size, size);
+ convolve.filter(blurred, result);
+ Graphics2D graphics = (Graphics2D) result.getGraphics().create();
+ graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ graphics.setColor(new Color(0, 0, 255));
+ graphics.setFont(font);
+ graphics.drawString("\u2248", 1, size - 1);
+
+ blurredGraphics.dispose();
+ // graphics.dispose();
+
+ waterSigns.put(size, result);
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ graphics.translate(x, y);
+ graphics.clipRect(0, 0, this.currWidth, this.currHeight);
+ if (this.watermark != null) {
+ graphics.setColor(SubstanceCoreUtilities.getSkin(c)
+ .getEnabledColorScheme(
+ SubstanceLookAndFeel.getDecorationType(c))
+ .getExtraLightColor());
+ graphics.fillRect(0, 0, this.currWidth, this.currHeight);
+ Composite oldComp = graphics.getComposite();
+ graphics.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, 0.6f));
+
+ this.watermark.previewWatermark(graphics,
+ SubstanceColorSchemeUtilities.METALLIC_SKIN, 0, 0,
+ this.currWidth, this.currHeight);
+ graphics.setComposite(oldComp);
+
+ int waterSize = this.currHeight / 2;
+ graphics.drawImage(getWatermarkSign(waterSize), this.currWidth
+ - waterSize - 2, this.currHeight - waterSize - 2, null);
+
+ graphics.setColor(Color.black);
+ graphics.drawRect(0, 0, this.currWidth - 1, this.currHeight - 1);
+ } else {
+ int waterSize = Math.min(this.currHeight, this.currWidth) - 2;
+ graphics.drawImage(getWatermarkSign(waterSize),
+ (this.currWidth - waterSize) / 2,
+ (this.currHeight - waterSize) / 2, null);
+
+ graphics.setColor(Color.black);
+ graphics.drawRect(0, 0, this.currWidth - 1, this.currHeight - 1);
+ }
+
+ graphics.dispose();
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/RibbonBorderShaper.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/RibbonBorderShaper.java
new file mode 100644
index 0000000..343c29a
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/RibbonBorderShaper.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.geom.GeneralPath;
+
+import javax.swing.JComponent;
+
+import org.pushingpixels.flamingo.api.ribbon.JRibbon;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+/**
+ * Utility class for computing outlines of ribbons and ribbon toggle buttons.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RibbonBorderShaper {
+ public static float getRibbonToggleButtonRadius(JComponent comp) {
+ return SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
+ .getComponentFontSize(comp), 5.0f, 2, 1.0f);
+ }
+
+ public static GeneralPath getRibbonBorderOutline(JRibbon ribbon,
+ int startX, int endX, int startSelectedX, int endSelectedX,
+ int topY, int bandTopY, int bottomY, float radius) {
+
+ int height = bottomY - topY;
+ GeneralPath result = new GeneralPath();
+ // float radius3 = SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
+ // .getComponentFontSize(ribbon), 3.0f, 3, 1.0f);
+ float radius3 = (float) (radius / (1.5 * Math.pow(height, 0.5)));
+ float buttonRadius = getRibbonToggleButtonRadius(ribbon);
+ float buttonRadius3 = (float) (buttonRadius / (1.5 * Math.pow(height,
+ 0.5)));
+
+ // start in the top left corner at the end of the curve
+ result.moveTo(startX + radius, bandTopY);
+
+ // move to the bottom start of the selected tab and curve up
+ result.lineTo(startSelectedX - radius, bandTopY);
+ result.quadTo(startSelectedX - radius3, bandTopY - radius3,
+ startSelectedX, bandTopY - radius);
+
+ // move to the top start of the selected tab and curve right
+ result.lineTo(startSelectedX, topY + buttonRadius);
+ result.quadTo(startSelectedX + buttonRadius3, topY + buttonRadius3,
+ startSelectedX + buttonRadius, topY);
+
+ // move to the top end of the selected tab and curve down
+ result.lineTo(endSelectedX - buttonRadius - 1, topY);
+ result.quadTo(endSelectedX + buttonRadius3 - 1, topY + buttonRadius3,
+ endSelectedX - 1, topY + buttonRadius);
+
+ // move to the bottom end of the selected tab and curve right
+ result.lineTo(endSelectedX - 1, bandTopY - radius);
+ result.quadTo(endSelectedX + radius3 - 1, bandTopY - radius3,
+ endSelectedX + radius - 1, bandTopY);
+
+ // move to the top right corner and curve down
+ result.lineTo(endX - radius - 1, bandTopY);
+ result.quadTo(endX - radius3 - 1, bandTopY + radius3, endX - 1,
+ bandTopY + radius);
+
+ // move to the bottom right corner and curve left
+ result.lineTo(endX - 1, bottomY - radius - 1);
+ result.quadTo(endX - radius3 - 1, bottomY - 1 - radius3, endX - radius
+ - 1, bottomY - 1);
+
+ // move to the bottom left corner and curve up
+ result.lineTo(startX + radius, bottomY - 1);
+ result.quadTo(startX + radius3, bottomY - 1 - radius3, startX, bottomY
+ - radius - 1);
+
+ // move to the top left corner and curve right
+ result.lineTo(startX, bandTopY + radius);
+ result.quadTo(startX + radius3, bandTopY + radius3, startX + radius,
+ bandTopY);
+
+ return result;
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceBandControlPanelUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceBandControlPanelUI.java
new file mode 100644
index 0000000..855d287
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceBandControlPanelUI.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.*;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
+import org.pushingpixels.flamingo.internal.ui.ribbon.*;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI for {@link JBandControlPanel} components in <b>Substance</b> look and
+ * feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceBandControlPanelUI extends BasicBandControlPanelUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceBandControlPanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicBandControlPanelUI#paintBandBackground
+ * (java.awt.Graphics, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintBandBackground(Graphics graphics, Rectangle toFill) {
+ float rolloverAmount = 0.0f;
+ Component parent = this.controlPanel.getParent();
+ if (parent instanceof AbstractRibbonBand) {
+ // not in popup
+ AbstractRibbonBand band = (AbstractRibbonBand) this.controlPanel
+ .getParent();
+ BasicRibbonBandUI ui = (BasicRibbonBandUI) band.getUI();
+ rolloverAmount = ui.getRolloverAmount();
+ }
+
+ SubstanceRibbonBandUI.paintRibbonBandBackground(graphics,
+ this.controlPanel, rolloverAmount, this.controlPanel
+ .getBounds().y);
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceFlowBandControlPanelUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceFlowBandControlPanelUI.java
new file mode 100644
index 0000000..c47de30
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceFlowBandControlPanelUI.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.*;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
+import org.pushingpixels.flamingo.internal.ui.ribbon.*;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI for {@link JFlowBandControlPanel} components in <b>Substance</b> look and
+ * feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceFlowBandControlPanelUI extends
+ BasicFlowBandControlPanelUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceFlowBandControlPanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicBandControlPanelUI#paintBandBackground
+ * (java.awt.Graphics, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintBandBackground(Graphics graphics, Rectangle toFill) {
+ float rolloverAmount = 0.0f;
+ Component parent = this.controlPanel.getParent();
+ if (parent instanceof AbstractRibbonBand) {
+ // not in popup
+ AbstractRibbonBand band = (AbstractRibbonBand) this.controlPanel
+ .getParent();
+ BasicRibbonBandUI ui = (BasicRibbonBandUI) band.getUI();
+ rolloverAmount = ui.getRolloverAmount();
+ }
+
+ SubstanceRibbonBandUI.paintRibbonBandBackground(graphics,
+ this.controlPanel, rolloverAmount, this.controlPanel
+ .getBounds().y);
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonApplicationMenuButtonUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonApplicationMenuButtonUI.java
new file mode 100644
index 0000000..fce8856
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonApplicationMenuButtonUI.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2005-2010 Substance Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.ribbon.JRibbon;
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.BasicRibbonApplicationMenuButtonUI;
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuButton;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.effects.GhostPaintingUtils;
+import org.pushingpixels.lafwidget.animation.effects.GhostingListener;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.flamingo.common.ui.ActionPopupTransitionAwareUI;
+import org.pushingpixels.substance.flamingo.utils.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI for {@link JRibbonApplicationMenuButton} components in <b>Substance</b>
+ * look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRibbonApplicationMenuButtonUI extends
+ BasicRibbonApplicationMenuButtonUI implements
+ ActionPopupTransitionAwareUI {
+
+ public SubstanceRibbonApplicationMenuButtonUI(JRibbon ribbon) {
+ super(ribbon);
+ }
+
+ /**
+ * Model change listener for ghost image effects.
+ */
+ private GhostingListener substanceModelChangeListener;
+
+ /**
+ * Tracker for visual state transitions.
+ */
+ protected CommandButtonVisualStateTracker substanceVisualStateTracker;
+
+ public static ComponentUI createUI(JComponent component) {
+ if (component instanceof JRibbon) {
+ return new SubstanceRibbonApplicationMenuButtonUI((JRibbon) component);
+ } else if (component instanceof JRibbonApplicationMenuButton) {
+ return new SubstanceRibbonApplicationMenuButtonUI(
+ ((JRibbonApplicationMenuButton) component).getRibbon());
+ }
+ throw new IllegalArgumentException(
+ "creating a BasicRibbonApplicationMenuButtonUI requires a JRibbon");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.ui.BasicCommandButtonUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substanceVisualStateTracker = new CommandButtonVisualStateTracker();
+ this.substanceVisualStateTracker.installListeners(this.commandButton);
+
+ this.substanceModelChangeListener = new GhostingListener(
+ this.commandButton, this.commandButton.getActionModel());
+ this.substanceModelChangeListener.registerListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.substanceVisualStateTracker.uninstallListeners(this.commandButton);
+ this.substanceVisualStateTracker = null;
+
+ this.substanceModelChangeListener.unregisterListeners();
+ this.substanceModelChangeListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicButtonUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ JRibbonApplicationMenuButton b = (JRibbonApplicationMenuButton) c;
+
+ this.layoutInfo = this.layoutManager.getLayoutInfo(this.commandButton,
+ g);
+ commandButton.putClientProperty("icon.bounds", layoutInfo.iconRect);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(commandButton);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(commandButton);
+ BufferedImage fullAlphaBackground = RibbonApplicationMenuButtonBackgroundDelegate
+ .getFullAlphaBackground(b, fillPainter, borderPainter,
+ commandButton.getWidth(), commandButton.getHeight());
+ g2d.drawImage(fullAlphaBackground, 0, 0, null);
+
+ // Paint the icon
+ Icon icon = b.getIcon();
+ if (icon != null) {
+ int iconWidth = icon.getIconWidth();
+ int iconHeight = icon.getIconHeight();
+ Rectangle iconRect = new Rectangle((c.getWidth() - iconWidth) / 2,
+ (c.getHeight() - iconHeight) / 2, iconWidth, iconHeight);
+ paintButtonIcon(g2d, iconRect);
+ }
+
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.common.ui.BasicCommandButtonUI#paintButtonIcon(java
+ * .awt.Graphics, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintButtonIcon(Graphics g, Rectangle iconRect) {
+ Icon regular = this.applicationMenuButton.isEnabled() ? this.applicationMenuButton
+ .getIcon()
+ : this.applicationMenuButton.getDisabledIcon();
+ if (regular == null)
+ return;
+ boolean useThemed = SubstanceCoreUtilities
+ .useThemedDefaultIcon(this.applicationMenuButton);
+
+ if (regular != null) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ GhostPaintingUtils.paintGhostIcon(g2d, this.applicationMenuButton,
+ regular, iconRect);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.applicationMenuButton, g));
+
+ if (!useThemed) {
+ regular.paintIcon(this.applicationMenuButton, g2d, iconRect.x,
+ iconRect.y);
+ } else {
+ CommandButtonBackgroundDelegate.paintThemedCommandButtonIcon(
+ g2d, iconRect, this.applicationMenuButton, regular,
+ this.applicationMenuButton.getPopupModel(),
+ this.substanceVisualStateTracker
+ .getPopupStateTransitionTracker());
+ }
+ g2d.dispose();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.appmenu.BasicRibbonApplicationMenuButtonUI
+ * #update(java.awt.Graphics, javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ this.paint(g, c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.substance.SubstanceButtonUI#contains(javax.swing.JComponent,
+ * int, int)
+ */
+ @Override
+ public boolean contains(JComponent c, int x, int y) {
+ // allow clicking anywhere in the area (around the button
+ // round outline as well) to activate the button.
+ return (x >= 0) && (x < c.getWidth()) && (y >= 0)
+ && (y < c.getHeight());
+ }
+
+ @Override
+ public StateTransitionTracker getActionTransitionTracker() {
+ return this.substanceVisualStateTracker
+ .getActionStateTransitionTracker();
+ }
+
+ @Override
+ public StateTransitionTracker getPopupTransitionTracker() {
+ return this.substanceVisualStateTracker
+ .getPopupStateTransitionTracker();
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.substanceVisualStateTracker
+ .getPopupStateTransitionTracker();
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return true;
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonApplicationMenuPopupPanelUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonApplicationMenuPopupPanelUI.java
new file mode 100644
index 0000000..954d191
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonApplicationMenuPopupPanelUI.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2005-2010 Substance Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.*;
+
+import javax.swing.CellRendererPane;
+import javax.swing.JComponent;
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.*;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.border.SubstanceBorder;
+
+/**
+ * UI for {@link JRibbonApplicationMenuPopupPanel} components in
+ * <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRibbonApplicationMenuPopupPanelUI extends
+ BasicRibbonApplicationMenuPopupPanelUI {
+ public static ComponentUI createUI(JComponent c) {
+ return new SubstanceRibbonApplicationMenuPopupPanelUI();
+ }
+
+ @Override
+ protected void installComponents() {
+ super.installComponents();
+ Border newBorder = new CompoundBorder(new SubstanceBorder(new Insets(2,
+ 2, 2, 2)), new Border() {
+ @Override
+ public boolean isBorderOpaque() {
+ return true;
+ }
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return new Insets(18, 0, 0, 0);
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ SubstanceColorScheme bgFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ ComponentState.ENABLED);
+ SubstanceColorScheme bgBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ ComponentState.ENABLED);
+ HighlightPainterUtils.paintHighlight(g, null, c, new Rectangle(
+ x, y, width, height), 0.0f, null, bgFillScheme,
+ bgBorderScheme);
+
+ // draw the application menu button
+ JRibbonApplicationMenuButton rendererButton = new JRibbonApplicationMenuButton(
+ applicationMenuPopupPanel.getAppMenuButton()
+ .getRibbon());
+
+ JRibbonApplicationMenuButton appMenuButton = applicationMenuPopupPanel
+ .getAppMenuButton();
+ rendererButton.applyComponentOrientation(appMenuButton
+ .getComponentOrientation());
+
+ rendererButton.setPopupKeyTip(appMenuButton.getPopupKeyTip());
+ rendererButton.setIcon(appMenuButton.getIcon());
+ rendererButton.getPopupModel().setRollover(false);
+ rendererButton.getPopupModel().setPressed(true);
+ rendererButton.getPopupModel().setArmed(true);
+ rendererButton.getPopupModel().setPopupShowing(true);
+
+ CellRendererPane buttonRendererPane = new CellRendererPane();
+ Point buttonLoc = appMenuButton.getLocationOnScreen();
+ Point panelLoc = c.getLocationOnScreen();
+
+ buttonRendererPane.setBounds(panelLoc.x - buttonLoc.x,
+ panelLoc.y - buttonLoc.y, appMenuButton.getWidth(),
+ appMenuButton.getHeight());
+ buttonRendererPane.paintComponent(g, rendererButton,
+ (Container) c, -panelLoc.x + buttonLoc.x, -panelLoc.y
+ + buttonLoc.y, appMenuButton.getWidth(),
+ appMenuButton.getHeight(), true);
+ }
+
+ });
+ this.applicationMenuPopupPanel.setBorder(newBorder);
+
+ this.panelLevel2.setBorder(new Border() {
+ @Override
+ public Insets getBorderInsets(Component c) {
+ boolean ltr = c.getComponentOrientation().isLeftToRight();
+ return new Insets(0, ltr ? 1 : 0, 0, ltr ? 0 : 1);
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return true;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ int componentFontSize = SubstanceSizeUtils
+ .getComponentFontSize(null);
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize) / 2.0);
+ float borderThickness = SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(applicationMenuPopupPanel,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ENABLED);
+ g2d.setColor(scheme.getMidColor());
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_NORMALIZE);
+ int joinKind = BasicStroke.JOIN_ROUND;
+ int capKind = BasicStroke.CAP_BUTT;
+ g2d.setStroke(new BasicStroke(borderThickness, capKind,
+ joinKind));
+
+ boolean ltr = applicationMenuPopupPanel
+ .getComponentOrientation().isLeftToRight();
+ int lineX = ltr ? borderDelta : c.getWidth() - borderDelta - 1;
+ g2d.drawLine(lineX, borderDelta, lineX, height - 1 - 2
+ * borderDelta);
+
+ g2d.dispose();
+ }
+ });
+ this.mainPanel.setBorder(new SubstanceBorder());
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonBandBorder.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonBandBorder.java
new file mode 100644
index 0000000..b72bd98
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonBandBorder.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.*;
+
+import javax.swing.border.Border;
+
+import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
+import org.pushingpixels.flamingo.api.ribbon.resize.IconRibbonBandResizePolicy;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.utils.*;
+
+public class SubstanceRibbonBandBorder implements Border {
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return SubstanceSizeUtils.getDefaultBorderInsets(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height) {
+ // failsafe for LAF change
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ return;
+ }
+
+ if ((width <= 0) || (height <= 0))
+ return;
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ float radius = this.getCornerRadius(c);
+
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(c,
+ ComponentState.ENABLED);
+
+ graphics
+ .setComposite(LafWidgetUtilities.getAlphaComposite(c, alpha, g));
+
+ AbstractRibbonBand band = (AbstractRibbonBand) c;
+ int titleHeight = band.getUI().getBandTitleHeight();
+
+ SubstanceColorScheme borderColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c, ColorSchemeAssociationKind.BORDER,
+ ComponentState.ENABLED);
+ SubstanceImageCreator.paintBorder(c, graphics, x, y, width, height,
+ radius, borderColorScheme);
+
+ if (!(band.getCurrentResizePolicy() instanceof IconRibbonBandResizePolicy)) {
+ // bottom part - header color scheme
+ graphics.clipRect(0, c.getHeight() - titleHeight, c.getWidth(),
+ titleHeight);
+ SubstanceColorScheme bottomColorScheme = SubstanceLookAndFeel
+ .getCurrentSkin(c).getColorScheme(
+ DecorationAreaType.HEADER,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ENABLED);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c,
+ 0.75f, g));
+ SubstanceImageCreator.paintBorder(c, graphics, x, y, width, height,
+ radius, bottomColorScheme);
+ }
+ graphics.dispose();
+ }
+
+ public float getCornerRadius(Component c) {
+ return SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonBandUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonBandUI.java
new file mode 100644
index 0000000..e751870
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonBandUI.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.*;
+import java.awt.geom.Arc2D;
+import java.awt.geom.GeneralPath;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonBandUI;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.fill.MatteFillPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
+import org.pushingpixels.substance.flamingo.common.TransitionAwareResizableIcon;
+import org.pushingpixels.substance.flamingo.common.ui.ActionPopupTransitionAwareUI;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.painter.SeparatorPainterUtils;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * UI for ribbon bands in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRibbonBandUI extends BasicRibbonBandUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceRibbonBandUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasicRibbonBandUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ SubstanceLookAndFeel.setDecorationType(this.ribbonBand,
+ DecorationAreaType.GENERAL);
+
+ Color backgr = this.ribbonBand.getBackground();
+ if (backgr == null || backgr instanceof UIResource) {
+ Color toSet = SubstanceColorSchemeUtilities.getColorScheme(
+ this.ribbonBand, ComponentState.ENABLED)
+ .getBackgroundFillColor();
+ this.ribbonBand.setBackground(new ColorUIResource(toSet));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasicRibbonBandUI#uninstallDefaults()
+ */
+ @Override
+ protected void uninstallDefaults() {
+ DecorationPainterUtils.clearDecorationType(this.ribbonBand);
+ super.uninstallDefaults();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasicRibbonBandUI#installComponents()
+ */
+ @Override
+ protected void installComponents() {
+ super.installComponents();
+
+ SubstanceLookAndFeel.setDecorationType(this.ribbonBand,
+ DecorationAreaType.GENERAL);
+
+ if (this.expandButton != null) {
+ this.expandButton.putClientProperty(
+ SubstanceLookAndFeel.USE_THEMED_DEFAULT_ICONS,
+ Boolean.FALSE);
+ this.expandButton.setFocusable(false);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonBandUI#paintBandTitle(java.awt
+ * .Graphics, java.awt.Rectangle, java.lang.String)
+ */
+ @Override
+ protected void paintBandTitle(Graphics graphics, Rectangle titleRectangle,
+ String title) {
+ // fix for issue 28 - empty ribbon band
+ if (titleRectangle.width <= 0)
+ return;
+
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.setFont(UIManager.getFont("Ribbon.font"));
+
+ FontMetrics fm = graphics.getFontMetrics();
+
+ int currLength = (int) fm.getStringBounds(title, g2d).getWidth();
+ String titleToPaint = title;
+ while (currLength > titleRectangle.width) {
+ title = title.substring(0, title.length() - 1);
+ titleToPaint = title + "...";
+ currLength = (int) fm.getStringBounds(titleToPaint, g2d).getWidth();
+ }
+
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(this.ribbonBand);
+
+ // compute the FG color for the ribbon band
+ // SubstanceColorScheme fgColorScheme = skin.getColorScheme(
+ // this.expandButton, ComponentState.ENABLED);
+
+ // make the title color blend a little with the background
+ SubstanceColorScheme bgColorScheme = skin
+ .getBackgroundColorScheme(DecorationAreaType.HEADER);
+ Color bgFillColor = bgColorScheme.getBackgroundFillColor();
+ Color fgColor = bgColorScheme.getForegroundColor();
+ fgColor = SubstanceColorUtilities.getInterpolatedColor(fgColor,
+ bgFillColor, 0.8f);
+
+ g2d.setColor(fgColor);
+
+ // restrict the title rectangle so that it's painted centered
+ int deltaX = (titleRectangle.width - currLength) / 2;
+ int deltaY = (titleRectangle.height - fm.getAscent() - fm.getDescent()) / 2;
+ Rectangle smallTitleRectangle = new Rectangle(
+ titleRectangle.x + deltaX, titleRectangle.y + deltaY,
+ titleRectangle.width - 2 * deltaX, titleRectangle.height - 2
+ * deltaY);
+ SubstanceTextUtilities.paintText(g2d, this.ribbonBand,
+ smallTitleRectangle, titleToPaint, -1, g2d.getFont(), g2d
+ .getColor(), g2d.getClipBounds());
+
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonBandUI#paintBandTitleBackground
+ * (java.awt.Graphics, java.awt.Rectangle, java.lang.String)
+ */
+ @Override
+ protected void paintBandTitleBackground(Graphics g,
+ Rectangle titleRectangle, String title) {
+ SubstanceFillPainter gradientPainter = new MatteFillPainter();
+
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(this.ribbonBand);
+ SubstanceColorScheme colorScheme = skin
+ .getBackgroundColorScheme(DecorationAreaType.HEADER);
+
+ boolean isDark = colorScheme.isDark();
+ float alpha = 0.85f - (isDark ? 0.15f : 0.35f) * this.rolloverAmount;
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.ribbonBand,
+ alpha, g));
+
+ SubstanceRibbonBandBorder border = (SubstanceRibbonBandBorder) this.ribbonBand
+ .getBorder();
+ float cornerRadius = border.getCornerRadius(this.ribbonBand);
+
+ GeneralPath outline = new GeneralPath();
+
+ // top left
+ outline.moveTo(0, 0);
+ // top right
+ outline.lineTo(titleRectangle.width, 0);
+ // bottom right
+ outline.lineTo(titleRectangle.width, titleRectangle.height
+ - cornerRadius - 1);
+ outline.append(new Arc2D.Double(titleRectangle.width - 2 * cornerRadius
+ - 1, titleRectangle.height - 1 - 2 * cornerRadius,
+ 2 * cornerRadius, 2 * cornerRadius, 0, -90, Arc2D.OPEN), true);
+ // bottom left
+ outline.lineTo(cornerRadius, titleRectangle.height - 1);
+ outline.append(new Arc2D.Double(0, titleRectangle.height - 2
+ * cornerRadius - 1, 2 * cornerRadius, 2 * cornerRadius, 270,
+ -90, Arc2D.OPEN), true);
+ // top left
+ outline.lineTo(0, 0);
+
+ g2d.translate(titleRectangle.x, titleRectangle.y);
+
+ gradientPainter.paintContourBackground(g2d, this.ribbonBand,
+ titleRectangle.width, titleRectangle.height, outline, false,
+ colorScheme, false);
+
+ // outline
+ g2d.setColor(colorScheme.getMidColor());
+ g2d.setStroke(new BasicStroke(1.2f));
+ g2d.draw(outline);
+
+ // top line
+ SubstanceColorScheme separatorScheme = SubstanceLookAndFeel
+ .getCurrentSkin(this.ribbonBand).getColorScheme(
+ DecorationAreaType.HEADER,
+ ColorSchemeAssociationKind.SEPARATOR,
+ ComponentState.ENABLED);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.ribbonBand,
+ alpha * 0.7f, g));
+ SeparatorPainterUtils.paintSeparator(this.ribbonBand, g2d,
+ separatorScheme, titleRectangle.width, 1,
+ SwingConstants.HORIZONTAL, false, 0, 0, true);
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonBandUI#paintBandBackground(java
+ * .awt.Graphics, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintBandBackground(Graphics graphics, Rectangle toFill) {
+ paintRibbonBandBackground(graphics, this.ribbonBand,
+ this.rolloverAmount, 0);
+ }
+
+ public static void paintRibbonBandBackground(Graphics graphics,
+ Component comp, float rolloverAmount, int dy) {
+
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(comp);
+ SubstanceColorScheme bgScheme = skin
+ .getBackgroundColorScheme(SubstanceLookAndFeel
+ .getDecorationType(comp));
+
+ int offset = 20 - dy;
+ float bp = (float) offset / (float) comp.getHeight();
+
+ Color c1 = bgScheme.getUltraLightColor();
+ Color c2 = SubstanceColorUtilities.getInterpolatedColor(bgScheme
+ .getUltraLightColor(), bgScheme.getExtraLightColor(),
+ rolloverAmount);
+ Color c3 = SubstanceColorUtilities
+ .getInterpolatedColor(bgScheme.getExtraLightColor(), bgScheme
+ .getLightColor(), rolloverAmount);
+ Color c4 = SubstanceColorUtilities.getInterpolatedColor(bgScheme
+ .getUltraLightColor(), bgScheme.getExtraLightColor(),
+ rolloverAmount);
+
+ LinearGradientPaint fillPaint = new LinearGradientPaint(0, 0, 0, comp
+ .getHeight(), new float[] { 0.0f, bp - 0.00001f, bp, 1.0f },
+ new Color[] { c1, c2, c3, c4 });
+
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.setPaint(fillPaint);
+ g2d.fillRect(0, 0, comp.getWidth(), comp.getHeight());
+
+ // stamp watermark
+ SubstanceWatermark watermark = skin.getWatermark();
+ if ((watermark != null) && SubstanceCoreUtilities.toDrawWatermark(comp))
+ watermark.drawWatermarkImage(g2d, comp, 0, 0, comp.getWidth(), comp
+ .getHeight());
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasicRibbonBandUI#getBandTitleHeight()
+ */
+ @Override
+ public int getBandTitleHeight() {
+ return 1 + SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
+ .getComponentFontSize(this.ribbonBand), 17, 1, 1, false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasicRibbonBandUI#createExpandButton()
+ */
+ @Override
+ protected JCommandButton createExpandButton() {
+ RibbonBandExpandButton result = new RibbonBandExpandButton();
+ // since paintBandTitleBackground uses HEADER, mark this button with
+ // HEADER as well to sync the mark color
+ SubstanceLookAndFeel.setDecorationType(result,
+ DecorationAreaType.HEADER);
+ result.setIcon(getExpandButtonIcon(result));
+ return result;
+ }
+
+ private static ResizableIcon getExpandButtonIcon(
+ final AbstractCommandButton button) {
+ final int fontSize = SubstanceSizeUtils.getComponentFontSize(button);
+ int arrowIconHeight = (int) SubstanceSizeUtils
+ .getSmallArrowIconHeight(fontSize) + 2;
+ int arrowIconWidth = (int) SubstanceSizeUtils
+ .getSmallArrowIconWidth(fontSize);
+ final ResizableIcon arrowIcon = new TransitionAwareResizableIcon(
+ button,
+ new TransitionAwareResizableIcon.StateTransitionTrackerDelegate() {
+ @Override
+ public StateTransitionTracker getStateTransitionTracker() {
+ return ((ActionPopupTransitionAwareUI) button.getUI())
+ .getActionTransitionTracker();
+ }
+ }, new TransitionAwareResizableIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme,
+ int width, int height) {
+ return SubstanceImageCreator
+ .getDoubleArrowIcon(
+ SubstanceSizeUtils
+ .getComponentFontSize(button),
+ width,
+ height,
+ SubstanceSizeUtils
+ .getDoubleArrowStrokeWidth(fontSize),
+ button.getComponentOrientation()
+ .isLeftToRight() ? SwingConstants.EAST
+ : SwingConstants.WEST, scheme);
+ }
+ }, new Dimension(arrowIconWidth, arrowIconHeight));
+ return arrowIcon;
+ }
+
+ @Override
+ protected void syncExpandButtonIcon() {
+ ResizableIcon icon = getExpandButtonIcon(this.expandButton);
+ this.expandButton.setIcon(icon);
+ }
+
+ private class RibbonBandExpandButton extends JCommandButton implements
+ SubstanceInternalButton {
+ public RibbonBandExpandButton() {
+ super(null, null);
+ this.setFocusable(false);
+
+ this.setBorder(new EmptyBorder(3, 2, 3, 2));
+ this.setActionKeyTip(ribbonBand.getExpandButtonKeyTip());
+ this.setActionRichTooltip(ribbonBand.getExpandButtonRichTooltip());
+ }
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonComponentUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonComponentUI.java
new file mode 100644
index 0000000..6925389
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonComponentUI.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.Graphics;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.api.ribbon.JRibbonComponent;
+import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonComponentUI;
+import org.pushingpixels.substance.flamingo.utils.SubstanceDisabledResizableIcon;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI for {@link JRibbonComponent} components in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRibbonComponentUI extends BasicRibbonComponentUI {
+ public static ComponentUI createUI(JComponent c) {
+ return new SubstanceRibbonComponentUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonComponentUI#createDisabledIcon()
+ */
+ @Override
+ protected ResizableIcon createDisabledIcon() {
+ return new SubstanceDisabledResizableIcon(this.ribbonComponent
+ .getIcon());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonComponentUI#paintIcon(java.awt
+ * .Graphics, org.jvnet.flamingo.ribbon.JRibbonComponent, javax.swing.Icon,
+ * int, int)
+ */
+ @Override
+ protected void paintIcon(Graphics g, JRibbonComponent ribbonComp,
+ Icon icon, int x, int y) {
+ if (ribbonComp.isEnabled() && (icon != null)
+ && SubstanceCoreUtilities.useThemedDefaultIcon(ribbonComp)) {
+ icon = SubstanceCoreUtilities.getThemedIcon(ribbonComp, icon);
+ }
+ super.paintIcon(g, ribbonComp, icon, x, y);
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonFrameTitlePane.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonFrameTitlePane.java
new file mode 100644
index 0000000..22a5adc
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonFrameTitlePane.java
@@ -0,0 +1,772 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.*;
+import java.awt.geom.Arc2D;
+import java.awt.geom.GeneralPath;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import org.pushingpixels.flamingo.api.ribbon.*;
+import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonUI;
+import org.pushingpixels.flamingo.internal.ui.ribbon.RibbonUI;
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuButton;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.utils.RenderingUtils;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.MatteFillPainter;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.painter.SeparatorPainterUtils;
+import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Custom title pane for {@link JRibbonFrame} running under Substance
+ * look-and-feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRibbonFrameTitlePane extends SubstanceTitlePane {
+ /**
+ * Custom component to paint the header of a single contextual task group.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class SubstanceContextualGroupComponent extends JComponent {
+ /**
+ * The associated contextual task group.
+ */
+ RibbonContextualTaskGroup taskGroup;
+
+ /**
+ * Creates the new task group header component.
+ *
+ * @param taskGroup
+ * The associated contextual task group.
+ */
+ public SubstanceContextualGroupComponent(
+ RibbonContextualTaskGroup taskGroup) {
+ this.taskGroup = taskGroup;
+ this.setOpaque(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
+ */
+ @Override
+ protected void paintComponent(Graphics g) {
+ int width = this.getWidth();
+ int height = this.getHeight();
+ Color hueColor = this.taskGroup.getHueColor();
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ Paint paint = new GradientPaint(0, 0, SubstanceColorUtilities
+ .getAlphaColor(hueColor, 0), 0, height,
+ SubstanceColorUtilities.getAlphaColor(hueColor,
+ (int) (255 * RibbonContextualTaskGroup.HUE_ALPHA)));
+ g2d = (Graphics2D) g.create();
+ // translucent gradient paint
+ g2d.setPaint(paint);
+ g2d.fillRect(0, 0, width, height);
+ // and a solid line at the bottom
+ g2d.setColor(hueColor);
+ g2d.drawLine(1, height - 1, width, height - 1);
+
+ JRibbon ribbon = getRibbon();
+
+ SubstanceColorScheme scheme = SubstanceCoreUtilities.getSkin(
+ rootPane).getEnabledColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE);
+
+ // task group title
+ FontMetrics fm = this.getFontMetrics(ribbon.getFont());
+ int yOffset = (height - fm.getHeight()) / 2;
+ RenderingUtils.installDesktopHints(g2d, this);
+
+ int offset = SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
+ .getComponentFontSize(this), 5, 2, 1, false);
+ if (getComponentOrientation().isLeftToRight()) {
+ SubstanceTextUtilities.paintText(g2d, this, new Rectangle(
+ offset, yOffset, width, height - yOffset),
+ this.taskGroup.getTitle(), -1, ribbon.getFont(),
+ SubstanceColorUtilities.getForegroundColor(scheme),
+ null);
+ } else {
+ SubstanceTextUtilities.paintText(g2d, this, new Rectangle(width
+ - offset
+ - g2d.getFontMetrics().stringWidth(
+ this.taskGroup.getTitle()), yOffset, width,
+ height - yOffset), this.taskGroup.getTitle(), -1,
+ ribbon.getFont(), SubstanceColorUtilities
+ .getForegroundColor(scheme), null);
+ }
+
+ // left separator
+ SeparatorPainterUtils.paintSeparator(ribbon, g2d, 2, height,
+ SwingConstants.VERTICAL, false, height / 3, 0, true);
+
+ // right separator
+ g2d.translate(width - 1, 0);
+ SeparatorPainterUtils.paintSeparator(ribbon, g2d, 2, height,
+ SwingConstants.VERTICAL, false, height / 3, 0, true);
+
+ g2d.dispose();
+ }
+ }
+
+ /**
+ * The taskbar panel that holds the {@link JRibbon#getTaskbarComponents()}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class TaskbarPanel extends JPanel {
+ /**
+ * Creates the new taskbar panel.
+ */
+ public TaskbarPanel() {
+ super(new TaskbarLayout());
+ this.setOpaque(false);
+ int insets = SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
+ .getComponentFontSize(this), 2, 3, 1, false);
+ this.setBorder(new EmptyBorder(insets, insets, insets, insets));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
+ */
+ @Override
+ protected void paintComponent(Graphics g) {
+ }
+
+ /**
+ * Returns the outline of this taskbar panel.
+ *
+ * @param insets
+ * Insets.
+ * @return The outline of this taskbar panel.
+ */
+ protected Shape getOutline(double insets) {
+ double width = this.getWidth() - 2 * insets - 1;
+ double height = this.getHeight() - 2 * insets - 1;
+ float radius = (float) height;
+
+ if (getComponentCount() == 0) {
+ return null;
+ } else {
+ GeneralPath outline = new GeneralPath();
+ JRibbonApplicationMenuButton menuButton = FlamingoUtilities
+ .getApplicationMenuButton(SwingUtilities
+ .getWindowAncestor(this));
+
+ boolean ltr = getComponentOrientation().isLeftToRight();
+
+ double alpha = Math.asin((radius - insets / 2)
+ / (radius + insets / 2));
+
+ if (ltr) {
+ // top right
+ outline.moveTo(insets + width - height / 2, insets);
+
+ // right arc
+ outline
+ .append(new Arc2D.Double(insets + width - height,
+ insets, height, height, 90, -180,
+ Arc2D.OPEN), true);
+ // bottom left
+ outline.lineTo(insets, insets + height);
+ if (menuButton != null) {
+ double arcSpan = 90;
+ if (insets != 0) {
+ arcSpan = 180.0 * alpha / Math.PI;
+ }
+ outline.append(new Arc2D.Double(insets - 2 * height,
+ insets, 2 * height, 2 * height, 0, arcSpan,
+ Arc2D.OPEN), true);
+ } else {
+ outline.lineTo(insets, insets);
+ }
+ } else {
+ // top left corner
+ outline.moveTo(insets + height / 2, 0);
+ // left arc
+ outline.append(new Arc2D.Double(insets, 0, height, height,
+ 90, 180, Arc2D.OPEN), true);
+ // bottom right corner
+ outline.lineTo(width - 1, insets + height);
+ if (menuButton != null) {
+ double arcSpan = -90;
+ if (insets != 0) {
+ arcSpan = -180.0 * alpha / Math.PI;
+ }
+ outline.append(new Arc2D.Double(width - 1, 0,
+ 2 * height, 2 * height, 180, arcSpan,
+ Arc2D.OPEN), true);
+ } else {
+ outline.lineTo(width - 1, 0);
+ }
+ }
+ outline.closePath();
+
+ return outline;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#getPreferredSize()
+ */
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension result = super.getPreferredSize();
+ return new Dimension(result.width + result.height / 2,
+ result.height);
+ }
+ }
+
+ /**
+ * Maps the currently visible contextual task groups to the respective child
+ * components of this title pane.
+ */
+ protected Map<RibbonContextualTaskGroup, SubstanceContextualGroupComponent> taskComponentMap;
+
+ /**
+ * Listener to sync {@link #taskComponentMap}.
+ */
+ protected ChangeListener ribbonFrameChangeListener;
+
+ /**
+ * Panel for the taskbar components.
+ */
+ protected TaskbarPanel taskbarPanel;
+
+ /**
+ * Creates a new title pane for {@link JRibbonFrame}.
+ *
+ * @param root
+ * Root pane.
+ * @param ui
+ * UI delegate.
+ */
+ public SubstanceRibbonFrameTitlePane(JRootPane root, SubstanceRootPaneUI ui) {
+ super(root, ui);
+ this.taskComponentMap = new HashMap<RibbonContextualTaskGroup, SubstanceContextualGroupComponent>();
+ this.taskbarPanel = new TaskbarPanel();
+ this.markExtraComponent(this.taskbarPanel, ExtraComponentKind.LEADING);
+ this.add(this.taskbarPanel);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.substance.utils.SubstanceTitlePane#createLayout()
+ */
+ @Override
+ protected LayoutManager createLayout() {
+ return new RibbonFrameTitlePaneLayout();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.substance.utils.SubstanceTitlePane#addNotify()
+ */
+ @Override
+ public void addNotify() {
+ super.addNotify();
+
+ JRibbon ribbon = this.getRibbon();
+ ribbon.putClientProperty(BasicRibbonUI.IS_USING_TITLE_PANE,
+ Boolean.TRUE);
+
+ this.syncRibbonState();
+
+ this.ribbonFrameChangeListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ syncRibbonState();
+ }
+ };
+ ribbon.addChangeListener(this.ribbonFrameChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.substance.utils.SubstanceTitlePane#removeNotify()
+ */
+ @Override
+ public void removeNotify() {
+ JRibbon ribbon = this.getRibbon();
+ ribbon.putClientProperty(BasicRibbonUI.IS_USING_TITLE_PANE, null);
+
+ for (SubstanceContextualGroupComponent groupComp : this.taskComponentMap
+ .values()) {
+ this.remove(groupComp);
+ }
+
+ ribbon.removeChangeListener(this.ribbonFrameChangeListener);
+ this.ribbonFrameChangeListener = null;
+
+ super.removeNotify();
+ }
+
+ /**
+ * Synchronizes the child components for ribbon state (visible contextual
+ * task groups and taskbar components).
+ */
+ protected void syncRibbonState() {
+ // Contextual task groups
+ for (SubstanceContextualGroupComponent groupComp : this.taskComponentMap
+ .values()) {
+ this.remove(groupComp);
+ }
+ this.taskComponentMap.clear();
+ JRibbon ribbon = this.getRibbon();
+ for (int i = 0; i < ribbon.getContextualTaskGroupCount(); i++) {
+ RibbonContextualTaskGroup group = ribbon.getContextualTaskGroup(i);
+ if (!ribbon.isVisible(group))
+ continue;
+ SubstanceContextualGroupComponent taskGroupComponent = new SubstanceContextualGroupComponent(
+ group);
+ taskGroupComponent.applyComponentOrientation(this.getRibbon()
+ .getComponentOrientation());
+ this.add(taskGroupComponent);
+ this.taskComponentMap.put(group, taskGroupComponent);
+ this.markExtraComponent(taskGroupComponent,
+ ExtraComponentKind.MIDDLING);
+ }
+ // Taskbar components
+ this.taskbarPanel.removeAll();
+ this.taskbarPanel.setPreferredSize(null);
+ for (Component taskbarComp : ribbon.getTaskbarComponents()) {
+ this.taskbarPanel.add(taskbarComp);
+ }
+ }
+
+ /**
+ * Custom layout manager for the title panes of {@link JRibbonFrame} under
+ * decorated mode.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class RibbonFrameTitlePaneLayout extends TitlePaneLayout {
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.jvnet.substance.utils.SubstanceTitlePane.TitlePaneLayout#
+ * layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ super.layoutContainer(c);
+
+ JRibbon ribbon = getRibbon();
+ boolean ltr = ribbon.getComponentOrientation().isLeftToRight();
+ RibbonUI ribbonUI = ribbon.getUI();
+
+ if (ltr) {
+ // headers of contextual task groups
+ for (Map.Entry<RibbonContextualTaskGroup, SubstanceContextualGroupComponent> entry : taskComponentMap
+ .entrySet()) {
+ Rectangle taskGroupBounds = ribbonUI
+ .getContextualTaskGroupBounds(entry.getKey());
+ // make sure that the header bounds do not overlap with the
+ // min / max / close buttons
+ int minTrailingX = c.getWidth();
+
+ for (int i = 0; i < c.getComponentCount(); i++) {
+ Component child = c.getComponent(i);
+ if (!child.isVisible())
+ continue;
+ if (child instanceof JComponent) {
+ ExtraComponentKind kind = (ExtraComponentKind) ((JComponent) child)
+ .getClientProperty(EXTRA_COMPONENT_KIND);
+ if (kind == ExtraComponentKind.LEADING)
+ continue;
+ if (child instanceof SubstanceContextualGroupComponent)
+ continue;
+
+ minTrailingX = Math.min(child.getX(), minTrailingX);
+ }
+ }
+
+ int width = taskGroupBounds.width;
+ if (taskGroupBounds.x + width > (minTrailingX - 5)) {
+ width = minTrailingX - 5 - taskGroupBounds.x;
+ }
+ entry.getValue().setBounds(
+ new Rectangle(taskGroupBounds.x, 0, width, c
+ .getHeight()));
+ // hide headers when the task toggle buttons
+ // are wrapped with visible scroller buttons
+ entry.getValue().setVisible(
+ !ribbonUI.isShowingScrollsForTaskToggleButtons());
+ }
+
+ // taskbar panel
+ taskbarPanel.setVisible(true);
+ Dimension pref = taskbarPanel.getPreferredSize();
+ if (taskbarPanel.getComponentCount() == 0) {
+ // fix for issue 38 on Flamingo - if there are no
+ // taskbar components, don't push the title to the right
+ pref.width = 0;
+ }
+
+ SubstanceRibbonRootPaneUI rootPaneUI = (SubstanceRibbonRootPaneUI) getRootPane()
+ .getUI();
+ JRibbonApplicationMenuButton menuButton = rootPaneUI.applicationMenuButton;
+
+ if (menuButton != null) {
+ if (menuButton.isVisible()) {
+ int maxLeadingX = menuButton.getX()
+ + menuButton.getWidth() + 2
+ * getTaskBarLayoutGap(taskbarPanel);
+ if (taskbarPanel.isVisible()) {
+ taskbarPanel.setBounds(maxLeadingX, 0, pref.width,
+ c.getHeight());
+ }
+ menuBar.setVisible(false);
+ } else {
+ if (taskbarPanel.isVisible()) {
+ if (pref.width == 0) {
+ taskbarPanel.setBounds(menuBar.getX()
+ + menuBar.getWidth(), 0, pref.width, c
+ .getHeight());
+ } else {
+ taskbarPanel.setBounds(menuBar.getX()
+ + menuBar.getWidth() + 5, 0,
+ pref.width, c.getHeight());
+ }
+ }
+ menuBar.setVisible(true);
+ }
+ } else {
+ menuBar.setVisible(true);
+ }
+ } else {
+ // headers of contextual task groups
+ for (Map.Entry<RibbonContextualTaskGroup, SubstanceContextualGroupComponent> entry : taskComponentMap
+ .entrySet()) {
+ Rectangle taskGroupBounds = ribbonUI
+ .getContextualTaskGroupBounds(entry.getKey());
+ // make sure that the header bounds do not overlap with the
+ // min / max / close buttons
+ int maxTrailingX = 0;
+
+ for (int i = 0; i < c.getComponentCount(); i++) {
+ Component child = c.getComponent(i);
+ if (!child.isVisible())
+ continue;
+ if (child instanceof JComponent) {
+ ExtraComponentKind kind = (ExtraComponentKind) ((JComponent) child)
+ .getClientProperty(EXTRA_COMPONENT_KIND);
+ if (kind == ExtraComponentKind.LEADING)
+ continue;
+ if (child instanceof SubstanceContextualGroupComponent)
+ continue;
+
+ maxTrailingX = Math.max(child.getX()
+ + child.getWidth(), maxTrailingX);
+ }
+ }
+
+ int width = taskGroupBounds.width;
+ int x = taskGroupBounds.x;
+ if (taskGroupBounds.x < (maxTrailingX + 5)) {
+ int diff = maxTrailingX + 5 - taskGroupBounds.x;
+ x += diff;
+ width -= diff;
+ }
+ entry.getValue().setBounds(
+ new Rectangle(x, 0, width, c.getHeight()));
+ // hide headers when the task toggle buttons
+ // are wrapped with visible scroller buttons
+ entry.getValue().setVisible(
+ !ribbonUI.isShowingScrollsForTaskToggleButtons());
+ }
+
+ // taskbar panel
+ taskbarPanel.setVisible(true);
+ Dimension pref = taskbarPanel.getPreferredSize();
+ if (taskbarPanel.getComponentCount() == 0) {
+ // fix for issue 38 on Flamingo - if there are no
+ // taskbar components, don't push the title to the left
+ pref.width = 0;
+ }
+
+ SubstanceRibbonRootPaneUI rootPaneUI = (SubstanceRibbonRootPaneUI) getRootPane()
+ .getUI();
+ JRibbonApplicationMenuButton menuButton = rootPaneUI.applicationMenuButton;
+
+ if (menuButton != null) {
+ if (menuButton.isVisible()) {
+ int maxLeadingX = menuButton.getX() - 2
+ * getTaskBarLayoutGap(taskbarPanel);
+ if (taskbarPanel.isVisible()) {
+ taskbarPanel.setBounds(maxLeadingX - pref.width, 0,
+ pref.width, c.getHeight());
+ }
+ menuBar.setVisible(false);
+ } else {
+ if (taskbarPanel.isVisible()) {
+ if (pref.width == 0) {
+ taskbarPanel.setBounds(menuBar.getX(), 0,
+ pref.width, c.getHeight());
+ } else {
+ taskbarPanel.setBounds(menuBar.getX() - 5
+ - pref.width, 0, pref.width, c
+ .getHeight());
+ }
+ }
+ menuBar.setVisible(true);
+ }
+ } else {
+ menuBar.setVisible(true);
+ }
+ }
+ }
+ }
+
+ /**
+ * Layout for the task bar.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class TaskbarLayout implements LayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ Insets ins = c.getInsets();
+ int pw = 0;
+ int gap = getTaskBarLayoutGap(c);
+ for (Component regComp : getRibbon().getTaskbarComponents()) {
+ // Do not add layout space for non-visible components
+ if (regComp.isVisible()){
+ pw += regComp.getPreferredSize().width;
+ pw += gap;
+ }
+ }
+ return new Dimension(pw + ins.left + ins.right, c.getParent()
+ .getHeight());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ return this.preferredLayoutSize(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ Insets ins = c.getInsets();
+ int gap = getTaskBarLayoutGap(c);
+ boolean ltr = getComponentOrientation().isLeftToRight();
+ int x = ltr ? ins.left : c.getWidth() - ins.right;
+ for (Component regComp : getRibbon().getTaskbarComponents()) {
+ // Do not add layout space for non-visible components
+ if (regComp.isVisible()){
+ int pw = regComp.getPreferredSize().width;
+ if (ltr) {
+ regComp.setBounds(x, ins.top, pw, c.getHeight() - ins.top
+ - ins.bottom);
+ x += (pw + gap);
+ } else {
+ regComp.setBounds(x - pw, ins.top, pw, c.getHeight()
+ - ins.top - ins.bottom);
+ x -= (pw + gap);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Retrieves the {@link JRibbon} component of the associated
+ * {@link JRibbonFrame}.
+ *
+ * @return {@link JRibbon} component of the associated {@link JRibbonFrame}.
+ */
+ private JRibbon getRibbon() {
+ JRibbonFrame ribbonFrame = (JRibbonFrame) SwingUtilities
+ .getWindowAncestor(this);
+ JRibbon ribbon = ribbonFrame.getRibbon();
+ return ribbon;
+ }
+
+ /**
+ * Returns the layout gap of the taskbar panel.
+ *
+ * @param c
+ * Container.
+ * @return Layout gap of the taskbar panel.
+ */
+ private int getTaskBarLayoutGap(Container c) {
+ return SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
+ .getComponentFontSize(c), 1, 6, 1, false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.substance.utils.SubstanceTitlePane#paintComponent(java.awt.
+ * Graphics)
+ */
+ @Override
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ if (taskbarPanel.getWidth() != 0) {
+ g2d.translate(taskbarPanel.getX(), taskbarPanel.getY());
+ // paint the outline of the taskbar panel to complete
+ // the correct appearance in the area behind the application
+ // menu button
+ // g2d.clipRect(0, 0, 20, 100);
+ paintTaskBarPanelOutline(g2d, this.taskbarPanel);
+ g2d.translate(-taskbarPanel.getX(), -taskbarPanel.getY());
+ }
+
+ if (SubstanceLookAndFeel.getCurrentSkin(this).getOverlayPainters(
+ DecorationAreaType.PRIMARY_TITLE_PANE).isEmpty()) {
+ // g2d.translate(0, this.getHeight() - 1);
+ SubstanceColorScheme compScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this, ColorSchemeAssociationKind.SEPARATOR,
+ ComponentState.ENABLED);
+ Color sepColor = compScheme.isDark() ? SeparatorPainterUtils
+ .getSeparatorShadowColor(compScheme)
+ : SeparatorPainterUtils.getSeparatorDarkColor(compScheme);
+ g2d.setColor(sepColor);
+ g2d.drawLine(0, this.getHeight() - 1, this.getWidth(), this
+ .getHeight() - 1);
+ // SeparatorPainterUtils.paintSeparator(this, g2d, this.getWidth(),
+ // 0,
+ // JSeparator.HORIZONTAL, false, 0);
+ }
+ g2d.dispose();
+ }
+
+ /**
+ * Paints the outline of the taskbar panel.
+ *
+ * @param g
+ * Graphics context.
+ * @param taskbarPanel
+ * Taskbar panel.
+ */
+ protected static void paintTaskBarPanelOutline(Graphics g,
+ TaskbarPanel taskbarPanel) {
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(taskbarPanel)) / 2.0);
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(taskbarPanel));
+
+ Shape contour = taskbarPanel.getOutline(borderDelta);
+ Shape contourInner = taskbarPanel.getOutline(borderDelta
+ + borderThickness);
+
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(taskbarPanel, ComponentState.ENABLED);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(taskbarPanel,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ENABLED);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(taskbarPanel);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setComposite(AlphaComposite.SrcOver.derive(0.6f));
+ if (contour != null) {
+ Shape clip = g2d.getClip();
+ g2d.clip(contour);
+ DecorationPainterUtils.paintDecorationBackground(g2d, taskbarPanel,
+ true);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(taskbarPanel,
+ 0.3f, g));
+ MatteFillPainter.INSTANCE.paintContourBackground(g2d, taskbarPanel,
+ taskbarPanel.getWidth(), taskbarPanel.getHeight(), contour
+ .getBounds(), false, colorScheme, false);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(taskbarPanel,
+ 1.0f, g));
+ g2d.setClip(clip);
+
+ }
+ borderPainter.paintBorder(g2d, taskbarPanel, taskbarPanel.getWidth(),
+ taskbarPanel.getHeight(), contour, contourInner, borderScheme);
+ g2d.dispose();
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonGalleryUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonGalleryUI.java
new file mode 100644
index 0000000..c6862bb
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonGalleryUI.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonGalleryUI;
+import org.pushingpixels.flamingo.internal.ui.ribbon.JRibbonGallery;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.flamingo.common.TransitionAwareResizableIcon;
+import org.pushingpixels.substance.flamingo.common.ui.ActionPopupTransitionAwareUI;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * UI delegate for {@link JRibbonGallery} component under Substance
+ * look-and-feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRibbonGalleryUI extends BasicRibbonGalleryUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceRibbonGalleryUI();
+ }
+
+ /**
+ * Creates new UI delegate.
+ */
+ private SubstanceRibbonGalleryUI() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonGalleryUI#paintRibbonGalleryBorder
+ * (java.awt.Graphics)
+ */
+ @Override
+ protected void paintRibbonGalleryBorder(Graphics graphics) {
+ Graphics2D g2d = (Graphics2D) graphics;
+ SubstanceColorScheme borderColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.ribbonGallery,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ENABLED);
+ SubstanceImageCreator.paintBorder(this.ribbonGallery, g2d,
+ this.margin.left, this.margin.top, this.ribbonGallery
+ .getWidth()
+ - this.margin.left - this.margin.right,
+ this.ribbonGallery.getHeight() - this.margin.top
+ - this.margin.bottom, SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(this.ribbonGallery)),
+ borderColorScheme);
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonGalleryUI#createExpandButton()
+ */
+ @Override
+ protected ExpandCommandButton createExpandButton() {
+ final ExpandCommandButton button = super.createExpandButton();
+ final int fontSize = SubstanceSizeUtils.getComponentFontSize(button);
+ int arrowIconHeight = (int) SubstanceSizeUtils
+ .getSmallArrowIconHeight(fontSize) + 3;
+ int arrowIconWidth = (int) SubstanceSizeUtils
+ .getSmallArrowIconWidth(fontSize);
+ final ResizableIcon arrowIcon = new TransitionAwareResizableIcon(
+ button,
+ new TransitionAwareResizableIcon.StateTransitionTrackerDelegate() {
+ @Override
+ public StateTransitionTracker getStateTransitionTracker() {
+ return ((ActionPopupTransitionAwareUI) button.getUI())
+ .getActionTransitionTracker();
+ }
+ }, new TransitionAwareResizableIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme,
+ int width, int height) {
+ return SubstanceImageCreator
+ .getDoubleArrowIcon(
+ SubstanceSizeUtils
+ .getComponentFontSize(button),
+ width,
+ height,
+ SubstanceSizeUtils
+ .getDoubleArrowStrokeWidth(fontSize),
+ SwingConstants.SOUTH, scheme);
+ }
+ }, new Dimension(arrowIconWidth, arrowIconHeight));
+ button.setIcon(arrowIcon);
+ return button;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonGalleryUI#createScrollDownButton
+ * ()
+ */
+ @Override
+ protected JCommandButton createScrollDownButton() {
+ final JCommandButton button = super.createScrollDownButton();
+ final int fontSize = SubstanceSizeUtils.getComponentFontSize(button);
+ int arrowIconHeight = (int) SubstanceSizeUtils
+ .getSmallArrowIconHeight(fontSize);
+ int arrowIconWidth = (int) SubstanceSizeUtils
+ .getSmallArrowIconWidth(fontSize);
+ final ResizableIcon arrowIcon = new TransitionAwareResizableIcon(
+ button,
+ new TransitionAwareResizableIcon.StateTransitionTrackerDelegate() {
+ @Override
+ public StateTransitionTracker getStateTransitionTracker() {
+ return ((ActionPopupTransitionAwareUI) button.getUI())
+ .getActionTransitionTracker();
+ }
+ }, new TransitionAwareResizableIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme,
+ int width, int height) {
+ return SubstanceImageCreator.getArrowIcon(width,
+ height, SubstanceSizeUtils
+ .getDoubleArrowStrokeWidth(fontSize),
+ SwingConstants.SOUTH, scheme);
+ }
+ }, new Dimension(arrowIconWidth, arrowIconHeight));
+ button.setIcon(arrowIcon);
+ return button;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonGalleryUI#createScrollUpButton()
+ */
+ @Override
+ protected JCommandButton createScrollUpButton() {
+ final JCommandButton button = super.createScrollUpButton();
+ final int fontSize = SubstanceSizeUtils.getComponentFontSize(button);
+ int arrowIconHeight = (int) SubstanceSizeUtils
+ .getSmallArrowIconHeight(fontSize);
+ int arrowIconWidth = (int) SubstanceSizeUtils
+ .getSmallArrowIconWidth(fontSize);
+ ResizableIcon arrowIcon = new TransitionAwareResizableIcon(
+ button,
+ new TransitionAwareResizableIcon.StateTransitionTrackerDelegate() {
+ @Override
+ public StateTransitionTracker getStateTransitionTracker() {
+ return ((ActionPopupTransitionAwareUI) button.getUI())
+ .getActionTransitionTracker();
+ }
+ }, new TransitionAwareResizableIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme,
+ int width, int height) {
+ return SubstanceImageCreator.getArrowIcon(width,
+ height, SubstanceSizeUtils
+ .getDoubleArrowStrokeWidth(fontSize),
+ SwingConstants.NORTH, scheme);
+ }
+ }, new Dimension(arrowIconWidth, arrowIconHeight));
+ button.setIcon(arrowIcon);
+ return button;
+ }
+}
\ No newline at end of file
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonRootPaneUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonRootPaneUI.java
new file mode 100644
index 0000000..97cfa7e
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonRootPaneUI.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2005-2010 Substance Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.*;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.ribbon.JRibbon;
+import org.pushingpixels.flamingo.api.ribbon.JRibbonFrame;
+import org.pushingpixels.flamingo.internal.ui.ribbon.JRibbonRootPane;
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuButton;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
+
+/**
+ * UI delegate for root panes of {@link JRibbonFrame} under Substance
+ * look-and-feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRibbonRootPaneUI extends SubstanceRootPaneUI {
+ /**
+ * Application menu button of this root pane.
+ */
+ protected JRibbonApplicationMenuButton applicationMenuButton;
+
+ /**
+ * Hierarchy listener to track the creation of application menu button.
+ */
+ protected HierarchyListener substanceRibbonHierarchyListener;
+
+ public static ComponentUI createUI(JComponent c) {
+ return new SubstanceRibbonRootPaneUI();
+ }
+
+ @Override
+ protected void installComponents(JRootPane root) {
+ super.installComponents(root);
+
+ this.createAppMenuButton(root);
+ }
+
+ @Override
+ protected void installListeners(final JRootPane root) {
+ super.installListeners(root);
+
+ this.substanceRibbonHierarchyListener = new HierarchyListener() {
+ @Override
+ public void hierarchyChanged(HierarchyEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ createAppMenuButton(root);
+ }
+ });
+ }
+ };
+
+ root.addHierarchyListener(this.substanceRibbonHierarchyListener);
+ }
+
+ @Override
+ protected void uninstallListeners(JRootPane root) {
+ root.removeHierarchyListener(this.substanceRibbonHierarchyListener);
+ this.substanceRibbonHierarchyListener = null;
+
+ super.uninstallListeners(root);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicRootPaneUI#uninstallComponents(javax.swing
+ * .JRootPane)
+ */
+ @Override
+ protected void uninstallComponents(JRootPane root) {
+ root.getLayeredPane().remove(this.applicationMenuButton);
+ this.applicationMenuButton = null;
+
+ super.uninstallComponents(root);
+ }
+
+ @Override
+ protected LayoutManager createLayoutManager() {
+ return new SubstanceRibbonRootLayout();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.substance.SubstanceRootPaneUI#createTitlePane(javax.swing.JRootPane
+ * )
+ */
+ @Override
+ protected JComponent createTitlePane(JRootPane root) {
+ return new SubstanceRibbonFrameTitlePane(root, this);
+ }
+
+ /**
+ * Custom layout manager for the {@link JRibbonRootPane} under Substance.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class SubstanceRibbonRootLayout extends SubstanceRootLayout {
+ @Override
+ public void layoutContainer(Container parent) {
+ super.layoutContainer(parent);
+
+ if (applicationMenuButton == null)
+ return;
+
+ JRibbonRootPane ribbonRootPane = (JRibbonRootPane) root;
+ JRibbonFrame ribbonFrame = (JRibbonFrame) ribbonRootPane
+ .getParent();
+ boolean ltr = ribbonFrame.getRibbon().getComponentOrientation()
+ .isLeftToRight();
+ SubstanceRibbonUI ribbonUI = (SubstanceRibbonUI) ribbonFrame
+ .getRibbon().getUI();
+ int appMenuButtonSize = getTitlePane().getHeight()
+ + ribbonUI.getTaskToggleButtonHeight();
+ applicationMenuButton.setVisible(ribbonFrame.getRibbon()
+ .getApplicationMenu() != null);
+ if (ltr) {
+ applicationMenuButton.setBounds(3, 3, appMenuButtonSize - 6,
+ appMenuButtonSize - 6);
+ } else {
+ applicationMenuButton.setBounds(parent.getWidth() - 3
+ - appMenuButtonSize, 3, appMenuButtonSize - 6,
+ appMenuButtonSize - 6);
+ }
+ syncApplicationMenuTips();
+ getTitlePane().doLayout();
+ ribbonFrame.getRibbon().doLayout();
+ }
+ }
+
+ /**
+ * Synchronizes the rich tooltip and popup keytip of the application menu
+ * button.
+ */
+ public void syncApplicationMenuTips() {
+ if ((this.applicationMenuButton == null)
+ || !this.applicationMenuButton.isVisible())
+ return;
+
+ JRibbonRootPane ribbonRootPane = (JRibbonRootPane) root;
+ if (ribbonRootPane == null)
+ return;
+ JRibbonFrame ribbonFrame = (JRibbonFrame) ribbonRootPane.getParent();
+ JRibbon ribbon = ribbonFrame.getRibbon();
+ if (ribbon != null) {
+ this.applicationMenuButton.setPopupRichTooltip(ribbon
+ .getApplicationMenuRichTooltip());
+ this.applicationMenuButton.setPopupKeyTip(ribbon
+ .getApplicationMenuKeyTip());
+ }
+ }
+
+ /**
+ * Creates the application menu button.
+ *
+ * @param root
+ * Root pane.
+ */
+ private void createAppMenuButton(JRootPane root) {
+ Window windowAncestor = SwingUtilities.getWindowAncestor(root);
+ if ((applicationMenuButton == null)
+ && (windowAncestor instanceof JRibbonFrame)) {
+ JRibbonFrame ribbonFrame = (JRibbonFrame) windowAncestor;
+ applicationMenuButton = new JRibbonApplicationMenuButton(
+ ribbonFrame.getRibbon());
+ applicationMenuButton.applyComponentOrientation(ribbonFrame
+ .getRibbon().getComponentOrientation());
+ root.getLayeredPane().add(applicationMenuButton);
+ root.getLayeredPane().setLayer(applicationMenuButton,
+ JRibbonRootPane.RIBBON_SPECIAL_LAYER);
+ FlamingoUtilities.updateRibbonFrameIconImages(ribbonFrame);
+ JComponent titlePane = getTitlePane();
+ if (titlePane != null) {
+ getTitlePane().doLayout();
+ }
+ }
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonTaskToggleButtonUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonTaskToggleButtonUI.java
new file mode 100644
index 0000000..8c7f84b
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonTaskToggleButtonUI.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Map;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.border.Border;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.ribbon.RibbonContextualTaskGroup;
+import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonTaskToggleButtonUI;
+import org.pushingpixels.flamingo.internal.ui.ribbon.JRibbonTaskToggleButton;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.flamingo.ribbon.RibbonBackgroundDelegate;
+import org.pushingpixels.substance.flamingo.utils.CommandButtonVisualStateTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * UI for {@link JRibbonTaskToggleButton} components in <b>Substance</b> look
+ * and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRibbonTaskToggleButtonUI extends
+ BasicRibbonTaskToggleButtonUI implements TransitionAwareUI {
+ /**
+ * Tracker for visual state transitions.
+ */
+ protected CommandButtonVisualStateTracker substanceVisualStateTracker;
+
+ /**
+ * Property change listener on the button.
+ */
+ private PropertyChangeListener substancePropertyChangeListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ return new SubstanceRibbonTaskToggleButtonUI();
+ }
+
+ /**
+ * Painting delegate.
+ */
+ private RibbonBackgroundDelegate delegate;
+
+ /**
+ * Simple constructor.
+ */
+ public SubstanceRibbonTaskToggleButtonUI() {
+ this.delegate = new RibbonBackgroundDelegate();
+ }
+
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ this.commandButton.setOpaque(false);
+ this.commandButton.setBorder(new Border() {
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return new Insets(0, 12, 0, 12);
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ }
+ });
+ SubstanceLookAndFeel.setDecorationType(this.commandButton,
+ DecorationAreaType.GENERAL);
+
+ this.commandButton.putClientProperty(
+ SubstanceLookAndFeel.COLORIZATION_FACTOR,
+ RibbonContextualTaskGroup.HUE_ALPHA);
+ }
+
+ @Override
+ protected void uninstallComponents() {
+ DecorationPainterUtils.clearDecorationType(this.commandButton);
+ super.uninstallDefaults();
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substanceVisualStateTracker = new CommandButtonVisualStateTracker();
+ this.substanceVisualStateTracker.installListeners(this.commandButton);
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("contextualGroupHueColor".equals(evt.getPropertyName())) {
+ Color newValue = (Color) evt.getNewValue();
+ commandButton.setBackground(newValue);
+ }
+ }
+ };
+ this.commandButton
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ @Override
+ protected void uninstallListeners() {
+ this.substanceVisualStateTracker.uninstallListeners(this.commandButton);
+ this.substanceVisualStateTracker = null;
+
+ this.commandButton
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicToggleButtonUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ this.layoutInfo = this.layoutManager.getLayoutInfo(this.commandButton,
+ g);
+
+ this.delegate.updateTaskToggleButtonBackground(g,
+ (JRibbonTaskToggleButton) this.commandButton);
+ this.paintText(g);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicToggleTabButtonUI#update(java.awt.Graphics
+ * , javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ this.paint(g, c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonTaskToggleButtonUI#paintText(
+ * java.awt.Graphics)
+ */
+ @Override
+ protected void paintText(Graphics g) {
+ FontMetrics fm = g.getFontMetrics();
+ String toPaint = this.commandButton.getText();
+
+ // compute the insets
+ int fullInsets = this.commandButton.getInsets().left;
+ int pw = this.getPreferredSize(this.commandButton).width;
+ int mw = this.getMinimumSize(this.commandButton).width;
+ int w = this.commandButton.getWidth();
+ int h = this.commandButton.getHeight();
+ int insets = fullInsets - (pw - w) * (fullInsets - 2) / (pw - mw);
+
+ // and the text rectangle
+ Rectangle textRect = new Rectangle(insets,
+ 1 + (h - fm.getHeight()) / 2, w - 2 * insets, fm.getHeight());
+
+ // show the first characters that fit into the available text rectangle
+ while (true) {
+ if (toPaint.length() == 0)
+ break;
+ int strWidth = fm.stringWidth(toPaint);
+ if (strWidth <= textRect.width)
+ break;
+ toPaint = toPaint.substring(0, toPaint.length() - 1);
+ }
+
+ int finalStrWidth = fm.stringWidth(toPaint);
+ if (finalStrWidth < textRect.width) {
+ int delta = textRect.width - finalStrWidth;
+ textRect.x += delta / 2;
+ textRect.width -= delta;
+ }
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = this.substanceVisualStateTracker
+ .getActionStateTransitionTracker().getModelStateInfo();
+ ComponentState currState = modelStateInfo.getCurrModelState();
+ float buttonAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ this.commandButton, currState);
+
+ Color fgColor = getForegroundColor(this.commandButton, modelStateInfo);
+
+ if (buttonAlpha < 1.0f) {
+ Color bgFillColor = SubstanceColorUtilities
+ .getBackgroundFillColor(this.commandButton);
+ fgColor = SubstanceColorUtilities.getInterpolatedColor(fgColor,
+ bgFillColor, buttonAlpha);
+ }
+
+ SubstanceTextUtilities.paintText(g, this.commandButton, textRect,
+ toPaint, -1, this.commandButton.getFont(), fgColor, null);
+ }
+
+ private static Color getForegroundColor(AbstractCommandButton button,
+ StateTransitionTracker.ModelStateInfo modelStateInfo) {
+ ComponentState currState = modelStateInfo.getCurrModelState();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(
+ button.isFlat() && currState == ComponentState.ENABLED ? button
+ .getParent() : button,
+ ColorSchemeAssociationKind.TAB, currState);
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ return colorScheme.getForegroundColor();
+ }
+
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ float alpha = activeEntry.getValue().getContribution();
+ SubstanceColorScheme activeColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(
+ button.isFlat()
+ && activeState == ComponentState.ENABLED ? button
+ .getParent() : button,
+ ColorSchemeAssociationKind.TAB, activeState);
+ Color activeForeground = activeColorScheme.getForegroundColor();
+ aggrRed += alpha * activeForeground.getRed();
+ aggrGreen += alpha * activeForeground.getGreen();
+ aggrBlue += alpha * activeForeground.getBlue();
+ }
+ return new Color((int) aggrRed, (int) aggrGreen, (int) aggrBlue);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.ComponentUI#getPreferredSize(javax.swing.JComponent)
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ AbstractCommandButton button = (AbstractCommandButton) c;
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(button);
+
+ Dimension superPref = super.getPreferredSize(button);
+ if (superPref == null)
+ return null;
+
+ if (shaper == null)
+ return superPref;
+
+ JButton dummy = new JButton(button.getText(), button.getIcon());
+ return shaper.getPreferredSize(dummy, superPref);
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.substanceVisualStateTracker
+ .getActionStateTransitionTracker();
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return true;
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonUI.java
new file mode 100644
index 0000000..b453646
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRibbonUI.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import org.pushingpixels.flamingo.api.ribbon.AbstractRibbonBand;
+import org.pushingpixels.flamingo.api.ribbon.RibbonContextualTaskGroup;
+import org.pushingpixels.flamingo.api.ribbon.RibbonTask;
+import org.pushingpixels.flamingo.internal.ui.ribbon.BasicRibbonUI;
+import org.pushingpixels.flamingo.internal.ui.ribbon.JRibbonRootPane;
+import org.pushingpixels.flamingo.internal.ui.ribbon.JRibbonTaskToggleButton;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.painter.SeparatorPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+import javax.swing.*;
+import javax.swing.plaf.ColorUIResource;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import java.awt.*;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * UI for ribbon in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRibbonUI extends BasicRibbonUI {
+
+ /**
+ * This component extends across the full width of the tab row
+ * so that header backgrounds can be properly painted
+ */
+ protected JComponent tabPanelHeaderBackground;
+
+ /**
+ * Panel for hosting task toggle buttons.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class SubstanceTaskToggleButtonsHostPanel extends
+ TaskToggleButtonsHostPanel {
+ @Override
+ protected void paintContextualTaskGroupOutlines(Graphics g,
+ RibbonContextualTaskGroup group, Rectangle groupBounds) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ // SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ // .getBorderColorScheme(ribbon, ComponentState.DEFAULT);
+
+ g2d.translate(groupBounds.x, 0);
+ SeparatorPainterUtils.paintSeparator(ribbon, g2d, 2,
+ groupBounds.height * 3 / 4, SwingConstants.VERTICAL, false,
+ 0, groupBounds.height / 3, true);
+
+ g2d.translate(groupBounds.width - 1, 0);
+ SeparatorPainterUtils.paintSeparator(ribbon, g2d, 2,
+ groupBounds.height * 3 / 4, SwingConstants.VERTICAL, false,
+ 0, groupBounds.height / 3, true);
+
+ g2d.dispose();
+ }
+
+ @Override
+ protected void paintTaskOutlines(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(ribbon,
+ ColorSchemeAssociationKind.SEPARATOR,
+ ComponentState.ENABLED);
+
+ Set<RibbonTask> tasksWithTrailingSeparators = new HashSet<RibbonTask>();
+ // add all regular tasks except the last
+ for (int i = 0; i < ribbon.getTaskCount() - 1; i++) {
+ RibbonTask task = ribbon.getTask(i);
+ tasksWithTrailingSeparators.add(task);
+ // System.out.println("Added " + task.getTitle());
+ }
+ // add all tasks of visible contextual groups except last task in
+ // each group
+ for (int i = 0; i < ribbon.getContextualTaskGroupCount(); i++) {
+ RibbonContextualTaskGroup group = ribbon
+ .getContextualTaskGroup(i);
+ if (ribbon.isVisible(group)) {
+ for (int j = 0; j < group.getTaskCount() - 1; j++) {
+ RibbonTask task = group.getTask(j);
+ tasksWithTrailingSeparators.add(task);
+ }
+ }
+ }
+
+ for (RibbonTask taskWithTrailingSeparator : tasksWithTrailingSeparators) {
+ JRibbonTaskToggleButton taskToggleButton = taskToggleButtons
+ .get(taskWithTrailingSeparator);
+ Rectangle bounds = taskToggleButton.getBounds();
+ int x = bounds.x + bounds.width + getTabButtonGap() / 2 - 1;
+ g2d.translate(x, 0);
+ SeparatorPainterUtils.paintSeparator(ribbon, g2d, scheme, 2,
+ getHeight(), SwingConstants.VERTICAL, false,
+ getHeight() / 3, 0, true);
+ g2d.translate(-x, 0);
+ }
+
+ g2d.dispose();
+ }
+ }
+
+ /**
+ * Panel for hosting ribbon bands.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class SubstanceBandHostPanel extends BandHostPanel {
+ @Override
+ protected void paintComponent(Graphics g) {
+ int dy = 0;
+ for (int i = 0; i < getComponentCount(); i++) {
+ Component child = getComponent(i);
+ if (child instanceof AbstractRibbonBand) {
+ dy = child.getBounds().y;
+ break;
+ }
+ }
+ SubstanceRibbonBandUI.paintRibbonBandBackground(g, this, 0.0f, dy);
+ }
+ }
+
+ protected class SubstanceRibbonLayout extends BasicRibbonUI.RibbonLayout {
+
+ @Override
+ public void layoutContainer(Container c) {
+ super.layoutContainer(c);
+
+ tabPanelHeaderBackground.setBounds(
+ 0, taskToggleButtonsScrollablePanel.getY(),
+ ribbon.getWidth(), taskToggleButtonsScrollablePanel.getHeight()
+ );
+ ribbon.setComponentZOrder(tabPanelHeaderBackground, ribbon.getComponentCount() - 1);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceRibbonUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasicRibbonUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ SubstanceLookAndFeel.setDecorationType(this.ribbon,
+ DecorationAreaType.HEADER);
+ Color backgr = this.ribbon.getBackground();
+ if (backgr == null || backgr instanceof UIResource) {
+ Color toSet = SubstanceColorSchemeUtilities.getColorScheme(
+ this.ribbon, ComponentState.ENABLED)
+ .getBackgroundFillColor();
+ this.ribbon.setBackground(new ColorUIResource(toSet));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasicRibbonUI#uninstallDefaults()
+ */
+ @Override
+ protected void uninstallDefaults() {
+ DecorationPainterUtils.clearDecorationType(this.ribbon);
+ super.uninstallDefaults();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasicRibbonUI#installComponents()
+ */
+ @Override
+ protected void installComponents() {
+ super.installComponents();
+
+ tabPanelHeaderBackground = new JPanel();
+ ribbon.add(tabPanelHeaderBackground);
+ SubstanceLookAndFeel.setDecorationType(this.tabPanelHeaderBackground,
+ DecorationAreaType.HEADER);
+
+ SubstanceLookAndFeel.setDecorationType(this.taskBarPanel,
+ DecorationAreaType.PRIMARY_TITLE_PANE);
+ SubstanceLookAndFeel.setDecorationType(this.ribbon,
+ DecorationAreaType.HEADER);
+ SubstanceLookAndFeel.setDecorationType(this.bandScrollablePanel,
+ DecorationAreaType.GENERAL);
+ }
+
+ @Override
+ protected void uninstallComponents() {
+ this.ribbon.remove(tabPanelHeaderBackground);
+ DecorationPainterUtils.clearDecorationType(this.taskBarPanel);
+ super.uninstallComponents();
+ }
+
+ @Override
+ protected LayoutManager createLayoutManager() {
+ return new SubstanceRibbonLayout();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonUI#createTaskToggleButtonsHostPanel
+ * ()
+ */
+ @Override
+ protected TaskToggleButtonsHostPanel createTaskToggleButtonsHostPanel() {
+ return new SubstanceTaskToggleButtonsHostPanel();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasicRibbonUI#createBandHostPanel()
+ */
+ @Override
+ protected BandHostPanel createBandHostPanel() {
+ return new SubstanceBandHostPanel();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonUI#paintBackground(java.awt.Graphics
+ * )
+ */
+ @Override
+ protected void paintBackground(Graphics g) {
+ BackgroundPaintingUtils.update(g, this.ribbon, false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonUI#paintTaskArea(java.awt.Graphics
+ * , int, int, int, int)
+ */
+ @Override
+ protected void paintTaskArea(Graphics g, int x, int y, int width, int height) {
+ if (this.ribbon.getTaskCount() == 0)
+ return;
+
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ RibbonTask selectedTask = this.ribbon.getSelectedTask();
+ JRibbonTaskToggleButton selectedTaskButton = this.taskToggleButtons
+ .get(selectedTask);
+ Rectangle selectedTaskButtonBounds = selectedTaskButton.getBounds();
+ Point converted = SwingUtilities.convertPoint(selectedTaskButton
+ .getParent(), selectedTaskButtonBounds.getLocation(),
+ this.ribbon);
+ float radius = SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(this.ribbon));
+
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.ribbon)) / 2.0);
+
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(this.ribbon);
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.ribbon));
+
+ AbstractRibbonBand band = (selectedTask.getBandCount() == 0) ? null
+ : selectedTask.getBand(0);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(band, ColorSchemeAssociationKind.BORDER,
+ ComponentState.ENABLED);
+
+ Rectangle taskToggleButtonsViewportBounds = taskToggleButtonsScrollablePanel
+ .getView().getParent().getBounds();
+ taskToggleButtonsViewportBounds.setLocation(SwingUtilities
+ .convertPoint(taskToggleButtonsScrollablePanel,
+ taskToggleButtonsViewportBounds.getLocation(),
+ this.ribbon));
+ int startSelectedX = Math.max(converted.x + 1,
+ (int) taskToggleButtonsViewportBounds.getMinX());
+ startSelectedX = Math.min(startSelectedX,
+ (int) taskToggleButtonsViewportBounds.getMaxX());
+ int endSelectedX = Math.min(converted.x
+ + selectedTaskButtonBounds.width - 1,
+ (int) taskToggleButtonsViewportBounds.getMaxX());
+ endSelectedX = Math.max(endSelectedX,
+ (int) taskToggleButtonsViewportBounds.getMinX());
+
+ Shape outerContour = RibbonBorderShaper.getRibbonBorderOutline(
+ this.ribbon, x + borderDelta, x + width - 2 * borderDelta - 1,
+ startSelectedX + borderDelta, endSelectedX - 2 * borderDelta,
+ converted.y + borderDelta, y + borderDelta, y + height - 2
+ * borderDelta, radius);
+
+ Shape innerContour = RibbonBorderShaper.getRibbonBorderOutline(
+ this.ribbon, x + borderDelta + borderThickness, x + width - 2
+ * (borderDelta + borderThickness) - 1, startSelectedX
+ + borderDelta + borderThickness, endSelectedX - 2
+ * (borderDelta + borderThickness), converted.y
+ + borderDelta + borderThickness, y + borderDelta
+ + borderThickness, y + height - 2
+ * (borderDelta + borderThickness) + 1, radius);
+
+ g2d.setColor(SubstanceColorSchemeUtilities.getColorScheme(band,
+ ComponentState.ENABLED).getBackgroundFillColor());
+ g2d.clipRect(x, y, width, height + 2);
+ g2d.fill(outerContour);
+
+ borderPainter.paintBorder(g2d, this.ribbon, width, height
+ + selectedTaskButtonBounds.height + 1, outerContour,
+ innerContour, borderScheme);
+
+ // check whether the currently selected task is a contextual task
+ RibbonTask selected = selectedTask;
+ RibbonContextualTaskGroup contextualGroup = selected
+ .getContextualGroup();
+ if (contextualGroup != null) {
+ // paint a small gradient directly below the task area
+ Insets ins = this.ribbon.getInsets();
+ int topY = ins.top + getTaskbarHeight();
+ int bottomY = topY + 5;
+ Color hueColor = contextualGroup.getHueColor();
+ Paint paint = new GradientPaint(0, topY, FlamingoUtilities
+ .getAlphaColor(hueColor,
+ (int) (255 * RibbonContextualTaskGroup.HUE_ALPHA)),
+ 0, bottomY, FlamingoUtilities.getAlphaColor(hueColor, 0));
+ g2d.setPaint(paint);
+ g2d.clip(outerContour);
+ g2d.fillRect(0, topY, width, bottomY - topY + 1);
+ }
+
+ // paint outlines of the contextual task groups
+ // paintContextualTaskGroupsOutlines(g);
+
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.ribbon.ui.BasicRibbonUI#getTabButtonGap()
+ */
+ @Override
+ protected int getTabButtonGap() {
+ return SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
+ .getComponentFontSize(this.ribbon), super.getTabButtonGap(), 3,
+ 1, false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonUI#syncApplicationMenuRichTooltip
+ * ()
+ */
+ @Override
+ protected void syncApplicationMenuTips() {
+ JRibbonRootPane ribbonRootPane = (JRibbonRootPane) SwingUtilities
+ .getRootPane(this.ribbon);
+ if (ribbonRootPane == null)
+ return;
+ SubstanceRibbonRootPaneUI ribbonRootPaneUI = (SubstanceRibbonRootPaneUI) ribbonRootPane
+ .getUI();
+ ribbonRootPaneUI.syncApplicationMenuTips();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.flamingo.ribbon.ui.BasicRibbonUI#paintMinimizedRibbonSeparator
+ * (java.awt.Graphics)
+ */
+ @Override
+ protected void paintMinimizedRibbonSeparator(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.translate(0, this.ribbon.getHeight() - 1);
+ SeparatorPainterUtils.paintSeparator(this.ribbon, g2d, this.ribbon
+ .getWidth(), 0, JSeparator.HORIZONTAL, false, 0);
+ g2d.dispose();
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRichTooltipPanelUI.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRichTooltipPanelUI.java
new file mode 100644
index 0000000..ae88674
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/ribbon/ui/SubstanceRichTooltipPanelUI.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.ribbon.ui;
+
+import java.awt.*;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.flamingo.internal.ui.common.BasicRichTooltipPanelUI;
+import org.pushingpixels.flamingo.internal.ui.common.JRichTooltipPanel;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+
+/**
+ * UI for {@link JRichTooltipPanel} components in <b>Substance</b> look and
+ * feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRichTooltipPanelUI extends BasicRichTooltipPanelUI {
+ public static ComponentUI createUI(JComponent c) {
+ return new SubstanceRichTooltipPanelUI();
+ }
+
+ @Override
+ protected void paintBackground(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.richTooltipPanel, ComponentState.ENABLED);
+ g2d.setPaint(new GradientPaint(0, 0, colorScheme.getExtraLightColor(),
+ 0, this.richTooltipPanel.getHeight(), colorScheme
+ .getLightColor()));
+ g2d.fillRect(0, 0, this.richTooltipPanel.getWidth(),
+ this.richTooltipPanel.getHeight());
+
+ g2d.dispose();
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/utils/CommandButtonBackgroundDelegate.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/utils/CommandButtonBackgroundDelegate.java
new file mode 100644
index 0000000..70a406e
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/utils/CommandButtonBackgroundDelegate.java
@@ -0,0 +1,511 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.utils;
+
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.*;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.common.JCommandButtonStrip;
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton.CommandButtonLocationOrderKind;
+import org.pushingpixels.flamingo.api.common.JCommandButtonStrip.StripOrientation;
+import org.pushingpixels.flamingo.api.common.model.PopupButtonModel;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.flamingo.common.ui.ActionPopupTransitionAwareUI;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAware;
+
+/**
+ * Delegate class for painting backgrounds of buttons in <b>Substance </b> look
+ * and feel. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class CommandButtonBackgroundDelegate {
+ /**
+ * Cache for background images. Each time
+ * {@link #getFullAlphaBackground(org.pushingpixels.flamingo.api.common.AbstractCommandButton, javax.swing.ButtonModel, org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter, org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter, int, int, org.pushingpixels.substance.internal.animation.StateTransitionTracker, boolean)}
+ * is called, it checks <code>this</code> map to see if it already contains
+ * such background. If so, the background from the map is returned.
+ */
+ private static LazyResettableHashMap<BufferedImage> imageCache = new LazyResettableHashMap<BufferedImage>(
+ "Substance.Flamingo.CommandButtonBackgroundDelegate");
+
+ /**
+ * Retrieves the background for the specified button.
+ *
+ * @param commandButton
+ * Button.
+ * @param fillPainter
+ * Button fill painter.
+ * @param borderPainter
+ * Button border painter.
+ * @param width
+ * Button width.
+ * @param height
+ * Button height.
+ * @return Button background.
+ */
+ public static BufferedImage getFullAlphaBackground(
+ AbstractCommandButton commandButton, ButtonModel buttonModel,
+ SubstanceFillPainter fillPainter,
+ SubstanceBorderPainter borderPainter, int width, int height,
+ StateTransitionTracker stateTransitionTracker,
+ boolean ignoreSelections) {
+ StateTransitionTracker.ModelStateInfo modelStateInfo = (stateTransitionTracker == null) ? null
+ : stateTransitionTracker.getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = (modelStateInfo == null) ? null
+ : (ignoreSelections ? modelStateInfo
+ .getStateNoSelectionContributionMap() : modelStateInfo
+ .getStateContributionMap());
+ ComponentState currState = (modelStateInfo == null) ? ComponentState
+ .getState(buttonModel, commandButton)
+ : (ignoreSelections ? modelStateInfo
+ .getCurrModelStateNoSelection() : modelStateInfo
+ .getCurrModelState());
+
+ SubstanceColorScheme baseFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(commandButton, currState);
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(commandButton,
+ ColorSchemeAssociationKind.BORDER, currState);
+
+ float radius = SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(commandButton));
+
+ Set<SubstanceConstants.Side> straightSides = SubstanceCoreUtilities
+ .getSides(commandButton,
+ SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY);
+
+ // special handling for location order
+ AbstractCommandButton.CommandButtonLocationOrderKind locationOrderKind = commandButton
+ .getLocationOrderKind();
+ int dx = 0;
+ int dy = 0;
+ int dw = 0;
+ int dh = 0;
+ boolean isVertical = false;
+ if ((locationOrderKind != null)
+ && (locationOrderKind != AbstractCommandButton.CommandButtonLocationOrderKind.ONLY)) {
+ Component parent = commandButton.getParent();
+ if ((parent instanceof JCommandButtonStrip)
+ && (((JCommandButtonStrip) parent).getOrientation() == StripOrientation.VERTICAL)) {
+ isVertical = true;
+ switch (locationOrderKind) {
+ case FIRST:
+ dh = commandButton.getHeight() / 2;
+ break;
+ case MIDDLE:
+ dy = -commandButton.getHeight() / 2;
+ dh = commandButton.getHeight();
+ break;
+ case LAST:
+ dy = -commandButton.getHeight() / 2;
+ dh = commandButton.getHeight() / 2;
+ }
+ } else {
+ boolean ltr = commandButton.getComponentOrientation()
+ .isLeftToRight();
+ if (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.MIDDLE) {
+ dx = -commandButton.getWidth() / 2;
+ dw = commandButton.getWidth();
+ } else {
+ boolean curveOnLeft = (ltr && (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.FIRST))
+ || (!ltr && (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.LAST));
+ if (curveOnLeft) {
+ dw = commandButton.getWidth() / 2;
+ } else {
+ dx = -commandButton.getWidth() / 2;
+ dw = commandButton.getWidth() / 2;
+ }
+ }
+ }
+ }
+
+ HashMapKey baseKey = SubstanceCoreUtilities.getHashKey(currState,
+ width, height, baseFillScheme.getDisplayName(),
+ baseBorderScheme.getDisplayName(),
+ fillPainter.getDisplayName(), borderPainter.getDisplayName(),
+ commandButton.getClass().getName(), radius, straightSides,
+ SubstanceSizeUtils.getComponentFontSize(commandButton),
+ locationOrderKind, dx, dy, dw, dh, isVertical);
+
+ BufferedImage baseLayer = imageCache.get(baseKey);
+ if (baseLayer == null) {
+ baseLayer = getSingleLayer(commandButton, fillPainter,
+ borderPainter, width, height, baseFillScheme,
+ baseBorderScheme, radius, straightSides, locationOrderKind,
+ dx, dy, dw, dh, isVertical);
+
+ imageCache.put(baseKey, baseLayer);
+ }
+
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ return baseLayer;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D g2d = result.createGraphics();
+
+ g2d.drawImage(baseLayer, 0, 0, null);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(commandButton, activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(commandButton,
+ ColorSchemeAssociationKind.BORDER, activeState);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(activeState,
+ width, height, fillScheme.getDisplayName(), borderScheme
+ .getDisplayName(), fillPainter.getDisplayName(),
+ borderPainter.getDisplayName(), commandButton.getClass()
+ .getName(), radius, straightSides,
+ SubstanceSizeUtils.getComponentFontSize(commandButton),
+ locationOrderKind, dx, dy, dw, dh, isVertical);
+
+ BufferedImage layer = imageCache.get(key);
+ if (layer == null) {
+ layer = getSingleLayer(commandButton, fillPainter,
+ borderPainter, width, height, fillScheme, borderScheme,
+ radius, straightSides, locationOrderKind, dx, dy, dw,
+ dh, isVertical);
+
+ imageCache.put(key, layer);
+ }
+
+ g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
+ g2d.drawImage(layer, 0, 0, null);
+ }
+
+ g2d.dispose();
+ return result;
+ }
+
+ private static BufferedImage getSingleLayer(
+ AbstractCommandButton commandButton,
+ SubstanceFillPainter fillPainter,
+ SubstanceBorderPainter borderPainter,
+ int width,
+ int height,
+ SubstanceColorScheme fillScheme,
+ SubstanceColorScheme borderScheme,
+ float radius,
+ Set<SubstanceConstants.Side> straightSides,
+ AbstractCommandButton.CommandButtonLocationOrderKind locationOrderKind,
+ int dx, int dy, int dw, int dh, boolean isVertical) {
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(commandButton)) / 2.0);
+
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(width + dw,
+ height + dh, radius, straightSides, borderDelta);
+ BufferedImage newBackground = SubstanceCoreUtilities.getBlankImage(
+ width, height);
+ Graphics2D finalGraphics = (Graphics2D) newBackground.getGraphics();
+ finalGraphics.translate(dx, dy);
+ fillPainter.paintContourBackground(finalGraphics, commandButton, width
+ + dw, height + dh, contour, false, fillScheme, true);
+
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(commandButton));
+ Shape contourInner = SubstanceOutlineUtilities.getBaseOutline(width
+ + dw, height + dh, radius, straightSides, borderDelta
+ + borderThickness);
+ borderPainter.paintBorder(finalGraphics, commandButton, width + dw,
+ height + dh, contour, contourInner, borderScheme);
+
+ if (isVertical) {
+ if ((locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.FIRST)
+ || (locationOrderKind == CommandButtonLocationOrderKind.MIDDLE)) {
+ // outer/inner line at the bottom
+ int y = -dy + commandButton.getHeight() - borderDelta - 2
+ * borderThickness;
+ int xs = borderDelta;
+ int xe = commandButton.getWidth() - 2 * borderDelta;
+ Shape upper = new Line2D.Double(xs + borderThickness, y, xe - 2
+ * borderThickness, y);
+ y += borderThickness;
+ Shape lower = new Line2D.Double(xs, y, xe, y);
+ borderPainter.paintBorder(finalGraphics, commandButton, width
+ + dw, height + dh, lower, upper, borderScheme);
+ }
+
+ // special case for MIDDLE and LAST location order kinds
+ if ((locationOrderKind == CommandButtonLocationOrderKind.MIDDLE)
+ || (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.LAST)) {
+ // inner line at the top
+ int y = -dy + borderDelta;
+ int xs = borderDelta;
+ int xe = commandButton.getWidth() - 2 * borderDelta;
+ Shape upper = new Line2D.Double(xs + borderThickness, y, xe - 2
+ * borderThickness, y);
+ borderPainter.paintBorder(finalGraphics, commandButton, width
+ + dw, height + dh, null, upper, borderScheme);
+ }
+ } else {
+ // special case for leftmost (not FIRST!!!) and MIDDLE location
+ // order
+ // kinds
+ boolean ltr = commandButton.getComponentOrientation()
+ .isLeftToRight();
+ boolean leftmost = (ltr && (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.FIRST))
+ || (!ltr && (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.LAST));
+ if (leftmost
+ || (locationOrderKind == CommandButtonLocationOrderKind.MIDDLE)) {
+ // outer / inner line at the right
+ int x = -dx + commandButton.getWidth() - borderDelta - 2
+ * borderThickness;
+ int ys = borderDelta;
+ int ye = commandButton.getHeight() - 2 * borderDelta;
+ Shape upper = new Line2D.Double(x, ys + borderThickness, x, ye
+ - 2 * borderThickness);
+ x += borderThickness;
+ Shape lower = new Line2D.Double(x, ys, x, ye);
+ borderPainter.paintBorder(finalGraphics, commandButton, width
+ + dw, height + dh, lower, upper, borderScheme);
+ }
+
+ // special case for MIDDLE and LAST location order kinds
+ boolean rightmost = (ltr && (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.LAST))
+ || (!ltr && (locationOrderKind == AbstractCommandButton.CommandButtonLocationOrderKind.FIRST));
+ if ((locationOrderKind == CommandButtonLocationOrderKind.MIDDLE)
+ || rightmost) {
+ // inner line at the left
+ int x = -dx + borderDelta;
+ int ys = borderDelta;
+ int ye = commandButton.getHeight() - 2 * borderDelta;
+ Shape upper = new Line2D.Double(x, ys + borderThickness, x, ye
+ - 2 * borderThickness);
+ borderPainter.paintBorder(finalGraphics, commandButton, width
+ + dw, height + dh, null, upper, borderScheme);
+ }
+ }
+ return newBackground;
+ }
+
+ public static BufferedImage getCombinedCommandButtonBackground(
+ AbstractCommandButton commandButton, ButtonModel actionModel,
+ Rectangle actionArea, PopupButtonModel popupModel,
+ Rectangle popupArea) {
+ ButtonModel backgroundModel = new DefaultButtonModel();
+ backgroundModel.setEnabled(actionModel.isEnabled()
+ && popupModel.isEnabled());
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(commandButton);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(commandButton);
+
+ // layer number one - background with the combined enabled
+ // state of both models. Full opacity
+ // System.out.println("Background layer");
+ BufferedImage fullAlphaBackground = CommandButtonBackgroundDelegate
+ .getFullAlphaBackground(commandButton, backgroundModel,
+ fillPainter, borderPainter, commandButton.getWidth(),
+ commandButton.getHeight(), null, false);
+
+ BufferedImage layers = SubstanceCoreUtilities
+ .getBlankImage(fullAlphaBackground.getWidth(),
+ fullAlphaBackground.getHeight());
+ Graphics2D combinedGraphics = layers.createGraphics();
+ combinedGraphics.drawImage(fullAlphaBackground, 0, 0, null);
+
+ ActionPopupTransitionAwareUI ui = (ActionPopupTransitionAwareUI) commandButton
+ .getUI();
+
+ if (actionModel.isEnabled() && popupModel.isEnabled()) {
+ // layer number two - background with the combined rollover state
+ // of both models. Opacity 60%.
+ backgroundModel.setRollover(actionModel.isRollover()
+ || popupModel.isRollover() || popupModel.isPopupShowing());
+ // System.out.println(actionModel.isRollover() + ":"
+ // + popupModel.isRollover());
+ combinedGraphics.setComposite(AlphaComposite.SrcOver.derive(0.6f));
+ // System.out.println("Rollover layer");
+ BufferedImage rolloverBackground = CommandButtonBackgroundDelegate
+ .getFullAlphaBackground(commandButton, backgroundModel,
+ fillPainter, borderPainter, commandButton
+ .getWidth(), commandButton.getHeight(), ui
+ .getTransitionTracker(), false);
+ combinedGraphics.drawImage(rolloverBackground, 0, 0, null);
+ }
+
+ // Shape currClip = combinedGraphics.getClip();
+ if ((actionArea != null) && !actionArea.isEmpty()) {
+ // layer number three - action area with its model. Opacity 40%
+ // for enabled popup area, 100% for disabled popup area
+ Graphics2D graphicsAction = (Graphics2D) combinedGraphics.create();
+ // System.out.println(actionArea);
+ graphicsAction.clip(actionArea);
+ // System.out.println(graphicsAction.getClipBounds());
+ float actionAlpha = 0.4f;
+ if ((popupModel != null) && !popupModel.isEnabled())
+ actionAlpha = 1.0f;
+ if (!actionModel.isEnabled())
+ actionAlpha = 0.0f;
+ graphicsAction.setComposite(AlphaComposite.SrcOver
+ .derive(actionAlpha));
+ BufferedImage actionAreaBackground = CommandButtonBackgroundDelegate
+ .getFullAlphaBackground(commandButton, null, fillPainter,
+ borderPainter, commandButton.getWidth(),
+ commandButton.getHeight(), ui
+ .getActionTransitionTracker(), false);
+ graphicsAction.drawImage(actionAreaBackground, 0, 0, null);
+ // graphicsAction.setColor(Color.red);
+ // graphicsAction.fill(toFill);
+ graphicsAction.dispose();
+ }
+ // combinedGraphics.setClip(currClip);
+ if ((popupArea != null) && !popupArea.isEmpty()) {
+ // layer number four - popup area with its model. Opacity 40%
+ // for enabled action area, 100% for disabled action area
+ Graphics2D graphicsPopup = (Graphics2D) combinedGraphics.create();
+ // System.out.println(popupArea);
+ graphicsPopup.clip(popupArea);
+ // System.out.println(graphicsPopup.getClipBounds());
+ float popupAlpha = 0.4f;
+ if (!actionModel.isEnabled())
+ popupAlpha = 1.0f;
+ if ((popupModel != null) && !popupModel.isEnabled())
+ popupAlpha = 0.0f;
+ graphicsPopup.setComposite(AlphaComposite.SrcOver
+ .derive(popupAlpha));
+ // System.out.println(popupAlpha + ":"
+ // + ComponentState.getState(popupModel, this.commandButton));
+ BufferedImage popupAreaBackground = CommandButtonBackgroundDelegate
+ .getFullAlphaBackground(commandButton, null, fillPainter,
+ borderPainter, commandButton.getWidth(),
+ commandButton.getHeight(), ui
+ .getPopupTransitionTracker(), false);
+ graphicsPopup.drawImage(popupAreaBackground, 0, 0, null);
+ // graphicsPopup.setColor(Color.blue);
+ // graphicsPopup.fill(toFill);
+ graphicsPopup.dispose();
+ }
+ combinedGraphics.dispose();
+ // System.out.println(imageCache.size());
+ return layers;
+ }
+
+ /**
+ * Returns the memory usage string.
+ *
+ * @return Memory usage string.
+ */
+ static String getMemoryUsage() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("SubstanceBackgroundDelegate: \n");
+ sb.append("\t" + imageCache.size() + " regular");
+ // + pairwiseBackgrounds.size() + " pairwise");
+ return sb.toString();
+ }
+
+ public static void paintThemedCommandButtonIcon(Graphics2D g,
+ Rectangle iconRect, AbstractCommandButton commandButton,
+ Icon regular, ButtonModel model,
+ StateTransitionTracker stateTransitionTracker) {
+ Icon themed = SubstanceCoreUtilities.getThemedIcon(commandButton,
+ regular);
+
+ boolean useRegularVersion = model.isArmed()
+ || model.isPressed()
+ || model.isSelected()
+ || regular.getClass()
+ .isAnnotationPresent(TransitionAware.class);
+ Graphics2D g2d = (Graphics2D) g.create();
+ if (useRegularVersion) {
+ regular.paintIcon(commandButton, g2d, iconRect.x, iconRect.y);
+ } else {
+ if (stateTransitionTracker != null) {
+ float alpha = stateTransitionTracker.getActiveStrength();
+ if (alpha < 1.0f) {
+ // paint the themed image full opaque on a separate image
+ BufferedImage themedImage = FlamingoUtilities
+ .getBlankImage(themed.getIconWidth(), themed
+ .getIconHeight());
+ themed.paintIcon(commandButton, themedImage
+ .createGraphics(), 0, 0);
+ // and paint that image translucently
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ commandButton, 1.0f - alpha, g));
+ g2d.drawImage(themedImage, iconRect.x, iconRect.y, null);
+ }
+
+ if (alpha > 0.0f) {
+ // paint the regular image full opaque on a separate image
+ BufferedImage regularImage = FlamingoUtilities
+ .getBlankImage(regular.getIconWidth(), regular
+ .getIconHeight());
+ regular.paintIcon(commandButton, regularImage
+ .createGraphics(), 0, 0);
+ // and paint that image translucently
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ commandButton, alpha, g));
+ g2d.drawImage(regularImage, iconRect.x, iconRect.y, null);
+ }
+ } else {
+ if (model.isRollover()) {
+ regular.paintIcon(commandButton, g2d, iconRect.x,
+ iconRect.y);
+ } else {
+ themed
+ .paintIcon(commandButton, g2d, iconRect.x,
+ iconRect.y);
+ }
+ }
+ }
+ g2d.dispose();
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/utils/CommandButtonVisualStateTracker.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/utils/CommandButtonVisualStateTracker.java
new file mode 100644
index 0000000..13a7914
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/utils/CommandButtonVisualStateTracker.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.utils;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
+import org.pushingpixels.flamingo.api.common.JCommandButton;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+
+public class CommandButtonVisualStateTracker {
+ /**
+ * Property change listener.
+ */
+ protected PropertyChangeListener substancePropertyListener;
+
+ /**
+ * Listener for transition animations on the action area.
+ */
+ protected StateTransitionTracker actionStateTransitionTracker;
+
+ /**
+ * Listener for transition animations on the popup area.
+ */
+ protected StateTransitionTracker popupStateTransitionTracker;
+
+ public void installListeners(final AbstractCommandButton b) {
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("actionModel".equals(evt.getPropertyName())) {
+ // action model has been changed
+ actionStateTransitionTracker.setModel(b.getActionModel());
+ }
+ if ("popupModel".equals(evt.getPropertyName())) {
+ JCommandButton jcb = (JCommandButton) b;
+ // popup model has been changed
+ popupStateTransitionTracker.setModel(jcb.getPopupModel());
+ }
+ }
+ };
+ b.addPropertyChangeListener(this.substancePropertyListener);
+
+ this.actionStateTransitionTracker = new StateTransitionTracker(b, b
+ .getActionModel());
+ this.actionStateTransitionTracker.registerModelListeners();
+
+ if (b instanceof JCommandButton) {
+ JCommandButton jcb = (JCommandButton) b;
+ this.popupStateTransitionTracker = new StateTransitionTracker(jcb,
+ jcb.getPopupModel());
+ this.popupStateTransitionTracker.registerModelListeners();
+ }
+ }
+
+ public void uninstallListeners(AbstractCommandButton b) {
+ b.removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+
+ this.actionStateTransitionTracker.unregisterModelListeners();
+ this.actionStateTransitionTracker = null;
+
+ if (this.popupStateTransitionTracker != null) {
+ this.popupStateTransitionTracker.unregisterModelListeners();
+ this.popupStateTransitionTracker = null;
+ }
+ }
+
+ public StateTransitionTracker getActionStateTransitionTracker() {
+ return actionStateTransitionTracker;
+ }
+
+ public StateTransitionTracker getPopupStateTransitionTracker() {
+ return popupStateTransitionTracker;
+ }
+
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/utils/RibbonApplicationMenuButtonBackgroundDelegate.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/utils/RibbonApplicationMenuButtonBackgroundDelegate.java
new file mode 100644
index 0000000..5ea8b86
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/utils/RibbonApplicationMenuButtonBackgroundDelegate.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.utils;
+
+import java.awt.*;
+import java.awt.geom.Ellipse2D;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+import javax.swing.*;
+
+import org.pushingpixels.flamingo.api.ribbon.JRibbon;
+import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuButton;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.flamingo.common.ui.ActionPopupTransitionAwareUI;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Delegate class for painting backgrounds of buttons in <b>Substance </b> look
+ * and feel. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RibbonApplicationMenuButtonBackgroundDelegate {
+ /**
+ * Cache for background images. Each time
+ * {@link #getFullAlphaBackground(org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuButton, org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter, org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter, int, int)}
+ * is called, it checks <code>this</code> map to see if it already contains
+ * such background. If so, the background from the map is returned.
+ */
+ private static LazyResettableHashMap<BufferedImage> imageCache = new LazyResettableHashMap<BufferedImage>(
+ "Substance.Flamingo.RibbonApplicationMenuButtonBackgroundDelegate");
+
+ /**
+ * Retrieves the background for the specified button.
+ *
+ * @param menuButton
+ * Button.
+ * @param painter
+ * Button gradient painter.
+ * @param borderPainter
+ * Button border painter.
+ * @param width
+ * Button width.
+ * @param height
+ * Button height.
+ * @return Button background.
+ */
+ public static BufferedImage getFullAlphaBackground(
+ JRibbonApplicationMenuButton menuButton,
+ SubstanceFillPainter painter, SubstanceBorderPainter borderPainter,
+ int width, int height) {
+
+ JRibbon ribbon = menuButton.getRibbon();
+
+ ButtonModel model = new DefaultButtonModel();
+ model.setEnabled(true);
+ model.setSelected(menuButton.getPopupModel().isSelected());
+ boolean popupShowing = menuButton.getPopupModel().isPopupShowing();
+ model.setRollover(menuButton.getPopupModel().isRollover()
+ && !popupShowing);
+ model
+ .setPressed(menuButton.getPopupModel().isPressed()
+ || popupShowing);
+ model.setArmed(menuButton.getActionModel().isArmed() || popupShowing);
+
+ ActionPopupTransitionAwareUI ui = (ActionPopupTransitionAwareUI) menuButton
+ .getUI();
+ StateTransitionTracker popupStateTransitionTracker = ui
+ .getPopupTransitionTracker();
+ StateTransitionTracker.ModelStateInfo modelStateInfo = popupStateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ ComponentState currState = ComponentState.getState(model, menuButton);
+
+ SubstanceColorScheme baseFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(menuButton, currState);
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(ribbon, ColorSchemeAssociationKind.BORDER,
+ currState);
+
+ HashMapKey baseKey = SubstanceCoreUtilities.getHashKey(width, height,
+ baseFillScheme.getDisplayName(), baseBorderScheme
+ .getDisplayName(), painter.getDisplayName(),
+ borderPainter.getDisplayName(), SubstanceSizeUtils
+ .getComponentFontSize(menuButton));
+
+ BufferedImage baseLayer = imageCache.get(baseKey);
+ if (baseLayer == null) {
+ baseLayer = getSingleLayer(menuButton, painter, borderPainter,
+ width, height, baseFillScheme, baseBorderScheme);
+
+ imageCache.put(baseKey, baseLayer);
+ }
+
+ if (popupShowing || currState.isDisabled()
+ || (activeStates.size() == 1)) {
+ return baseLayer;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D g2d = result.createGraphics();
+
+ g2d.drawImage(baseLayer, 0, 0, null);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(menuButton, activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(ribbon, ColorSchemeAssociationKind.BORDER,
+ activeState);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
+ fillScheme.getDisplayName(), borderScheme.getDisplayName(),
+ painter.getDisplayName(), borderPainter.getDisplayName(),
+ SubstanceSizeUtils.getComponentFontSize(menuButton));
+
+ BufferedImage layer = imageCache.get(key);
+ if (layer == null) {
+ layer = getSingleLayer(menuButton, painter, borderPainter,
+ width, height, fillScheme, borderScheme);
+
+ imageCache.put(key, layer);
+ }
+
+ g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
+ g2d.drawImage(layer, 0, 0, null);
+ }
+
+ g2d.dispose();
+ return result;
+ }
+
+ private static BufferedImage getSingleLayer(
+ JRibbonApplicationMenuButton menuButton,
+ SubstanceFillPainter painter, SubstanceBorderPainter borderPainter,
+ int width, int height, SubstanceColorScheme fillScheme,
+ SubstanceColorScheme borderScheme) {
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(menuButton)) / 2.0);
+
+ int outerRadius = Math.min(width - 2 * borderDelta - 2, height - 2
+ * borderDelta - 2);
+ int delta = (outerRadius % 2 == 1) ? 1 : 0;
+ Shape contour = new Ellipse2D.Double(borderDelta + delta, borderDelta
+ + delta, outerRadius - delta, outerRadius - delta);
+
+ BufferedImage newBackground = SubstanceCoreUtilities.getBlankImage(
+ width, height);
+ Graphics2D finalGraphics = (Graphics2D) newBackground.getGraphics();
+ painter.paintContourBackground(finalGraphics, menuButton, width,
+ height, contour, false, fillScheme, true);
+
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(menuButton));
+ int innerRadius = Math.min(width - 2 * (borderDelta + borderThickness)
+ - 2, height - 2 * (borderDelta + borderThickness) - 2);
+ delta = (innerRadius % 2 == 1) ? 1 : 0;
+ Shape contourInner = new Ellipse2D.Double(borderDelta + borderThickness
+ + delta, borderDelta + borderThickness + delta, innerRadius
+ - delta, innerRadius - delta);
+
+ borderPainter.paintBorder(finalGraphics, menuButton, width, height,
+ contour, contourInner, borderScheme);
+ return newBackground;
+ }
+}
diff --git a/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/utils/SubstanceDisabledResizableIcon.java b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/utils/SubstanceDisabledResizableIcon.java
new file mode 100644
index 0000000..0246873
--- /dev/null
+++ b/substance-flamingo/src/main/java/org/pushingpixels/substance/flamingo/utils/SubstanceDisabledResizableIcon.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2005-2010 Flamingo / Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Flamingo Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.flamingo.utils;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+import org.pushingpixels.flamingo.api.common.AsynchronousLoading;
+import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
+import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Implementation of a resizable icon of disabled controls based on the current
+ * Substance skin.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceDisabledResizableIcon implements ResizableIcon {
+ /**
+ * Image cache to speed up rendering.
+ */
+ protected LazyResettableHashMap<BufferedImage> cachedImages;
+
+ /**
+ * The main (pre-filtered) icon.
+ */
+ protected ResizableIcon delegate;
+
+ /**
+ * Creates a new filtered icon.
+ *
+ * @param delegate
+ * The main (pre-filtered) icon.
+ */
+ public SubstanceDisabledResizableIcon(ResizableIcon delegate) {
+ super();
+ this.delegate = delegate;
+ this.cachedImages = new LazyResettableHashMap<BufferedImage>(
+ "FlamingoSubstanceDisabledIcons");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return delegate.getIconHeight();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return delegate.getIconWidth();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jvnet.flamingo.common.icon.ResizableIcon#setDimension(java.awt
+ * .Dimension )
+ */
+ @Override
+ public void setDimension(Dimension newDimension) {
+ delegate.setDimension(newDimension);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ // check if loading
+ if (this.delegate instanceof AsynchronousLoading) {
+ AsynchronousLoading asyncDelegate = (AsynchronousLoading) this.delegate;
+ // if the delegate is still loading - do nothing
+ if (asyncDelegate.isLoading())
+ return;
+ }
+
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c, ComponentState.DISABLED_UNSELECTED);
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(this.getIconWidth(),
+ this.getIconHeight(), scheme.getDisplayName());
+
+ BufferedImage filtered = this.cachedImages.get(key);
+ if (filtered == null) {
+ BufferedImage offscreen = FlamingoUtilities.getBlankImage(this
+ .getIconWidth(), this.getIconHeight());
+ Graphics2D g2d = offscreen.createGraphics();
+ this.delegate.paintIcon(c, g2d, 0, 0);
+ g2d.dispose();
+ filtered = SubstanceImageCreator.getColorSchemeImage(offscreen,
+ scheme, 0.5f);
+ this.cachedImages.put(key, filtered);
+ }
+ g.drawImage(filtered, x, y, null);
+ }
+}
\ No newline at end of file
diff --git a/substance-flamingo/src/main/resources/META-INF/substance-plugin.xml b/substance-flamingo/src/main/resources/META-INF/substance-plugin.xml
new file mode 100644
index 0000000..9c12510
--- /dev/null
+++ b/substance-flamingo/src/main/resources/META-INF/substance-plugin.xml
@@ -0,0 +1,3 @@
+<laf-plugin>
+ <component-plugin-class>org.pushingpixels.substance.flamingo.FlamingoPlugin</component-plugin-class>
+</laf-plugin>
\ No newline at end of file
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/RobotMain.java b/substance-flamingo/src/tools/java/tools/docrobot/RobotMain.java
new file mode 100644
index 0000000..a6af574
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/RobotMain.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot;
+
+import java.lang.reflect.Method;
+
+import javax.swing.JFrame;
+
+/**
+ * The main method for taking screenshots for Substance documentation. Expects
+ * one parameter - fully qualified class name of a single screenshot robot which
+ * has a <code>public void run()</code> method.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RobotMain {
+ /**
+ * Runs the specified screenshot robot.
+ *
+ * @param args
+ * Should contain one string - fully qualified class name of a
+ * single screenshot robot which has a
+ * <code>public void run()</code> method.
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+
+ String mainClassName = args[0];
+ Class<?> robotClass = Class.forName(mainClassName);
+ Object robotInstance = robotClass.newInstance();
+ Method runMethod = robotClass.getMethod("run", new Class[0]);
+ runMethod.invoke(robotInstance, new Object[0]);
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/SkinRobot.java b/substance-flamingo/src/tools/java/tools/docrobot/SkinRobot.java
new file mode 100644
index 0000000..0ccd013
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/SkinRobot.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.imageio.ImageIO;
+import javax.swing.JFrame;
+
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.GuiActionRunner;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.timing.Pause;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+
+import test.substance.ribbon.NewCheckRibbon;
+
+/**
+ * The base class for taking screenshots of skins for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class SkinRobot {
+ /**
+ * The associated Substance skin.
+ */
+ protected SubstanceSkin skin;
+
+ /**
+ * The screenshot filename.
+ */
+ protected String screenshotFilename;
+
+ /**
+ * The frame instance.
+ */
+ protected NewCheckRibbon ribbonFrame;
+
+ /**
+ * Creates the new screenshot robot.
+ *
+ * @param skin
+ * Substance skin.
+ * @param screenshotFilename
+ * The screenshot filename.
+ */
+ public SkinRobot(SubstanceSkin skin, String screenshotFilename) {
+ this.skin = skin;
+ this.screenshotFilename = screenshotFilename;
+ }
+
+ /**
+ * Runs the screenshot process.
+ */
+ public void run() {
+ long start = System.currentTimeMillis();
+
+ Robot robot = BasicRobot.robotWithNewAwtHierarchy();
+
+ // set skin
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ SubstanceLookAndFeel.setSkin(skin);
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ }
+ });
+ robot.waitForIdle();
+
+ // create the frame and set the icon image
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ ribbonFrame = new NewCheckRibbon();
+ ribbonFrame.configureRibbon();
+ ribbonFrame.applyComponentOrientation(ComponentOrientation
+ .getOrientation(Locale.getDefault()));
+ Rectangle r = GraphicsEnvironment.getLocalGraphicsEnvironment()
+ .getMaximumWindowBounds();
+ ribbonFrame.setPreferredSize(new Dimension(r.width,
+ r.height / 2));
+ ribbonFrame.setMinimumSize(new Dimension(100, r.height / 3));
+ ribbonFrame.pack();
+ ribbonFrame.setLocation(r.x, r.y);
+ ribbonFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ ribbonFrame.setVisible(true);
+ }
+ });
+ robot.waitForIdle();
+
+ robot.moveMouse(new Point(0, 0));
+ robot.waitForIdle();
+
+ // wait for a second
+ Pause.pause(1000);
+
+ // make the first screenshot
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ makeScreenshot();
+ }
+ });
+ robot.waitForIdle();
+
+ // dispose the frame
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ ribbonFrame.dispose();
+ }
+ });
+ robot.waitForIdle();
+
+ long end = System.currentTimeMillis();
+ System.out.println(this.getClass().getSimpleName() + " : "
+ + (end - start) + "ms");
+ }
+
+ /**
+ * Creates the screenshot and saves it on the disk.
+ *
+ */
+ public void makeScreenshot() {
+ BufferedImage bi = new BufferedImage(ribbonFrame.getWidth(),
+ ribbonFrame.getHeight(), BufferedImage.TYPE_INT_ARGB);
+ Graphics g = bi.getGraphics();
+ ribbonFrame.paint(g);
+
+ BufferedImage finalIm = new BufferedImage(500, 200,
+ BufferedImage.TYPE_INT_ARGB);
+ finalIm.getGraphics().drawImage(bi, 0, 0, null);
+
+ try {
+ File output = new File(this.screenshotFilename + ".png");
+ output.getParentFile().mkdirs();
+ ImageIO.write(finalIm, "png", output);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Autumn.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Autumn.java
new file mode 100644
index 0000000..d4d13c8
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Autumn.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.AutumnSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link AutumnSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Autumn extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Autumn() {
+ super(
+ new AutumnSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/autumn");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Business.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Business.java
new file mode 100644
index 0000000..ccc4202
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Business.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.BusinessSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link BusinessSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Business extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Business() {
+ super(
+ new BusinessSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/business");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/BusinessBlackSteel.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/BusinessBlackSteel.java
new file mode 100644
index 0000000..ce7f8f8
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/BusinessBlackSteel.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.BusinessBlackSteelSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link BusinessBlackSteelSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BusinessBlackSteel extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public BusinessBlackSteel() {
+ super(
+ new BusinessBlackSteelSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/businessblacksteel");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/BusinessBlueSteel.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/BusinessBlueSteel.java
new file mode 100644
index 0000000..1542efd
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/BusinessBlueSteel.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.BusinessBlueSteelSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link BusinessBlueSteelSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BusinessBlueSteel extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public BusinessBlueSteel() {
+ super(
+ new BusinessBlueSteelSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/businessbluesteel");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Cerulean.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Cerulean.java
new file mode 100755
index 0000000..299d334
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Cerulean.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.CeruleanSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link org.pushingpixels.substance.api.skin.CeruleanSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Cerulean extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Cerulean() {
+ super(
+ new Cerulean(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/cerulean");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/ChallengerDeep.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/ChallengerDeep.java
new file mode 100644
index 0000000..ecffa2b
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/ChallengerDeep.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.ChallengerDeepSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link ChallengerDeepSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ChallengerDeep extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public ChallengerDeep() {
+ super(
+ new ChallengerDeepSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/challengerdeep");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Creme.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Creme.java
new file mode 100644
index 0000000..5db113d
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Creme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.CremeSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link CremeSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Creme extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Creme() {
+ super(
+ new CremeSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/creme");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/CremeCoffee.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/CremeCoffee.java
new file mode 100644
index 0000000..3ada3dd
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/CremeCoffee.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.CremeCoffeeSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link CremeCoffeeSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class CremeCoffee extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public CremeCoffee() {
+ super(
+ new CremeCoffeeSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/cremecoffee");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Dust.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Dust.java
new file mode 100644
index 0000000..f9350b8
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Dust.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.DustSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link DustSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Dust extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Dust() {
+ super(new DustSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/dust");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/DustCoffee.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/DustCoffee.java
new file mode 100644
index 0000000..bc3a5ce
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/DustCoffee.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.DustCoffeeSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link DustCoffeeSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DustCoffee extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DustCoffee() {
+ super(
+ new DustCoffeeSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/dustcoffee");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/EmeraldDusk.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/EmeraldDusk.java
new file mode 100644
index 0000000..6d9da90
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/EmeraldDusk.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.EmeraldDuskSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link EmeraldDuskSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class EmeraldDusk extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public EmeraldDusk() {
+ super(
+ new EmeraldDuskSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/emeralddusk");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Gemini.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Gemini.java
new file mode 100644
index 0000000..6525f16
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Gemini.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.GeminiSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link GeminiSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Gemini extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Gemini() {
+ super(
+ new GeminiSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/gemini");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Graphite.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Graphite.java
new file mode 100644
index 0000000..9b7b5cc
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Graphite.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.GraphiteSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link GraphiteSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Graphite extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Graphite() {
+ super(
+ new GraphiteSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/graphite");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/GraphiteAqua.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/GraphiteAqua.java
new file mode 100644
index 0000000..bb5f315
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/GraphiteAqua.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.GraphiteAquaSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link GraphiteAquaSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class GraphiteAqua extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public GraphiteAqua() {
+ super(
+ new GraphiteAquaSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/graphiteaqua");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/GraphiteGlass.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/GraphiteGlass.java
new file mode 100644
index 0000000..4ddbd4f
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/GraphiteGlass.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.GraphiteGlassSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link GraphiteGlassSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class GraphiteGlass extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public GraphiteGlass() {
+ super(
+ new GraphiteGlassSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/graphiteglass");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Magellan.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Magellan.java
new file mode 100644
index 0000000..9f07932
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Magellan.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.MagellanSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link MagellanSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Magellan extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Magellan() {
+ super(
+ new MagellanSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/magellan");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/MistAqua.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/MistAqua.java
new file mode 100644
index 0000000..57519bd
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/MistAqua.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.MistAquaSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link MistAquaSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class MistAqua extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public MistAqua() {
+ super(
+ new MistAquaSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/mistaqua");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/MistSilver.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/MistSilver.java
new file mode 100644
index 0000000..826a465
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/MistSilver.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.MistSilverSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link MistSilverSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class MistSilver extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public MistSilver() {
+ super(
+ new MistSilverSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/mistsilver");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Moderate.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Moderate.java
new file mode 100644
index 0000000..6ff424c
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Moderate.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.ModerateSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link ModerateSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Moderate extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Moderate() {
+ super(
+ new ModerateSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/moderate");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Nebula.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Nebula.java
new file mode 100644
index 0000000..0c85bef
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Nebula.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.NebulaSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link NebulaSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Nebula extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Nebula() {
+ super(
+ new NebulaSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/nebula");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/NebulaBrickWall.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/NebulaBrickWall.java
new file mode 100644
index 0000000..aab1c85
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/NebulaBrickWall.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.NebulaBrickWallSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link NebulaBrickWallSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class NebulaBrickWall extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public NebulaBrickWall() {
+ super(
+ new NebulaBrickWallSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/nebulabrickwall");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/OfficeBlack2007.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/OfficeBlack2007.java
new file mode 100644
index 0000000..2507fa9
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/OfficeBlack2007.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.OfficeBlack2007Skin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link OfficeBlack2007Skin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class OfficeBlack2007 extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public OfficeBlack2007() {
+ super(
+ new OfficeBlack2007Skin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/officeblack2007");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/OfficeBlue2007.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/OfficeBlue2007.java
new file mode 100644
index 0000000..7896e18
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/OfficeBlue2007.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.OfficeBlue2007Skin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link OfficeBlue2007Skin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class OfficeBlue2007 extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public OfficeBlue2007() {
+ super(
+ new OfficeBlue2007Skin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/officeblue2007");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/OfficeSilver2007.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/OfficeSilver2007.java
new file mode 100644
index 0000000..28e8888
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/OfficeSilver2007.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.OfficeSilver2007Skin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link OfficeSilver2007Skin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class OfficeSilver2007 extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public OfficeSilver2007() {
+ super(
+ new OfficeSilver2007Skin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/officesilver2007");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Raven.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Raven.java
new file mode 100644
index 0000000..97f506b
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Raven.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.RavenSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link RavenSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Raven extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Raven() {
+ super(
+ new RavenSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/raven");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Sahara.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Sahara.java
new file mode 100644
index 0000000..a8ed5ea
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Sahara.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.SaharaSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link SaharaSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Sahara extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Sahara() {
+ super(
+ new SaharaSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/sahara");
+ }
+}
diff --git a/substance-flamingo/src/tools/java/tools/docrobot/skins/Twilight.java b/substance-flamingo/src/tools/java/tools/docrobot/skins/Twilight.java
new file mode 100644
index 0000000..1e87ed7
--- /dev/null
+++ b/substance-flamingo/src/tools/java/tools/docrobot/skins/Twilight.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.TwilightSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link TwilightSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Twilight extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Twilight() {
+ super(
+ new TwilightSkin(),
+ "/Users/kirillg/JProjects/substance-flamingo/www/images/screenshots/skins/twilight");
+ }
+}
diff --git a/substance-swingx/build.gradle b/substance-swingx/build.gradle
new file mode 100755
index 0000000..3b7030e
--- /dev/null
+++ b/substance-swingx/build.gradle
@@ -0,0 +1,140 @@
+ext.swingXVersion = "1.6.3"
+
+configurations {
+ testCompile { extendsFrom compile }
+ toolsCompile { extendsFrom compile }
+}
+
+sourceSets {
+ main
+ test
+ tools {
+ compileClasspath = sourceSets.main.output + sourceSets.test.output + configurations.toolsCompile + configurations.testCompile
+ }
+}
+
+dependencies {
+ compile project(":substance")
+ compile group: 'org.swinglabs.swingx', name: 'swingx-core', version: swingXVersion
+ testCompile project(":substance").sourceSets.test.output
+ testCompile group: 'com.jgoodies', name: 'forms', version: '1.2.0'
+ toolsCompile project(":substance").sourceSets.tools.output
+ toolsCompile group: 'org.easytesting', name: 'fest-swing', version: '1.2.1'
+ toolsCompile group: 'asm', name: 'asm-all', version: '2.2.3'
+}
+
+task augmentation(dependsOn: classes) {
+ description = "Performs code augmentaiton for the laf-plugin and laf-widget libraries on the substance jar classes"
+
+ doLast {
+ def augmentClassPath = configurations.toolsCompile.asPath + File.pathSeparator + configurations.compile.asPath
+
+ ant.taskdef(name: 'delegate-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentTask", classpath: augmentClassPath)
+ ant.taskdef(name: 'delegate-update-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentUpdateTask", classpath: augmentClassPath)
+ ant.taskdef(name: 'laf-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentMainTask", classpath: augmentClassPath)
+ ant.taskdef(name: 'icon-ghosting-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentIconGhostingTask", classpath: augmentClassPath)
+ ant.taskdef(name: 'container-ghosting-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentContainerGhostingTask", classpath: augmentClassPath)
+
+ def verboseAugmentation = false
+
+ // Delegate augmentation
+ ant.'delegate-update-augment'(verbose: verboseAugmentation, pattern: ".*UI\u002Eclass") {
+ classpathset(dir: sourceSets.main.output.classesDir)
+ }
+
+ ant.'delegate-augment'(verbose: verboseAugmentation, pattern: ".*UI\u002Eclass") {
+ classpathset(dir: sourceSets.main.output.classesDir)
+ }
+
+ // Container ghosting augmentation
+ ant.'container-ghosting-augment'(verbose: verboseAugmentation) {
+ classpathset(dir: sourceSets.main.output.classesDir)
+ containerghosting(className: "org.pushingpixels.substance.swingx.SubstanceStatusBarUI", toInjectAfterOriginal: "true")
+ containerghosting(className: "org.pushingpixels.substance.swingx.SubstanceTaskPaneContainerUI", toInjectAfterOriginal: "true")
+ }
+ }
+}
+
+jar {
+ dependsOn augmentation
+
+ manifest {
+ attributes(
+ "Substance-Version": version,
+ "SwingX-Version": swingXVersion,
+ "Substance-VersionName": versionKey,
+ )
+ }
+
+}
+
+task testJar(type: Jar) {
+ classifier = 'tst'
+
+ from sourceSets.test.output
+
+ manifest {
+ attributes(
+ "Substance-Version": version,
+ "Flamingo-Version": version,
+ "Substance-VersionName": versionKey,
+ "Flamingo-VersionName": versionKey,
+ )
+ }
+}
+
+task distroJar(type: Jar) {
+ //dependsOn toolsJar, testJar, jar
+ dependsOn jar
+ classifier = 'all'
+ from(projectDir)
+ include "lib/**"
+ include "src/**"
+ include "www/**/*.html"
+ include "www/docs/**"
+ include "www/images/**"
+ include "gradle.properties"
+ include "build.gradle"
+}
+
+
+uploadPublished {
+ repositories {
+ mavenDeployer {
+ configurePOM(pom)
+ }
+ }
+}
+
+install {
+ configurePOM(repositories.mavenInstaller.pom)
+}
+
+private def configurePOM(def pom) {
+ configureBasePom(pom)
+ pom.project {
+ name "substance-swingx"
+ description "A fork of @kirilcool's substance project"
+ url "http://insubstantial.github.com/insubstantial/substance-swingx/"
+ licenses {
+ license {
+ name 'BSD License'
+ url 'http://www.opensource.org/licenses/bsd-license.php'
+ distribution 'repo'
+ comments "Does not cover the Xoetrope Color Wheel"
+ }
+ license {
+ name 'Mozilla Public License 1.1'
+ url 'http://www.opensource.org/licenses/mozilla1.1'
+ distribution 'repo'
+ comments "Covers the Xoetrope Color Wheel"
+ }
+ }
+ }
+}
+
+task testCheckX(type: JavaExec) {
+ main = 'test.Check'
+ debug = Boolean.valueOf(System.getProperty('debug', 'false'))
+ classpath = sourceSets.test.runtimeClasspath
+}
\ No newline at end of file
diff --git a/substance-swingx/run doc robot.bat b/substance-swingx/run doc robot.bat
new file mode 100644
index 0000000..815507e
--- /dev/null
+++ b/substance-swingx/run doc robot.bat
@@ -0,0 +1,9 @@
+set JAVA="C:\Program Files\Java\jdk1.6.0_10\bin\java"
+
+%JAVA% -cp lib/swingx.jar;lib/swing-worker.jar;../substance/lib/forms-1.1.0.jar;../substance/lib/test/fest-swing-1.2a3.jar;../substance/lib/test/fest-reflect-1.1.jar;../substance/lib/test/fest-util-1.1.jar;../substance/lib/test/fest-assert-1.1.jar;drop/substance-swingx-tst.jar;drop/substance-swingx.jar;../substance/drop/substance.jar;../substance/drop/substance-tst.jar docrobot.StatusBarRunner
+%JAVA% -cp lib/swingx.jar;lib/swing-worker.jar;../substance/lib/forms-1.1.0.jar;../substance/lib/test/fest-swing-1.2a3.jar;../substance/lib/test/fest-reflect-1.1.jar;../substance/lib/test/fest-util-1.1.jar;../substance/lib/test/fest-assert-1.1.jar;drop/substance-swingx-tst.jar;drop/substance-swingx.jar;../substance/drop/substance.jar;../substance/drop/substance-tst.jar docrobot.HeaderRunner
+%JAVA% -cp lib/swingx.jar;lib/swing-worker.jar;../substance/lib/forms-1.1.0.jar;../substance/lib/test/fest-swing-1.2a3.jar;../substance/lib/test/fest-reflect-1.1.jar;../substance/lib/test/fest-util-1.1.jar;../substance/lib/test/fest-assert-1.1.jar;drop/substance-swingx-tst.jar;drop/substance-swingx.jar;../substance/drop/substance.jar;../substance/drop/substance-tst.jar docrobot.LoginDialogRunner
+%JAVA% -cp lib/swingx.jar;lib/swing-worker.jar;../substance/lib/forms-1.1.0.jar;../substance/lib/test/fest-swing-1.2a3.jar;../substance/lib/test/fest-reflect-1.1.jar;../substance/lib/test/fest-util-1.1.jar;../substance/lib/test/fest-assert-1.1.jar;drop/substance-swingx-tst.jar;drop/substance-swingx.jar;../substance/drop/substance.jar;../substance/drop/substance-tst.jar docrobot.MonthViewRunner
+%JAVA% -cp lib/swingx.jar;lib/swing-worker.jar;../substance/lib/forms-1.1.0.jar;../substance/lib/test/fest-swing-1.2a3.jar;../substance/lib/test/fest-reflect-1.1.jar;../substance/lib/test/fest-util-1.1.jar;../substance/lib/test/fest-assert-1.1.jar;drop/substance-swingx-tst.jar;drop/substance-swingx.jar;../substance/drop/substance.jar;../substance/drop/substance-tst.jar docrobot.TaskPaneRunner
+%JAVA% -cp lib/swingx.jar;lib/swing-worker.jar;../substance/lib/forms-1.1.0.jar;../substance/lib/test/fest-swing-1.2a3.jar;../substance/lib/test/fest-reflect-1.1.jar;../substance/lib/test/fest-util-1.1.jar;../substance/lib/test/fest-assert-1.1.jar;drop/substance-swingx-tst.jar;drop/substance-swingx.jar;../substance/drop/substance.jar;../substance/drop/substance-tst.jar docrobot.TitledPanelRunner
+%JAVA% -cp lib/swingx.jar;lib/swing-worker.jar;../substance/lib/forms-1.1.0.jar;../substance/lib/test/fest-swing-1.2a3.jar;../substance/lib/test/fest-reflect-1.1.jar;../substance/lib/test/fest-util-1.1.jar;../substance/lib/test/fest-assert-1.1.jar;drop/substance-swingx-tst.jar;drop/substance-swingx.jar;../substance/drop/substance.jar;../substance/drop/substance-tst.jar docrobot.ErrorPaneRunner
diff --git a/substance-swingx/settings.gradle b/substance-swingx/settings.gradle
new file mode 100755
index 0000000..10dd818
--- /dev/null
+++ b/substance-swingx/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'substance-swingx'
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceDatePickerUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceDatePickerUI.java
new file mode 100644
index 0000000..2f40a93
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceDatePickerUI.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.LayoutManager;
+import java.awt.Shape;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFormattedTextField;
+import javax.swing.SwingConstants;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.text.JTextComponent;
+
+import org.jdesktop.swingx.JXDatePicker;
+import org.jdesktop.swingx.JXMonthView;
+import org.jdesktop.swingx.plaf.MonthViewUI;
+import org.jdesktop.swingx.plaf.basic.BasicDatePickerUI;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceConstants.Side;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceDropDownButton;
+import org.pushingpixels.substance.internal.utils.SubstanceOutlineUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceTextUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities.TextComponentAware;
+import org.pushingpixels.substance.internal.utils.border.SubstanceTextComponentBorder;
+
+/**
+ * Substance-consistent UI delegate for {@link JXDatePicker}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceDatePickerUI extends BasicDatePickerUI {
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ protected JButton substancePopupButton;
+
+ private Insets layoutInsets;
+
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceDatePickerUI();
+ }
+
+ @Override
+ public void installUI(JComponent c) {
+ super.installUI(c);
+
+ c.putClientProperty(SubstanceCoreUtilities.TEXT_COMPONENT_AWARE,
+ new TextComponentAware<JXDatePicker>() {
+ @Override
+ public JTextComponent getTextComponent(JXDatePicker t) {
+ return t.getEditor();
+ }
+ });
+ }
+
+ @Override
+ public void uninstallUI(JComponent c) {
+ c.putClientProperty(SubstanceCoreUtilities.TEXT_COMPONENT_AWARE, null);
+
+ super.uninstallUI(c);
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ // issue 7 - update the popup button on orientation change
+ if ("componentOrientation".equals(evt.getPropertyName())) {
+ configurePopupButton();
+ }
+ }
+ };
+ this.datePicker
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ @Override
+ protected void uninstallListeners() {
+ this.datePicker
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+ super.uninstallListeners();
+ }
+
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+
+ Border b = this.datePicker.getBorder();
+ if (b == null || b instanceof UIResource) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(this.datePicker);
+ Insets borderInsets = SubstanceSizeUtils
+ .getComboBorderInsets(fontSize);
+ this.datePicker.setBorder(new SubstanceTextComponentBorder(
+ borderInsets));
+ this.layoutInsets = SubstanceSizeUtils
+ .getComboLayoutInsets(fontSize);
+ }
+
+ this.datePicker.setOpaque(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicDatePickerUI#createPopupButton()
+ */
+ @Override
+ protected JButton createPopupButton() {
+ this.substancePopupButton = new SubstanceDropDownButton(this.datePicker);
+ this.substancePopupButton.setFocusPainted(false);
+ configurePopupButton();
+
+ this.substancePopupButton.setIcon(SubstanceCoreUtilities.getArrowIcon(
+ this.substancePopupButton, SwingConstants.SOUTH));
+
+ return this.substancePopupButton;
+ }
+
+ private void configurePopupButton() {
+ Side side = this.datePicker.getComponentOrientation().isLeftToRight() ? Side.LEFT
+ : Side.RIGHT;
+ this.substancePopupButton.putClientProperty(
+ SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY, side);
+ // this.substancePopupButton.putClientProperty(
+ // SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY, side);
+
+ // this.substancePopupButton.putClientProperty(
+ // SubstanceLookAndFeel.FLAT_PROPERTY, Boolean.TRUE);
+
+ this.substancePopupButton.setRolloverEnabled(true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicDatePickerUI#hidePopup()
+ */
+ @Override
+ public void hidePopup() {
+ super.hidePopup();
+
+ // fix for issue 6 - clear rollover indices on the popup
+ // month view component
+ JXMonthView popupMonthView = this.datePicker.getMonthView();
+ MonthViewUI ui = popupMonthView.getUI();
+ if (ui instanceof SubstanceMonthViewUI) {
+ ((SubstanceMonthViewUI) ui).resetRolloverIndex();
+ }
+ }
+
+ @Override
+ protected JFormattedTextField createEditor() {
+ JFormattedTextField result = super.createEditor();
+ Insets ins = SubstanceSizeUtils
+ .getComboTextBorderInsets(SubstanceSizeUtils
+ .getComponentFontSize(result));
+ result.setBorder(new EmptyBorder(ins.top, ins.left, ins.bottom,
+ ins.right));
+ result.setBackground(this.datePicker.getBackground());
+ result.setOpaque(false);
+ return result;
+ }
+
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ int componentFontSize = SubstanceSizeUtils
+ .getComponentFontSize(this.datePicker);
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize));
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(datePicker
+ .getWidth(), datePicker.getHeight(), Math.max(0, 2.0f
+ * SubstanceSizeUtils
+ .getClassicButtonCornerRadius(componentFontSize)
+ - borderDelta), null, borderDelta);
+
+ graphics.setColor(SubstanceTextUtilities
+ .getTextBackgroundFillColor(this.datePicker));
+ graphics.fill(contour);
+
+ super.paint(g, c);
+ }
+
+ @Override
+ protected LayoutManager createLayoutManager() {
+ return new DatePickerLayoutManager();
+ }
+
+ private class DatePickerLayoutManager implements LayoutManager {
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ public Dimension preferredLayoutSize(Container parent) {
+ return parent.getPreferredSize();
+ }
+
+ public Dimension minimumLayoutSize(Container parent) {
+ return parent.getMinimumSize();
+ }
+
+ public void layoutContainer(Container parent) {
+ int popupButtonWidth = substancePopupButton != null ? substancePopupButton
+ .getPreferredSize().width
+ : 0;
+
+ boolean ltr = datePicker.getComponentOrientation().isLeftToRight();
+
+ datePicker.getEditor().setBounds(
+ ltr ? layoutInsets.left : layoutInsets.left
+ + popupButtonWidth,
+ layoutInsets.top,
+ datePicker.getWidth() - popupButtonWidth
+ - layoutInsets.left - layoutInsets.right,
+ datePicker.getHeight() - layoutInsets.top
+ - layoutInsets.bottom);
+
+ if (substancePopupButton != null) {
+ if (ltr) {
+ substancePopupButton.setBounds(datePicker.getWidth()
+ - popupButtonWidth, 0, popupButtonWidth, datePicker
+ .getHeight());
+ } else {
+ substancePopupButton.setBounds(0, 0, popupButtonWidth,
+ datePicker.getHeight());
+ }
+ }
+ }
+ }
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceErrorPaneUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceErrorPaneUI.java
new file mode 100644
index 0000000..3353492
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceErrorPaneUI.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.jdesktop.swingx.JXErrorPane;
+import org.jdesktop.swingx.plaf.basic.BasicErrorPaneUI;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.internal.animation.IconGlowTracker;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.icon.GlowingIcon;
+
+/**
+ * UI delegate for the {@link JXErrorPane} component.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceErrorPaneUI extends BasicErrorPaneUI {
+ protected IconGlowTracker iconGlowTracker;
+
+ static {
+ AnimationConfigurationManager.getInstance().allowAnimations(
+ AnimationFacet.ICON_GLOW, JXErrorPane.class);
+ }
+
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceErrorPaneUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicErrorPaneUI#installComponents()
+ */
+ @Override
+ protected void installComponents() {
+ super.installComponents();
+
+ this.errorMessage.setBorder(null);
+ this.errorScrollPane.setOpaque(false);
+ this.errorScrollPane.getViewport().setOpaque(false);
+
+ this.iconGlowTracker = new IconGlowTracker(this.iconLabel);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicErrorPaneUI#getDefaultErrorIcon()
+ */
+ @Override
+ protected Icon getDefaultErrorIcon() {
+ Icon errorIcon = UIManager.getIcon("OptionPane.errorIcon");
+ return new GlowingIcon(errorIcon, this.iconGlowTracker);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicErrorPaneUI#getDefaultWarningIcon()
+ */
+ @Override
+ protected Icon getDefaultWarningIcon() {
+ Icon errorIcon = UIManager.getIcon("OptionPane.warningIcon");
+ return new GlowingIcon(errorIcon, this.iconGlowTracker);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicErrorPaneUI#reinit()
+ */
+ @Override
+ protected void reinit() {
+ super.reinit();
+
+ if (this.iconLabel.getIcon() != null) {
+ this.iconGlowTracker.play(3);
+ }
+ }
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceHeaderUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceHeaderUI.java
new file mode 100644
index 0000000..8690676
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceHeaderUI.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import java.awt.Font;
+import java.awt.Graphics2D;
+
+import javax.swing.JComponent;
+import javax.swing.UIManager;
+import javax.swing.plaf.ComponentUI;
+
+import org.jdesktop.swingx.JXHeader;
+import org.jdesktop.swingx.painter.Painter;
+import org.jdesktop.swingx.plaf.PainterUIResource;
+import org.jdesktop.swingx.plaf.basic.BasicHeaderUI;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Substance-consistent UI delegate for {@link JXHeader}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceHeaderUI extends BasicHeaderUI {
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceHeaderUI();
+ }
+
+ // protected class SubstanceDescriptionPane extends DescriptionPane {
+ // @Override
+ // protected void paintComponent(Graphics g) {
+ // Graphics2D g2d = (Graphics2D)g.create();
+ // RenderingUtils.installDesktopHints(g2d, this);
+ // super.paintComponent(g2d);
+ // g2d.dispose();
+ // }
+ // }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicHeaderUI#installUI(javax.swing.JComponent
+ * )
+ */
+ @Override
+ public void installUI(JComponent c) {
+ super.installUI(c);
+
+ Font font = UIManager.getFont("Label.font");
+ this.descriptionPane.setFont(font);
+ this.titleLabel.setFont(font.deriveFont(font.getSize() + 2.0f));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicHeaderUI#installDefaults(org.jdesktop
+ * .swingx.JXHeader)
+ */
+ @Override
+ protected void installDefaults(JXHeader h) {
+ // this.descriptionPane = new SubstanceDescriptionPane();
+ // this.descriptionPane.setLineWrap(true);
+ // this.descriptionPane.setOpaque(false);
+
+ super.installDefaults(h);
+ SubstanceLookAndFeel.setDecorationType(h, DecorationAreaType.HEADER);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicHeaderUI#uninstallDefaults(org.jdesktop
+ * .swingx.JXHeader)
+ */
+ @Override
+ protected void uninstallDefaults(JXHeader h) {
+ DecorationPainterUtils.clearDecorationType(h);
+ super.uninstallDefaults(h);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicHeaderUI#createBackgroundPainter()
+ */
+ @Override
+ protected Painter<?> createBackgroundPainter() {
+ return new PainterUIResource(new Painter<JXHeader>() {
+ public void paint(Graphics2D g, JXHeader jxHeader, int width,
+ int height) {
+ // SubstanceDecorationUtilities.paintDecorationBackground(g,
+ // jxHeader, false);
+
+ BackgroundPaintingUtils.update(g, jxHeader, false);
+ // g.translate(0, height - 2);
+ // SubstanceTheme decorationTheme = SubstanceThemeUtilities
+ // .getDecorationTheme(jxHeader);
+ // SubstanceCoreUtilities.paintSeparator(jxHeader, g,
+ // decorationTheme.getColorScheme(),
+ // SubstanceCoreUtilities.isThemeDark(decorationTheme),
+ // width, 1, JSeparator.HORIZONTAL);
+ // g.translate(0, 2 - height);
+ }
+ });
+ }
+
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceHyperlinkUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceHyperlinkUI.java
new file mode 100644
index 0000000..72177c7
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceHyperlinkUI.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.jdesktop.swingx.JXHyperlink;
+import org.jdesktop.swingx.plaf.basic.BasicHyperlinkUI;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Substance-consistent UI delegate for {@link JXHyperlink}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceHyperlinkUI extends BasicHyperlinkUI {
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceHyperlinkUI();
+ }
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceLoginPaneUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceLoginPaneUI.java
new file mode 100644
index 0000000..d10bf25
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceLoginPaneUI.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import java.awt.*;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.awt.image.BufferedImage;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.jdesktop.swingx.JXLoginPane;
+import org.jdesktop.swingx.plaf.basic.BasicLoginPaneUI;
+import org.pushingpixels.lafwidget.utils.RenderingUtils;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Substance-consistent UI delegate for {@link JXLoginPane}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceLoginPaneUI extends BasicLoginPaneUI {
+ protected JXLoginPane loginPanel;
+
+ protected HierarchyListener substanceHierarchyListener;
+
+ // protected ComponentListener substanceComponentListener;
+
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceLoginPaneUI((JXLoginPane) comp);
+ }
+
+ /**
+ * Creates a new UI component.
+ *
+ * @param dlg
+ * Login panel component.
+ */
+ public SubstanceLoginPaneUI(JXLoginPane dlg) {
+ super(dlg);
+ this.loginPanel = dlg;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ super.installUI(c);
+ this.substanceHierarchyListener = new HierarchyListener() {
+ public void hierarchyChanged(HierarchyEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ Window window = SwingUtilities
+ .getWindowAncestor(loginPanel);
+ if (window != null) {
+ JComponent titlePane = SubstanceLookAndFeel
+ .getTitlePaneComponent(window);
+ if (titlePane != null) {
+ titlePane.putClientProperty(
+ SubstanceLookAndFeel.WATERMARK_VISIBLE,
+ Boolean.FALSE);
+ }
+ }
+
+ loginPanel.setBanner(getBanner());
+ }
+ });
+ }
+ };
+ this.loginPanel.addHierarchyListener(this.substanceHierarchyListener);
+ // this.substanceComponentListener = new ComponentAdapter() {
+ // @Override
+ // public void componentShown(ComponentEvent e) {
+ // SwingUtilities.invokeLater(new Runnable() {
+ // public void run() {
+ // System.out.println("Recomputed");
+ // loginPanel.setImage(getBanner());
+ // }
+ // });
+ // }
+ // };
+ // this.loginPanel.addComponentListener(this.substanceComponentListener)
+ // ;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ this.loginPanel
+ .removeHierarchyListener(this.substanceHierarchyListener);
+ this.substanceHierarchyListener = null;
+ // this.loginPanel.removeComponentListener(this.
+ // substanceComponentListener);
+ // this.substanceComponentListener = null;
+ super.uninstallUI(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicLoginPanelUI#getBanner()
+ */
+ @Override
+ public Image getBanner() {
+ Image superResult = super.getBanner();
+ if (this.loginPanel.getParent() == null)
+ return superResult;
+
+ int width = superResult.getWidth(null);
+ int height = superResult.getHeight(null);
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D graphics = (Graphics2D) result.getGraphics();
+
+ Icon origIcon = SubstanceCoreUtilities
+ .getIcon("resource/32/login-new32.png");
+ origIcon = SubstanceCoreUtilities.getThemedIcon(this.loginPanel,
+ origIcon);
+
+ SubstanceLookAndFeel.setDecorationType(this.loginPanel,
+ DecorationAreaType.HEADER);
+ BackgroundPaintingUtils.update(graphics, this.loginPanel, true);
+ DecorationPainterUtils.clearDecorationType(this.loginPanel);
+
+ float loginStringX = origIcon.getIconWidth() + 8 + width * .05f;
+ float loginStringY = height * .75f;
+
+ Font font = UIManager.getFont("JXLoginPane.bannerFont");
+ graphics.setFont(font);
+ Graphics2D originalGraphics = graphics;
+ if (!loginPanel.getComponentOrientation().isLeftToRight()) {
+ originalGraphics = (Graphics2D) graphics.create();
+ graphics.scale(-1, 1);
+ graphics.translate(-width, 0);
+ loginStringX = width
+ - origIcon.getIconWidth()
+ - 8
+ - (((float) font.getStringBounds(
+ loginPanel.getBannerText(),
+ originalGraphics.getFontRenderContext()).getWidth()) + width * .05f);
+ }
+
+ RenderingUtils.installDesktopHints(graphics, this.loginPanel);
+
+ originalGraphics.setColor(SubstanceColorUtilities
+ .getForegroundColor(SubstanceCoreUtilities.getSkin(
+ this.loginPanel).getEnabledColorScheme(
+ DecorationAreaType.HEADER)));
+ originalGraphics.drawString(loginPanel.getBannerText(), loginStringX,
+ loginStringY);
+
+ int iconY = (height - origIcon.getIconHeight()) / 2;
+ if (!loginPanel.getComponentOrientation().isLeftToRight()) {
+ origIcon.paintIcon(loginPanel, graphics, width
+ - origIcon.getIconWidth() - 8, iconY);
+ } else {
+ origIcon.paintIcon(loginPanel, graphics, 8, iconY);
+ }
+ return result;
+ }
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceMonthViewUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceMonthViewUI.java
new file mode 100644
index 0000000..0eadc88
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceMonthViewUI.java
@@ -0,0 +1,1073 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonModel;
+import javax.swing.DefaultButtonModel;
+import javax.swing.JComponent;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicLookAndFeel;
+
+import org.jdesktop.swingx.JXMonthView;
+import org.jdesktop.swingx.border.IconBorder;
+import org.jdesktop.swingx.calendar.DateSelectionModel;
+import org.jdesktop.swingx.event.DateSelectionEvent;
+import org.jdesktop.swingx.event.DateSelectionListener;
+import org.jdesktop.swingx.plaf.basic.BasicMonthViewUI;
+import org.jdesktop.swingx.plaf.basic.CalendarState;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.ComponentStateFacet;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.animation.StateTransitionMultiTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.RepaintCallback;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+import org.pushingpixels.substance.internal.utils.SubstanceTextUtilities;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.TimelineCallback;
+import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
+
+/**
+ * Substance-consistent UI delegate for {@link JXMonthView}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceMonthViewUI extends BasicMonthViewUI implements
+ TransitionAwareUI {
+ /**
+ * Listener for transition animations on rollovers.
+ */
+ protected DayRolloverFadeListener substanceFadeRolloverListener;
+
+ protected DateId rolloverDateId;
+
+ /**
+ * Holds the list of currently selected days. Every entry is day:month:year
+ */
+ protected Set<DateId> selectedDates;
+
+ /**
+ * Listener for transition animations on day selections.
+ */
+ protected DateSelectionListener substanceFadeSelectionListener;
+
+ private StateTransitionMultiTracker<DateId> dayStateTransitionMultiTracker;
+
+ private StateTransitionMultiTracker<MonthId> monthStateTransitionMultiTracker;
+
+ static class DateId implements Comparable<DateId> {
+ protected int day;
+
+ protected int month;
+
+ protected int year;
+
+ public DateId(int day, int month, int year) {
+ this.day = day;
+ this.month = month;
+ this.year = year;
+ }
+
+ @Override
+ public int compareTo(DateId o) {
+ if ((this.day == o.day) && (this.month == o.month)
+ && (this.year == o.year))
+ return 0;
+ return 1;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof DateId) {
+ return this.compareTo((DateId) obj) == 0;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (this.day ^ (this.day >>> 32))
+ & (this.month ^ (this.month >>> 32))
+ & (this.year ^ (this.year >>> 32));
+ }
+
+ @Override
+ public String toString() {
+ return "Day " + this.day + ", Month " + this.month + ", Year "
+ + this.year;
+ }
+ }
+
+ static class MonthId implements Comparable<MonthId> {
+ protected int month;
+
+ protected int year;
+
+ public MonthId(int month, int year) {
+ this.month = month;
+ this.year = year;
+ }
+
+ @Override
+ public int compareTo(MonthId o) {
+ if ((this.month == o.month) && (this.year == o.year))
+ return 0;
+ return 1;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof MonthId) {
+ return this.compareTo((MonthId) obj) == 0;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (this.month ^ (this.month >>> 32))
+ & (this.year ^ (this.year >>> 32));
+ }
+
+ @Override
+ public String toString() {
+ return "Month " + this.month + ", Year " + this.year;
+ }
+ }
+
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceMonthViewUI();
+ }
+
+ /**
+ * Creates a new UI delegate.
+ */
+ public SubstanceMonthViewUI() {
+ super();
+
+ this.dayStateTransitionMultiTracker = new StateTransitionMultiTracker<DateId>();
+ this.monthStateTransitionMultiTracker = new StateTransitionMultiTracker<MonthId>();
+ this.selectedDates = new HashSet<DateId>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicMonthViewUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ BasicLookAndFeel.installColors(this.monthView,
+ "JXMonthView.background", "JXMonthView.foreground");
+
+ Calendar cal = this.monthView.getCalendar();
+ if (cal != null) {
+ for (Date selected : this.getSelection()) {
+ cal.setTime(selected);
+ int day = cal.get(Calendar.DAY_OF_MONTH);
+ int month = cal.get(Calendar.MONTH);
+ int year = cal.get(Calendar.YEAR);
+ this.selectedDates.add(new DateId(day, month, year));
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicMonthViewUI#installDelegate()
+ */
+ @Override
+ protected void installDelegate() {
+ super.installDelegate();
+ this.monthDownImage = SubstanceImageCreator.getArrowIcon(this.monthView
+ .getFont().getSize(), SwingConstants.WEST,
+ SubstanceColorSchemeUtilities
+ .getColorScheme(this.monthView,
+ ColorSchemeAssociationKind.MARK,
+ ComponentState.ENABLED));
+ this.monthUpImage = SubstanceImageCreator.getArrowIcon(this.monthView
+ .getFont().getSize(), SwingConstants.EAST,
+ SubstanceColorSchemeUtilities
+ .getColorScheme(this.monthView,
+ ColorSchemeAssociationKind.MARK,
+ ComponentState.ENABLED));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicMonthViewUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substanceFadeRolloverListener = new DayRolloverFadeListener();
+ this.monthView.addMouseMotionListener(substanceFadeRolloverListener);
+ this.monthView.addMouseListener(substanceFadeRolloverListener);
+
+ // Add listener for the selection animation
+ substanceFadeSelectionListener = new DateSelectionListener() {
+ public void valueChanged(DateSelectionEvent ev) {
+ // System.out.println("Has "
+ // + monthView.getSelectionModel().getSelection().size());
+
+ // try {
+ boolean hasSelectionAnimations = !LafWidgetUtilities
+ .hasNoAnimations(monthView, AnimationFacet.SELECTION);
+
+ Calendar cal = monthView.getCalendar();
+
+ DateSelectionModel selectionModel = monthView
+ .getSelectionModel();
+
+ for (Iterator<DateId> selIt = selectedDates.iterator(); selIt
+ .hasNext();) {
+ DateId currSelected = selIt.next();
+ // still selected?
+ cal.set(Calendar.DAY_OF_MONTH, currSelected.day);
+ cal.set(Calendar.MONTH, currSelected.month);
+ cal.set(Calendar.YEAR, currSelected.year);
+ if (selectionModel.isSelected(cal.getTime()))
+ continue;
+
+ if (hasSelectionAnimations) {
+ StateTransitionTracker tracker = getDayTracker(
+ currSelected, getDayState(currSelected.day,
+ currSelected.month, currSelected.year)
+ .isFacetActive(
+ ComponentStateFacet.ROLLOVER),
+ true);
+ tracker.getModel().setSelected(false);
+ }
+ selIt.remove();
+ }
+
+ for (Date newlySelected : selectionModel.getSelection()) {
+ cal.setTime(newlySelected);
+ DateId newlySelectedDate = new DateId(cal
+ .get(Calendar.DAY_OF_MONTH), cal
+ .get(Calendar.MONTH), cal.get(Calendar.YEAR));
+ if (selectedDates.contains(newlySelectedDate))
+ continue;
+
+ if (hasSelectionAnimations) {
+ StateTransitionTracker tracker = getDayTracker(
+ newlySelectedDate, getDayState(
+ newlySelectedDate.day,
+ newlySelectedDate.month,
+ newlySelectedDate.year).isFacetActive(
+ ComponentStateFacet.ROLLOVER), false);
+ tracker.getModel().setSelected(true);
+ }
+ selectedDates.add(newlySelectedDate);
+ }
+ }
+ };
+ this.monthView.getSelectionModel().addDateSelectionListener(
+ this.substanceFadeSelectionListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicMonthViewUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.monthView.getSelectionModel().removeDateSelectionListener(
+ this.substanceFadeSelectionListener);
+ this.substanceFadeSelectionListener = null;
+
+ this.monthView.removeMouseListener(this.substanceFadeRolloverListener);
+ this.monthView
+ .removeMouseMotionListener(this.substanceFadeRolloverListener);
+ this.substanceFadeRolloverListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicMonthViewUI#uninstallDefaults()
+ */
+ @Override
+ protected void uninstallDefaults() {
+ this.selectedDates.clear();
+
+ super.uninstallDefaults();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.substance.utils.Trackable#isInside(java.awt.event.MouseEvent)
+ */
+ public boolean isInside(MouseEvent me) {
+ // The entire component area is considered as trackable for rollover
+ // effects.
+ return true;
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return null;
+ }
+
+ /**
+ * Listener for fade animations on month view rollovers.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class DayRolloverFadeListener implements MouseListener,
+ MouseMotionListener {
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ public void mousePressed(MouseEvent e) {
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ public void mouseExited(MouseEvent e) {
+ this.fadeOutDay();
+ this.fadeOutMonth();
+ // System.out.println("Nulling RO index");
+ resetRolloverIndex();
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ if (!monthView.isEnabled())
+ return;
+ this.handleMove(e);
+ }
+
+ public void mouseDragged(MouseEvent e) {
+ // if (SubstanceCoreUtilities.toBleedWatermark(list))
+ // return;
+
+ if (!monthView.isEnabled())
+ return;
+ this.handleMove(e);
+ }
+
+ /**
+ * Handles various mouse move events and initiates the fade animation if
+ * necessary.
+ *
+ * @param e
+ * Mouse event.
+ */
+ private void handleMove(MouseEvent e) {
+ boolean fadeAllowed = AnimationConfigurationManager.getInstance()
+ .isAnimationAllowed(AnimationFacet.ROLLOVER, monthView);
+ if (!fadeAllowed) {
+ this.fadeOutDay();
+ this.fadeOutMonth();
+ resetRolloverIndex();
+ return;
+ }
+
+ Date matchingDay = monthView.getDayAtLocation(e.getX(), e.getY());
+ if (matchingDay == null) {
+ this.fadeOutDay();
+ this.fadeOutMonth();
+ // System.out.println("Nulling RO index");
+ resetRolloverIndex();
+ } else {
+ // special case - check the month bounds of the matching day
+ Rectangle monthBounds = getMonthBounds(matchingDay);
+ if ((monthBounds == null)
+ || !monthBounds.contains(e.getPoint())) {
+ // either trailing or leading day
+ this.fadeOutDay();
+ this.fadeOutMonth();
+ resetRolloverIndex();
+ } else {
+ Calendar cal = monthView.getCalendar();
+ cal.setTime(matchingDay);
+ int roIndexDay = cal.get(Calendar.DAY_OF_MONTH);
+ int roIndexMonth = cal.get(Calendar.MONTH);
+ int roIndexYear = cal.get(Calendar.YEAR);
+
+ DateId dateId = new DateId(roIndexDay, roIndexMonth,
+ roIndexYear);
+ // check if this is the same index
+ if (dateId.equals(rolloverDateId))
+ return;
+
+ this.fadeOutDay();
+ boolean isDifferentMonth = (rolloverDateId == null)
+ || (rolloverDateId.month != roIndexMonth)
+ || (rolloverDateId.year != roIndexYear);
+ if (isDifferentMonth) {
+ this.fadeOutMonth();
+ }
+
+ rolloverDateId = dateId;
+
+ StateTransitionTracker dayTracker = getDayTracker(dateId,
+ false, getDayState(roIndexDay, roIndexMonth,
+ roIndexYear).isFacetActive(
+ ComponentStateFacet.SELECTION));
+ dayTracker.getModel().setRollover(true);
+
+ if (isDifferentMonth) {
+ StateTransitionTracker monthTracker = getMonthTracker(
+ new MonthId(roIndexMonth, roIndexYear), false,
+ getMonthState(roIndexMonth, roIndexYear)
+ .isFacetActive(
+ ComponentStateFacet.SELECTION));
+ monthTracker.getModel().setRollover(true);
+ // System.out.println("Fading in " + rolloverMonthId +
+ // ":"
+ // + rolloverYearId);
+ }
+ // System.out.println("Setting RO index to " + roIndex);
+ }
+ }
+ }
+
+ /**
+ * Initiates the fade out effect on the specific day.
+ */
+ private void fadeOutDay() {
+ if (rolloverDateId == null)
+ return;
+
+ StateTransitionTracker tracker = getDayTracker(rolloverDateId,
+ true, getDayState(rolloverDateId.day, rolloverDateId.month,
+ rolloverDateId.year).isFacetActive(
+ ComponentStateFacet.SELECTION));
+ tracker.getModel().setRollover(false);
+ }
+
+ /**
+ * Initiates the fade out effect on the specific month.
+ */
+ private void fadeOutMonth() {
+ if (rolloverDateId == null)
+ return;
+
+ StateTransitionTracker tracker = getMonthTracker(new MonthId(
+ rolloverDateId.month, rolloverDateId.year), true,
+ getMonthState(rolloverDateId.month, rolloverDateId.year)
+ .isFacetActive(ComponentStateFacet.SELECTION));
+ tracker.getModel().setRollover(false);
+ }
+ }
+
+ /**
+ * Repaints a single month during the fade animation cycle.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class MonthRepaintCallback extends
+ UIThreadTimelineCallbackAdapter {
+ /**
+ * Associated control.
+ */
+ protected JXMonthView monthView;
+
+ /**
+ * Associated (animated) month index.
+ */
+ protected int monthIndex;
+
+ protected int yearIndex;
+
+ /**
+ * Creates a new animation repaint callback.
+ *
+ * @param monthView
+ * Associated control.
+ * @param monthIndex
+ * Associated (animated) month index.
+ */
+ public MonthRepaintCallback(JXMonthView monthView, int monthIndex,
+ int yearIndex) {
+ this.monthView = monthView;
+ this.monthIndex = monthIndex;
+ this.yearIndex = yearIndex;
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ this.repaintCell();
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ this.repaintCell();
+ }
+
+ /**
+ * Repaints the associated cell.
+ */
+ private void repaintCell() {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ if (monthView != SubstanceMonthViewUI.this.monthView) {
+ // may happen if the LAF was switched in the meantime
+ return;
+ }
+ Calendar cal = monthView.getCalendar();
+ cal.set(Calendar.MONTH, monthIndex);
+ cal.set(Calendar.YEAR, yearIndex);
+ Rectangle monthBounds = getMonthBounds(cal.getTime());
+ // Rectangle monthTitleBounds = monthTitleBoundsMap
+ // .get(monthIndex + ":" + yearIndex);
+ if (monthBounds != null) {
+ monthView.repaint(monthBounds);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Repaints a single day during the fade animation cycle.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class DayRepaintCallback extends UIThreadTimelineCallbackAdapter {
+ /**
+ * Associated control.
+ */
+ protected JXMonthView monthView;
+
+ /**
+ * Associated (animated) day index.
+ */
+ protected int dayIndex;
+
+ protected int monthIndex;
+
+ protected int yearIndex;
+
+ /**
+ * Creates a new animation repaint callback.
+ *
+ * @param monthView
+ * Associated control.
+ * @param dayIndex
+ * Associated (animated) day index.
+ */
+ public DayRepaintCallback(JXMonthView monthView, int dayIndex,
+ int monthIndex, int yearIndex) {
+ this.monthView = monthView;
+ this.dayIndex = dayIndex;
+ this.monthIndex = monthIndex;
+ this.yearIndex = yearIndex;
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ this.repaintCell();
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ this.repaintCell();
+ }
+
+ /**
+ * Repaints the associated cell.
+ */
+ private void repaintCell() {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ if (monthView != SubstanceMonthViewUI.this.monthView) {
+ // may happen if the LAF was switched in the meantime
+ return;
+ }
+ Calendar cal = monthView.getCalendar();
+ cal.set(Calendar.DAY_OF_MONTH, dayIndex);
+ cal.set(Calendar.MONTH, monthIndex);
+ cal.set(Calendar.YEAR, yearIndex);
+
+ Rectangle dayBounds = getDayBounds(cal.getTime());
+
+ if (dayBounds != null) {
+ DayRepaintCallback.this.monthView.repaint(dayBounds);
+ }
+ }
+ });
+ }
+ }
+
+ private ComponentState getDayState(int dayIndex, int monthIndex,
+ int yearIndex) {
+ DateId dateId = new DateId(dayIndex, monthIndex, yearIndex);
+ boolean isEnabled = this.monthView.isEnabled();
+ StateTransitionTracker tracker = this.dayStateTransitionMultiTracker
+ .getTracker(dateId);
+ if (tracker == null) {
+ boolean isRollover = dateId.equals(this.rolloverDateId);
+ boolean isSelected = this.selectedDates.contains(dateId);
+ return ComponentState.getState(isEnabled, isRollover, isSelected);
+ } else {
+ ComponentState fromTracker = tracker.getModelStateInfo()
+ .getCurrModelState();
+ return ComponentState.getState(isEnabled, fromTracker
+ .isFacetActive(ComponentStateFacet.ROLLOVER), fromTracker
+ .isFacetActive(ComponentStateFacet.SELECTION));
+ }
+ }
+
+ private ComponentState getMonthState(int monthIndex, int yearIndex) {
+ MonthId monthId = new MonthId(monthIndex, yearIndex);
+ boolean isEnabled = this.monthView.isEnabled();
+ StateTransitionTracker tracker = this.monthStateTransitionMultiTracker
+ .getTracker(monthId);
+ if (tracker == null) {
+ boolean isRollover = (this.rolloverDateId != null)
+ && (this.rolloverDateId.month == monthIndex)
+ && (this.rolloverDateId.year == yearIndex);
+ return ComponentState.getState(isEnabled, isRollover, false);
+ } else {
+ ComponentState fromTracker = tracker.getModelStateInfo()
+ .getCurrModelState();
+ return ComponentState.getState(isEnabled, fromTracker
+ .isFacetActive(ComponentStateFacet.ROLLOVER), fromTracker
+ .isFacetActive(ComponentStateFacet.SELECTION));
+ }
+ }
+
+ /**
+ * Resets the rollover index.
+ */
+ public void resetRolloverIndex() {
+ this.rolloverDateId = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicMonthViewUI#createRenderingHandler()
+ */
+ @Override
+ protected RenderingHandler createRenderingHandler() {
+ // return null;
+ return new SubstanceRenderingHandler();
+ }
+
+ @Override
+ protected void paintMonthHeader(Graphics g, Calendar calendar) {
+ Rectangle page = getMonthHeaderBounds(calendar.getTime(), false);
+ int month = calendar.get(Calendar.MONTH);
+ int year = calendar.get(Calendar.YEAR);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ ComponentState componentState = monthView.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED;
+ float fillAlpha = SubstanceColorSchemeUtilities.getAlpha(monthView,
+ componentState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(monthView,
+ fillAlpha, g));
+ SubstanceColorScheme bgFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(monthView,
+ ColorSchemeAssociationKind.HIGHLIGHT, componentState);
+ SubstanceColorScheme bgBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(monthView,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ componentState);
+ HighlightPainterUtils.paintHighlight(g2d, null, monthView, page, 0.5f,
+ null, bgFillScheme, bgBorderScheme);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(monthView, g));
+
+ StateTransitionTracker monthTracker = this.monthStateTransitionMultiTracker
+ .getTracker(new MonthId(month, year));
+ StateTransitionTracker.ModelStateInfo modelStateInfo = (monthTracker == null) ? null
+ : monthTracker.getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ((modelStateInfo == null) ? null
+ : modelStateInfo.getStateContributionMap());
+ ComponentState currState = ((modelStateInfo == null) ? getMonthState(
+ month, year) : modelStateInfo.getCurrModelState());
+
+ if (activeStates == null) {
+ SubstanceColorScheme currFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(monthView,
+ ColorSchemeAssociationKind.HIGHLIGHT, currState);
+ SubstanceColorScheme currBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(monthView,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ currState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(monthView,
+ SubstanceColorSchemeUtilities.getHighlightAlpha(monthView,
+ currState), g));
+ HighlightPainterUtils.paintHighlight(g2d, null, monthView, page,
+ 0.5f, null, currFillScheme, currBorderScheme);
+ } else {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+ ComponentState activeState = activeEntry.getKey();
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(monthView,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(monthView,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ activeState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ monthView, SubstanceColorSchemeUtilities
+ .getHighlightAlpha(monthView, activeState)
+ * contribution, g));
+ HighlightPainterUtils.paintHighlight(g2d, null, monthView,
+ page, 0.5f, null, fillScheme, borderScheme);
+ }
+ }
+ g2d.dispose();
+
+ super.paintMonthHeader(g, calendar);
+ }
+
+ @Override
+ protected void paintDayOfMonth(Graphics g, Rectangle bounds,
+ Calendar calendar, CalendarState state) {
+ if (state == CalendarState.IN_MONTH || state == CalendarState.TODAY) {
+ // paint rollover / selection background
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ int day = calendar.get(Calendar.DAY_OF_MONTH);
+ int month = calendar.get(Calendar.MONTH);
+ int year = calendar.get(Calendar.YEAR);
+
+ if (isToday(calendar.getTime())) {
+ graphics.setColor(monthView.getTodayBackground());
+ graphics.drawRect(bounds.x, bounds.y, bounds.width - 1,
+ bounds.height - 1);
+ }
+
+ StateTransitionTracker dayTracker = this.dayStateTransitionMultiTracker
+ .getTracker(new DateId(day, month, year));
+ StateTransitionTracker.ModelStateInfo modelStateInfo = (dayTracker == null) ? null
+ : dayTracker.getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ((modelStateInfo == null) ? null
+ : modelStateInfo.getStateContributionMap());
+ ComponentState currState = ((modelStateInfo == null) ? getDayState(
+ day, month, year) : modelStateInfo.getCurrModelState());
+
+ if (activeStates == null) {
+ SubstanceColorScheme currFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(monthView,
+ ColorSchemeAssociationKind.HIGHLIGHT, currState);
+ SubstanceColorScheme currBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(monthView,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ currState);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ monthView, SubstanceColorSchemeUtilities
+ .getHighlightAlpha(monthView, currState), g));
+ HighlightPainterUtils.paintHighlight(graphics, null, monthView,
+ bounds, 0.5f, null, currFillScheme, currBorderScheme);
+ } else {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ float contribution = activeEntry.getValue()
+ .getContribution();
+ if (contribution == 0.0f)
+ continue;
+ ComponentState activeState = activeEntry.getKey();
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(monthView,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(
+ monthView,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ activeState);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ monthView, SubstanceColorSchemeUtilities
+ .getHighlightAlpha(monthView, activeState)
+ * contribution, g));
+ HighlightPainterUtils.paintHighlight(graphics, null,
+ monthView, bounds, 0.5f, null, fillScheme,
+ borderScheme);
+ }
+ }
+
+ graphics.dispose();
+
+ }
+ super.paintDayOfMonth(g, bounds, calendar, state);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ BackgroundPaintingUtils.update(g, this.monthView, false);
+ this.paint(g, c);
+ }
+
+ protected class SubstanceRenderingHandler extends RenderingHandler {
+ @Override
+ public JComponent prepareRenderingComponent(JXMonthView monthView,
+ Calendar calendar, CalendarState dayState) {
+ JComponent result = super.prepareRenderingComponent(monthView,
+ calendar, dayState);
+
+ // System.out.println(dayState + ":" + result.getForeground());
+
+ int day = calendar.get(Calendar.DAY_OF_MONTH);
+ int month = calendar.get(Calendar.MONTH);
+ int year = calendar.get(Calendar.YEAR);
+ if (dayState == CalendarState.IN_MONTH
+ || dayState == CalendarState.TODAY) {
+ // fix for issue 4 (custom colors on the control)
+ Color customFgColor = result.getForeground();
+ boolean isForegroundUiResource = customFgColor instanceof UIResource;
+ Color fgColor = customFgColor;
+ if (isForegroundUiResource) {
+ if (monthView.isEnabled()) {
+ StateTransitionTracker dayTracker = dayStateTransitionMultiTracker
+ .getTracker(new DateId(day, month, year));
+ StateTransitionTracker.ModelStateInfo modelStateInfo = (dayTracker == null) ? null
+ : dayTracker.getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ((modelStateInfo == null) ? null
+ : modelStateInfo.getStateContributionMap());
+ ComponentState currState = ((modelStateInfo == null) ? getDayState(
+ day, month, year)
+ : modelStateInfo.getCurrModelState());
+
+ SubstanceColorScheme colorScheme = getColorSchemeForState(
+ monthView, currState);
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ fgColor = colorScheme.getForegroundColor();
+ } else {
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : modelStateInfo
+ .getStateContributionMap().entrySet()) {
+ ComponentState activeState = activeEntry
+ .getKey();
+ SubstanceColorScheme scheme = getColorSchemeForState(
+ monthView, activeState);
+ Color schemeFg = scheme.getForegroundColor();
+ float contribution = activeEntry.getValue()
+ .getContribution();
+ aggrRed += schemeFg.getRed() * contribution;
+ aggrGreen += schemeFg.getGreen() * contribution;
+ aggrBlue += schemeFg.getBlue() * contribution;
+ }
+ fgColor = new Color((int) aggrRed, (int) aggrGreen,
+ (int) aggrBlue);
+ }
+ } else {
+ float textAlpha = SubstanceColorSchemeUtilities
+ .getAlpha(monthView,
+ ComponentState.DISABLED_UNSELECTED);
+ fgColor = SubstanceTextUtilities.getForegroundColor(
+ monthView, " ",
+ ComponentState.DISABLED_UNSELECTED, textAlpha);
+ }
+ }
+ result.setForeground(fgColor);
+ }
+
+ if (dayState == CalendarState.TITLE) {
+ // month title
+ StateTransitionTracker monthTracker = monthStateTransitionMultiTracker
+ .getTracker(new MonthId(month, year));
+ StateTransitionTracker.ModelStateInfo modelStateInfo = (monthTracker == null) ? null
+ : monthTracker.getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ((modelStateInfo == null) ? null
+ : modelStateInfo.getStateContributionMap());
+ ComponentState currState = ((modelStateInfo == null) ? getMonthState(
+ month, year)
+ : modelStateInfo.getCurrModelState());
+
+ SubstanceColorScheme colorScheme = getColorSchemeForState(
+ monthView, currState);
+ Color fgColor = null;
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ fgColor = colorScheme.getForegroundColor();
+ } else {
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : modelStateInfo
+ .getStateContributionMap().entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ SubstanceColorScheme scheme = getColorSchemeForState(
+ monthView, activeState);
+ Color schemeFg = scheme.getForegroundColor();
+ float contribution = activeEntry.getValue()
+ .getContribution();
+ aggrRed += schemeFg.getRed() * contribution;
+ aggrGreen += schemeFg.getGreen() * contribution;
+ aggrBlue += schemeFg.getBlue() * contribution;
+ }
+ fgColor = new Color((int) aggrRed, (int) aggrGreen,
+ (int) aggrBlue);
+ }
+
+ result.setForeground(fgColor);
+ }
+
+ if (dayState == CalendarState.LEADING
+ || dayState == CalendarState.TRAILING) {
+ float textAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ monthView, ComponentState.DISABLED_UNSELECTED);
+ result.setForeground(SubstanceTextUtilities.getForegroundColor(
+ monthView, " ", ComponentState.DISABLED_UNSELECTED,
+ textAlpha));
+ }
+
+ if (dayState == CalendarState.TITLE) {
+ result.setBorder(getTitleBorder());
+ }
+
+ result.setOpaque(false);
+
+ return result;
+ }
+
+ private Border getTitleBorder() {
+ if (monthView.isTraversable()) {
+ IconBorder up = new IconBorder(monthUpImage,
+ SwingConstants.EAST, monthView.getBoxPaddingX());
+ IconBorder down = new IconBorder(monthDownImage,
+ SwingConstants.WEST, monthView.getBoxPaddingX());
+ Border compound = BorderFactory.createCompoundBorder(up, down);
+ Border empty = BorderFactory
+ .createEmptyBorder(2 * monthView.getBoxPaddingY(), 0,
+ 2 * monthView.getBoxPaddingY(), 0);
+ return BorderFactory.createCompoundBorder(compound, empty);
+ }
+
+ return BorderFactory.createEmptyBorder(monthView.getBoxPaddingY(),
+ monthView.getBoxPaddingX(), monthView.getBoxPaddingY(),
+ monthView.getBoxPaddingX());
+ }
+ }
+
+ private static SubstanceColorScheme getColorSchemeForState(
+ JXMonthView monthView, ComponentState state) {
+ if (state == ComponentState.ENABLED)
+ return SubstanceColorSchemeUtilities.getColorScheme(monthView,
+ state);
+ return SubstanceColorSchemeUtilities.getColorScheme(monthView,
+ ColorSchemeAssociationKind.HIGHLIGHT, state);
+ }
+
+ private StateTransitionTracker getDayTracker(final DateId dateId,
+ boolean initialRollover, boolean initialSelected) {
+ StateTransitionTracker tracker = dayStateTransitionMultiTracker
+ .getTracker(dateId);
+ if (tracker == null) {
+ ButtonModel model = new DefaultButtonModel();
+ model.setSelected(initialSelected);
+ model.setRollover(initialRollover);
+ tracker = new StateTransitionTracker(this.monthView, model);
+ tracker.registerModelListeners();
+ tracker.setRepaintCallback(new RepaintCallback() {
+ @Override
+ public TimelineCallback getRepaintCallback() {
+ return new DayRepaintCallback(monthView, dateId.day,
+ dateId.month, dateId.year);
+ }
+ });
+ dayStateTransitionMultiTracker.addTracker(dateId, tracker);
+ }
+ return tracker;
+ }
+
+ private StateTransitionTracker getMonthTracker(final MonthId monthId,
+ boolean initialRollover, boolean initialSelected) {
+ StateTransitionTracker tracker = monthStateTransitionMultiTracker
+ .getTracker(monthId);
+ if (tracker == null) {
+ ButtonModel model = new DefaultButtonModel();
+ model.setSelected(initialSelected);
+ model.setRollover(initialRollover);
+ tracker = new StateTransitionTracker(this.monthView, model);
+ tracker.registerModelListeners();
+ tracker.setRepaintCallback(new RepaintCallback() {
+ @Override
+ public TimelineCallback getRepaintCallback() {
+ return new MonthRepaintCallback(monthView, monthId.month,
+ monthId.year);
+ }
+ });
+ monthStateTransitionMultiTracker.addTracker(monthId, tracker);
+ }
+ return tracker;
+ }
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstancePanelUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstancePanelUI.java
new file mode 100644
index 0000000..89b05a8
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstancePanelUI.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.jdesktop.swingx.JXPanel;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Substance-consistent UI delegate for {@link JXPanel}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstancePanelUI extends org.pushingpixels.substance.internal.ui.SubstancePanelUI {
+
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstancePanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jvnet.substance.SubstancePanelUI#toPaintBackground(javax.swing.JComponent
+ * )
+ */
+ @Override
+ protected boolean toPaintBackground(JComponent c) {
+ boolean result = super.toPaintBackground(c);
+ if (c instanceof JXPanel) {
+ JXPanel jxPanel = (JXPanel) c;
+ if (jxPanel.getAlpha() < 1.0)
+ return true;
+ }
+ return result;
+ }
+
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceStatusBarUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceStatusBarUI.java
new file mode 100644
index 0000000..666108e
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceStatusBarUI.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.jdesktop.swingx.JXStatusBar;
+import org.jdesktop.swingx.plaf.basic.BasicStatusBarUI;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.painter.SeparatorPainterUtils;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Substance-consistent UI delegate for {@link JXStatusBar}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceStatusBarUI extends BasicStatusBarUI {
+ /**
+ * Background delegate.
+ */
+ private SubstanceSwingxFillBackgroundDelegate bgDelegate;
+
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceStatusBarUI();
+ }
+
+ /**
+ * Creates a new UI delegate.
+ */
+ public SubstanceStatusBarUI() {
+ super();
+ this.bgDelegate = new SubstanceSwingxFillBackgroundDelegate();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicStatusBarUI#installDefaults(org.jdesktop
+ * .swingx.JXStatusBar)
+ */
+ @Override
+ protected void installDefaults(JXStatusBar sb) {
+ super.installDefaults(sb);
+ SubstanceLookAndFeel.setDecorationType(sb, DecorationAreaType.FOOTER);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicStatusBarUI#uninstallDefaults(org
+ * .jdesktop.swingx.JXStatusBar)
+ */
+ @Override
+ protected void uninstallDefaults(JXStatusBar sb) {
+ DecorationPainterUtils.clearDecorationType(sb);
+ super.uninstallDefaults(sb);
+ }
+
+ @Override
+ protected void paintBackground(Graphics2D g, JXStatusBar bar) {
+ this.bgDelegate.paint(bar, g, true);
+
+ JRootPane rootPane = SwingUtilities.getRootPane(bar);
+ Window window = SwingUtilities.getWindowAncestor(bar);
+ boolean isResizable = false;
+ if (window instanceof JFrame) {
+ JFrame frame = (JFrame) window;
+ isResizable = frame.isResizable()
+ && (frame.getExtendedState() != JFrame.MAXIMIZED_BOTH);
+ }
+ if (window instanceof JDialog) {
+ isResizable = ((JDialog) window).isResizable();
+ }
+ boolean hasResizeGrip = SubstanceCoreUtilities
+ .toShowExtraWidgets(rootPane)
+ && isResizable;
+ if (hasResizeGrip) {
+ int dim = bar.getHeight() * 2 / 3;
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(bar, ColorSchemeAssociationKind.SEPARATOR,
+ ComponentState.ENABLED);
+ BufferedImage resizeImage = SubstanceImageCreator
+ .getResizeGripImage(bar, scheme, dim, false);
+ g.drawImage(resizeImage, bar.getWidth() - dim, bar.getHeight()
+ - dim, null);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicStatusBarUI#paintSeparator(java.awt
+ * .Graphics2D, org.jdesktop.swingx.JXStatusBar, int, int, int, int)
+ */
+ @Override
+ protected void paintSeparator(Graphics2D g, JXStatusBar bar, int x, int y,
+ int w, int h) {
+ g.translate(x, y);
+ SeparatorPainterUtils.paintSeparator(bar, g, w, h, JSlider.VERTICAL,
+ true, 4);
+ g.translate(-x, -y);
+ }
+
+ @Override
+ protected int getSeparatorWidth() {
+ return SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
+ .getComponentFontSize(this.statusBar), super
+ .getSeparatorWidth(), 2, 1, false);
+ }
+
+ @Override
+ protected Insets getSeparatorInsets(Insets insets) {
+ Insets result = super.getSeparatorInsets(insets);
+ int totalSeparatorWidth = this.getSeparatorWidth();
+ int componentFontSize = SubstanceSizeUtils
+ .getComponentFontSize(this.statusBar);
+ int lineSeparatorWidth = (int) (2 * SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize));
+ int widthLeft = (totalSeparatorWidth - lineSeparatorWidth - result.left - result.right);
+ if (widthLeft > 0) {
+ result.left += widthLeft / 2;
+ result.right += widthLeft / 2;
+ }
+ result.top = SubstanceSizeUtils.getAdjustedSize(componentFontSize,
+ result.top, 4, 1, false);
+ result.bottom = SubstanceSizeUtils.getAdjustedSize(componentFontSize,
+ result.bottom, 4, 1, false);
+ return result;
+ }
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceSwingxFillBackgroundDelegate.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceSwingxFillBackgroundDelegate.java
new file mode 100644
index 0000000..5370271
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceSwingxFillBackgroundDelegate.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import java.awt.Graphics2D;
+import java.util.List;
+
+import javax.swing.JComponent;
+import javax.swing.JSeparator;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.overlay.SubstanceOverlayPainter;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.SeparatorPainterUtils;
+
+public class SubstanceSwingxFillBackgroundDelegate {
+ public void paint(JComponent component, Graphics2D graphics,
+ boolean paintSeparator) {
+ BackgroundPaintingUtils.updateIfOpaque(graphics, component);
+ if (paintSeparator) {
+ SubstanceSkin skin = SubstanceLookAndFeel.getCurrentSkin(component);
+ DecorationAreaType decorationAreaType = SubstanceLookAndFeel
+ .getDecorationType(component);
+ List<SubstanceOverlayPainter> overlayPainters = skin
+ .getOverlayPainters(decorationAreaType);
+ // only if there are no overlays specified on this decoration area
+ // type in the skin
+ if (overlayPainters.size() == 0) {
+ // paint the separator on top.
+ SeparatorPainterUtils.paintSeparator(component, graphics,
+ component.getWidth(), 0, JSeparator.HORIZONTAL, false,
+ 0);
+ }
+ }
+ }
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceSwingxPlugin.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceSwingxPlugin.java
new file mode 100644
index 0000000..feae9bb
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceSwingxPlugin.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+
+import javax.swing.JComponent;
+import javax.swing.SwingConstants;
+import javax.swing.UIDefaults;
+import javax.swing.plaf.ColorUIResource;
+import javax.swing.plaf.FontUIResource;
+import javax.swing.plaf.IconUIResource;
+
+import org.jdesktop.swingx.JXDatePicker;
+import org.jdesktop.swingx.JXErrorPane;
+import org.jdesktop.swingx.JXHeader;
+import org.jdesktop.swingx.JXHyperlink;
+import org.jdesktop.swingx.JXLoginPane;
+import org.jdesktop.swingx.JXMonthView;
+import org.jdesktop.swingx.JXStatusBar;
+import org.jdesktop.swingx.JXTaskPane;
+import org.jdesktop.swingx.JXTaskPaneContainer;
+import org.jdesktop.swingx.JXTipOfTheDay;
+import org.jdesktop.swingx.JXTitledPanel;
+import org.jdesktop.swingx.painter.Painter;
+import org.jdesktop.swingx.plaf.PainterUIResource;
+import org.pushingpixels.lafplugin.LafComponentPlugin;
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.fonts.FontSet;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.border.SubstanceBorder;
+
+/**
+ * Substance plugin for <a href="https://swingx.dev.java.net">SwingX</a>
+ * components.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceSwingxPlugin implements LafComponentPlugin {
+
+ /**
+ * Creates a plugin instance.
+ */
+ public SubstanceSwingxPlugin() {
+ }
+
+ public Object[] getDefaults(Object mSkin) {
+ SubstanceSkin skin = (SubstanceSkin) mSkin;
+ final SubstanceColorScheme mainActiveScheme = skin
+ .getActiveColorScheme(DecorationAreaType.NONE);
+ SubstanceColorScheme mainEnabledScheme = skin
+ .getEnabledColorScheme(DecorationAreaType.NONE);
+ SubstanceColorScheme mainDisabledScheme = skin
+ .getDisabledColorScheme(DecorationAreaType.NONE);
+ Color foregroundColor = SubstanceColorUtilities
+ .getForegroundColor(mainEnabledScheme);
+ Color backgroundColor = new ColorUIResource(mainActiveScheme
+ .getBackgroundFillColor());
+ Color disabledForegroundColor = SubstanceColorUtilities
+ .getForegroundColor(mainDisabledScheme);
+
+ SubstanceColorScheme highlightColorScheme = skin.getColorScheme(
+ (Component) null, ColorSchemeAssociationKind.HIGHLIGHT,
+ ComponentState.SELECTED);
+ if (highlightColorScheme == null) {
+ highlightColorScheme = skin.getColorScheme(null,
+ ComponentState.ROLLOVER_SELECTED);
+ }
+
+ Color selectionBackgroundColor = new ColorUIResource(
+ highlightColorScheme.getSelectionBackgroundColor());
+
+ Color selectionForegroundColor = new ColorUIResource(
+ highlightColorScheme.getSelectionForegroundColor());
+
+ FontSet fontSet = SubstanceLookAndFeel.getFontPolicy().getFontSet(
+ "Substance", null);
+
+ Font controlFont = fontSet.getControlFont();
+
+ Painter titlePanelPainter = new Painter() {
+ public void paint(Graphics2D g, Object jxTitledPanel, int width,
+ int height) {
+ JComponent titledPanel = (JComponent) jxTitledPanel;
+ Graphics2D g2d = (Graphics2D) g.create();
+ // g2d.translate(10, 10);
+ DecorationAreaType decorationType = SubstanceLookAndFeel
+ .getDecorationType(titledPanel);
+ if ((decorationType != null)
+ && (SubstanceCoreUtilities.getSkin(titledPanel)
+ .isRegisteredAsDecorationArea(decorationType))) {
+ DecorationPainterUtils.paintDecorationBackground(g2d,
+ titledPanel, false);
+ } else {
+ BackgroundPaintingUtils.fillAndWatermark(g2d, titledPanel,
+ SubstanceCoreUtilities.getSkin(titledPanel)
+ .getBackgroundColorScheme(decorationType)
+ .getBackgroundFillColor(), new Rectangle(0,
+ 0, titledPanel.getWidth(), titledPanel
+ .getHeight()));
+ }
+ g2d.dispose();
+ }
+ };
+
+ SubstanceColorScheme headerDecorationTheme = skin
+ .getEnabledColorScheme(DecorationAreaType.HEADER);
+ Color titlePaneForeground = new ColorUIResource(SubstanceColorUtilities
+ .getForegroundColor(headerDecorationTheme));
+
+ Object[] defaults = new Object[] {
+
+ JXDatePicker.uiClassID,
+ "org.pushingpixels.substance.swingx.SubstanceDatePickerUI",
+
+ JXMonthView.uiClassID,
+ "org.pushingpixels.substance.swingx.SubstanceMonthViewUI",
+
+ JXTaskPaneContainer.uiClassID,
+ "org.pushingpixels.substance.swingx.SubstanceTaskPaneContainerUI",
+
+ JXTaskPane.uiClassID,
+ "org.pushingpixels.substance.swingx.SubstanceTaskPaneUI",
+
+ JXStatusBar.uiClassID,
+ "org.pushingpixels.substance.swingx.SubstanceStatusBarUI",
+
+ JXHeader.uiClassID,
+ "org.pushingpixels.substance.swingx.SubstanceHeaderUI",
+
+ JXLoginPane.uiClassID,
+ "org.pushingpixels.substance.swingx.SubstanceLoginPaneUI",
+
+ JXTipOfTheDay.uiClassID,
+ "org.pushingpixels.substance.swingx.SubstanceTipOfTheDayUI",
+
+ JXTitledPanel.uiClassID,
+ "org.pushingpixels.substance.swingx.SubstanceTitledPanelUI",
+
+ JXErrorPane.uiClassID,
+ "org.pushingpixels.substance.swingx.SubstanceErrorPaneUI",
+
+ JXHyperlink.uiClassID,
+ "org.pushingpixels.substance.swingx.SubstanceHyperlinkUI",
+
+ "PanelUI",
+ "org.pushingpixels.substance.swingx.SubstancePanelUI",
+
+ "TableUI",
+ "org.pushingpixels.substance.swingx.SubstanceTableUI",
+
+ "ColumnHeaderRenderer.upIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new IconUIResource(SubstanceImageCreator
+ .getArrowIcon(SubstanceSizeUtils
+ .getControlFontSize(),
+ SwingConstants.NORTH, mainActiveScheme));
+ }
+ },
+
+ "ColumnHeaderRenderer.downIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new IconUIResource(SubstanceImageCreator
+ .getArrowIcon(SubstanceSizeUtils
+ .getControlFontSize(),
+ SwingConstants.SOUTH, mainActiveScheme));
+ }
+ },
+
+ "JXDatePicker.arrowIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new IconUIResource(SubstanceImageCreator
+ .getArrowIcon(SubstanceSizeUtils
+ .getControlFontSize(),
+ SwingConstants.SOUTH, mainActiveScheme));
+ }
+ },
+
+ "JXLoginPane.bannerFont",
+ new FontUIResource(controlFont.deriveFont(3.0f * controlFont
+ .getSize())),
+
+ "JXLoginPane.bannerForeground",
+ new ColorUIResource(SubstanceColorUtilities
+ .getNegativeColor(foregroundColor)),
+
+ "JXLoginPane.bannerLightBackground",
+ new ColorUIResource(
+ mainActiveScheme.isDark() ? mainActiveScheme
+ .getUltraLightColor() : mainActiveScheme
+ .getLightColor()),
+
+ "JXLoginPane.bannerDarkBackground",
+ new ColorUIResource(
+ mainActiveScheme.isDark() ? mainActiveScheme
+ .getLightColor() : mainActiveScheme
+ .getMidColor()),
+
+ "JXMonthView.background",
+ backgroundColor,
+
+ "JXMonthView.foreground",
+ foregroundColor,
+
+ "JXMonthView.monthStringBackground",
+ backgroundColor,
+
+ "JXMonthView.monthStringForeground",
+ foregroundColor,
+
+ "JXMonthView.daysOfTheWeekForeground",
+ foregroundColor,
+
+ "JXMonthView.weekOfTheYearForeground",
+ foregroundColor,
+
+ "JXMonthView.unselectableDayForeground",
+ new ColorUIResource(Color.RED),
+
+ "JXMonthView.selectedBackground",
+ selectionBackgroundColor,
+
+ "JXMonthView.selectedForeground",
+ selectionForegroundColor,
+
+ "JXMonthView.flaggedDayForeground",
+ new ColorUIResource(Color.RED),
+
+ "JXMonthView.leadingDayForeground",
+ disabledForegroundColor,
+
+ "JXMonthView.trailingDayForeground",
+ disabledForegroundColor,
+
+ "TaskPane.titleForeground",
+ foregroundColor,
+
+ "TaskPane.titleOver",
+ foregroundColor,
+
+ "TaskPane.specialTitleForeground",
+ foregroundColor,
+
+ "TaskPane.specialTitleOver",
+ foregroundColor,
+
+ "TaskPaneContainer.background",
+ backgroundColor,
+ // new ColorUIResource(new SubstanceComplexTheme("dummy",
+ // activeTitleTheme.getKind(), null, activeTitleTheme,
+ // null, null).getBackgroundColor()),
+
+ "TaskPaneContainer.backgroundPainter",
+ null,
+
+ "TaskPane.background",
+ backgroundColor,
+
+ "TaskPane.foreground",
+ foregroundColor,
+
+ "TaskPane.font",
+ new FontUIResource(controlFont.deriveFont(Font.BOLD)),
+
+ "TipOfTheDay.background",
+ backgroundColor.darker(),
+
+ "TipOfTheDay.foreground",
+ foregroundColor,
+
+ "TipOfTheDay.font",
+ new FontUIResource(controlFont),
+
+ "TipOfTheDay.tipFont",
+ new FontUIResource(controlFont.deriveFont(4.0f + controlFont
+ .getSize())),
+
+ "TipOfTheDay.border", new SubstanceBorder(),
+
+ "TitledBorder.font", new FontUIResource(controlFont),
+
+ "TitledBorder.titleColor", foregroundColor,
+
+ "JXTitledPanel.titleForeground", titlePaneForeground,
+
+ "JXTitledPanel.titleFont",
+ new FontUIResource(controlFont.deriveFont(Font.BOLD)),
+
+ "JXTitledPanel.titlePainter",
+ new PainterUIResource(titlePanelPainter),
+
+ "JXHeader.titleForeground", titlePaneForeground,
+
+ "JXHeader.descriptionForeground", titlePaneForeground, };
+
+ return defaults;
+ }
+
+ public void initialize() {
+ }
+
+ public void uninitialize() {
+ }
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTableUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTableUI.java
new file mode 100644
index 0000000..653ccb0
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTableUI.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableCellRenderer;
+
+import org.jdesktop.swingx.JXPanel;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Substance-consistent UI delegate for {@link JXPanel}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTableUI extends
+ org.pushingpixels.substance.internal.ui.SubstanceTableUI {
+
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceTableUI();
+ }
+
+ @Override
+ protected void installRendererIfNecessary(java.lang.Class<?> clazz,
+ javax.swing.table.TableCellRenderer renderer) {
+ TableCellRenderer currRenderer = this.table.getDefaultRenderer(clazz);
+ if (currRenderer != null) {
+ boolean isCore = (currRenderer instanceof DefaultTableCellRenderer.UIResource)
+ || (currRenderer.getClass().getName().startsWith(
+ "javax.swing.JTable") || (currRenderer instanceof org.jdesktop.swingx.renderer.DefaultTableRenderer));
+ if (!isCore)
+ return;
+ }
+ // System.out.println(clazz.getSimpleName() + " : overriding "
+ // + currRenderer.getClass().getName() + "["
+ // + currRenderer.hashCode() + "] with "
+ // + renderer.getClass().getName() + "[" + renderer.hashCode()
+ // + "]");
+ this.table.setDefaultRenderer(clazz, renderer);
+ }
+
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTaskPaneContainerUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTaskPaneContainerUI.java
new file mode 100644
index 0000000..dcca92a
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTaskPaneContainerUI.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+
+import javax.swing.BorderFactory;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+import org.jdesktop.swingx.JXTaskPaneContainer;
+import org.jdesktop.swingx.VerticalLayout;
+import org.jdesktop.swingx.plaf.basic.BasicTaskPaneContainerUI;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Substance-consistent UI delegate for {@link JXTaskPaneContainer}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTaskPaneContainerUI extends BasicTaskPaneContainerUI {
+ /**
+ * Background delegate.
+ */
+ private SubstanceSwingxFillBackgroundDelegate bgDelegate;
+
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceTaskPaneContainerUI();
+ }
+
+ /**
+ * Creates a new UI delegate.
+ */
+ public SubstanceTaskPaneContainerUI() {
+ super();
+ this.bgDelegate = new SubstanceSwingxFillBackgroundDelegate();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTaskPaneContainerUI#installUI(javax
+ * .swing.JComponent)
+ */
+ @Override
+ public void installUI(JComponent c) {
+ super.installUI(c);
+
+ JXTaskPaneContainer taskPane = (JXTaskPaneContainer) c;
+ taskPane.setLayout(new VerticalLayout(14));
+ taskPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ // taskPane.putClientProperty(SubstanceLookAndFeel.PAINT_ACTIVE_PROPERTY
+ // ,
+ // Boolean.TRUE);
+ // taskPane.setOpaque(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTaskPaneContainerUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ this.taskPane.setBackgroundPainter(null);
+ SubstanceLookAndFeel.setDecorationType(this.taskPane,
+ DecorationAreaType.GENERAL);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTaskPaneContainerUI#uninstallDefaults
+ * ()
+ */
+ @Override
+ protected void uninstallDefaults() {
+ DecorationPainterUtils.clearDecorationType(this.taskPane);
+ super.uninstallDefaults();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTaskPaneContainerUI#paint(java.awt
+ * .Graphics, javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ this.bgDelegate.paint(c, (Graphics2D) g, false);
+ }
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTaskPaneUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTaskPaneUI.java
new file mode 100644
index 0000000..ac9892a
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTaskPaneUI.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.MouseEvent;
+import java.util.Map;
+
+import javax.swing.ButtonModel;
+import javax.swing.DefaultButtonModel;
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.SwingConstants;
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.ComponentUI;
+
+import org.jdesktop.swingx.JXTaskPane;
+import org.jdesktop.swingx.icon.EmptyIcon;
+import org.jdesktop.swingx.plaf.basic.BasicTaskPaneUI;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
+import org.pushingpixels.substance.internal.utils.RolloverControlListener;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceTextUtilities;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
+
+/**
+ * Substance-consistent UI delegate for {@link JXTaskPane}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTaskPaneUI extends BasicTaskPaneUI implements
+ TransitionAwareUI {
+ /**
+ * Background delegate.
+ */
+ private SubstanceSwingxFillBackgroundDelegate bgDelegate;
+
+ /**
+ * Listener for thumb transition animations.
+ */
+ private RolloverControlListener substanceRolloverListener;
+
+ /**
+ * Listener for transition animations.
+ */
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Surrogate model for the fade effects on the title pane border.
+ */
+ protected ButtonModel taskPaneModel;
+
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceTaskPaneUI((JXTaskPane) comp);
+ }
+
+ /**
+ * Creates a new UI delegate.
+ */
+ public SubstanceTaskPaneUI(JXTaskPane taskPane) {
+ super();
+ this.bgDelegate = new SubstanceSwingxFillBackgroundDelegate();
+
+ this.taskPaneModel = new DefaultButtonModel();
+ this.taskPaneModel.setArmed(false);
+ this.taskPaneModel.setSelected(false);
+ this.taskPaneModel.setPressed(false);
+ this.taskPaneModel.setRollover(false);
+
+ this.stateTransitionTracker = new StateTransitionTracker(taskPane,
+ this.taskPaneModel);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicTaskPaneUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substanceRolloverListener = new RolloverControlListener(this,
+ this.taskPaneModel);
+ this.group.addMouseListener(this.substanceRolloverListener);
+ this.group.addMouseMotionListener(this.substanceRolloverListener);
+
+ this.stateTransitionTracker.registerModelListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicTaskPaneUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.group.removeMouseListener(this.substanceRolloverListener);
+ this.group.removeMouseMotionListener(this.substanceRolloverListener);
+ this.substanceRolloverListener = null;
+
+ this.stateTransitionTracker.unregisterModelListeners();
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicTaskPaneUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ SubstanceLookAndFeel.setDecorationType(this.group,
+ DecorationAreaType.GENERAL);
+
+ super.installDefaults();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTaskPaneUI#uninstallUI(javax.swing
+ * .JComponent)
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ DecorationPainterUtils.clearDecorationType(this.group);
+ super.uninstallUI(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jdesktop.swingx.plaf.basic.BasicTaskPaneUI#createPaneBorder()
+ */
+ @Override
+ protected Border createPaneBorder() {
+ return new SubstancePaneBorder();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTaskPaneUI#createContentPaneBorder()
+ */
+ @Override
+ protected Border createContentPaneBorder() {
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(group, ColorSchemeAssociationKind.BORDER,
+ ComponentState.ENABLED);
+ // the code below is the same as for the separators
+ final Color borderColor = colorScheme.isDark() ? colorScheme
+ .getDarkColor() : SubstanceColorUtilities.getInterpolatedColor(
+ colorScheme.getMidColor(), colorScheme.getDarkColor(), 0.4);
+ Border outer = new Border() {
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ g2d.setColor(borderColor);
+ // left
+ g2d.drawLine(x, y, x, y + height - 1);
+ // right
+ g2d.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
+ // bottom
+ g2d.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+
+ g2d.dispose();
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return true;
+ }
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ // don't paint at the top
+ return new Insets(0, 1, 1, 1);
+ }
+ };
+ return new CompoundBorder(outer, new EmptyBorder(1, 2, 2, 2));
+ }
+
+ /**
+ * Pane border for task pane.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class SubstancePaneBorder extends PaneBorder {
+ protected Icon expandedIcon;
+
+ protected Icon collapsedIcon;
+
+ /**
+ * Creates a new pane border.
+ */
+ public SubstancePaneBorder() {
+ this.borderColor = SubstanceColorSchemeUtilities.getColorScheme(
+ group, ColorSchemeAssociationKind.BORDER,
+ ComponentState.ENABLED).getMidColor();
+ TransitionAwareIcon.ColorSchemeAssociationKindDelegate colorSchemeAssociationDelegate = new TransitionAwareIcon.ColorSchemeAssociationKindDelegate() {
+ @Override
+ public ColorSchemeAssociationKind getColorSchemeAssociationKind(
+ ComponentState state) {
+ if (!state.isDisabled()
+ && (state != ComponentState.ENABLED)) {
+ // use HIGHLIGHT
+ return ColorSchemeAssociationKind.HIGHLIGHT;
+ }
+ return ColorSchemeAssociationKind.MARK;
+ }
+ };
+ this.expandedIcon = new TransitionAwareIcon(group,
+ new TransitionAwareIcon.TransitionAwareUIDelegate() {
+ @Override
+ public TransitionAwareUI getTransitionAwareUI() {
+ return (TransitionAwareUI) group.getUI();
+ }
+ }, new TransitionAwareIcon.Delegate() {
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ return SubstanceImageCreator
+ .getDoubleArrowIconDelta(SubstanceSizeUtils
+ .getComponentFontSize(group), 0, 3,
+ 0, SwingConstants.NORTH, scheme);
+ }
+ }, colorSchemeAssociationDelegate,
+ "substance.swingx.taskpane.expanded");
+ this.collapsedIcon = new TransitionAwareIcon(group,
+ new TransitionAwareIcon.TransitionAwareUIDelegate() {
+ @Override
+ public TransitionAwareUI getTransitionAwareUI() {
+ return (TransitionAwareUI) group.getUI();
+ }
+ }, new TransitionAwareIcon.Delegate() {
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ return SubstanceImageCreator
+ .getDoubleArrowIconDelta(SubstanceSizeUtils
+ .getComponentFontSize(group), 0, 3,
+ 0, SwingConstants.SOUTH, scheme);
+ }
+ }, colorSchemeAssociationDelegate,
+ "substance.swingx.taskpane.collapsed");
+
+ // since the label is never added to this component, we need
+ // to explicitly mark it as DecorationAreaType.GENERAL
+ SubstanceLookAndFeel.setDecorationType(this.label,
+ DecorationAreaType.GENERAL);
+ // and enforce the foreground color computed in #getPaintColor
+ this.label.putClientProperty(
+ SubstanceTextUtilities.ENFORCE_FG_COLOR, Boolean.TRUE);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.jdesktop.swingx.plaf.basic.BasicTaskPaneUI.PaneBorder#
+ * paintTitleBackground(org.jdesktop.swingx.JXTaskPane,
+ * java.awt.Graphics)
+ */
+ @Override
+ protected void paintTitleBackground(JXTaskPane group, Graphics g) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ ComponentState currState = stateTransitionTracker
+ .getModelStateInfo().getCurrModelState();
+
+ SubstanceColorScheme baseFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(group,
+ ColorSchemeAssociationKind.HIGHLIGHT, currState);
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(group,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ currState);
+
+ HighlightPainterUtils
+ .paintHighlight(graphics, null, group, new Rectangle(0, 0,
+ group.getWidth(), getTitleHeight(group)), 0.5f,
+ null, baseFillScheme, baseBorderScheme);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = stateEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = stateEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(group,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(group,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ activeState);
+
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ group, contribution, g));
+ HighlightPainterUtils.paintHighlight(graphics, null, group,
+ new Rectangle(0, 0, group.getWidth(),
+ getTitleHeight(group)), 0.5f, null, fillScheme,
+ borderScheme);
+ }
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTaskPaneUI$PaneBorder#getPaintColor
+ * (org.jdesktop.swingx.JXTaskPane)
+ */
+ @Override
+ protected Color getPaintColor(JXTaskPane group) {
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ ComponentState currState = stateTransitionTracker
+ .getModelStateInfo().getCurrModelState();
+
+ if (currState.isDisabled() || (activeStates.size() == 1))
+ return getColorSchemeForState(group, currState)
+ .getForegroundColor();
+
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : modelStateInfo
+ .getStateContributionMap().entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ SubstanceColorScheme scheme = getColorSchemeForState(group,
+ activeState);
+ Color schemeFg = scheme.getForegroundColor();
+ float contribution = activeEntry.getValue().getContribution();
+ aggrRed += schemeFg.getRed() * contribution;
+ aggrGreen += schemeFg.getGreen() * contribution;
+ aggrBlue += schemeFg.getBlue() * contribution;
+ }
+ return new Color((int) aggrRed, (int) aggrGreen, (int) aggrBlue);
+ }
+
+ private SubstanceColorScheme getColorSchemeForState(JXTaskPane group,
+ ComponentState state) {
+ ColorSchemeAssociationKind assocKind = ColorSchemeAssociationKind.FILL;
+ if (!state.isDisabled() && (state != ComponentState.ENABLED)) {
+ // use HIGHLIGHT
+ assocKind = ColorSchemeAssociationKind.HIGHLIGHT;
+ }
+ SubstanceColorScheme currScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(group, assocKind, state);
+ return currScheme;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.jdesktop.swingx.plaf.basic.BasicTaskPaneUI$PaneBorder#
+ * paintExpandedControls(org.jdesktop.swingx.JXTaskPane,
+ * java.awt.Graphics, int, int, int, int)
+ */
+ @Override
+ protected void paintExpandedControls(JXTaskPane group, Graphics g,
+ int x, int y, int width, int height) {
+ Icon arrowIcon = group.isCollapsed() ? collapsedIcon : expandedIcon;
+ int dx = (width - arrowIcon.getIconWidth()) / 2;
+ int dy = 1 + (height - arrowIcon.getIconHeight()) / 2;
+ arrowIcon.paintIcon(group, g, x + dx, y + dy);
+ // g.setColor(Color.red);
+ // g.drawRect(x, y, width, height);
+ // g.setColor(Color.green);
+ // g.drawRect(x + dx, y + dy, arrowIcon.getIconWidth(), arrowIcon
+ // .getIconHeight());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.jdesktop.swingx.plaf.basic.BasicTaskPaneUI$PaneBorder#
+ * isMouseOverBorder()
+ */
+ @Override
+ protected boolean isMouseOverBorder() {
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTaskPaneUI$PaneBorder#paintFocus
+ * (java.awt.Graphics, java.awt.Color, int, int, int, int)
+ */
+ @Override
+ protected void paintFocus(Graphics g, Color paintColor, int x, int y,
+ int width, int height) {
+ SubstanceCoreUtilities.paintFocus(g, group, group,
+ SubstanceTaskPaneUI.this, new Rectangle(x, y, width - 1,
+ height - 1), label.getBounds(), 1.0f, 0);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTaskPaneUI$PaneBorder#configureLabel
+ * (org.jdesktop.swingx.JXTaskPane)
+ */
+ @Override
+ protected void configureLabel(JXTaskPane group) {
+ label.applyComponentOrientation(group.getComponentOrientation());
+ label.setFont(group.getFont());
+ label.setText(group.getTitle());
+ label.setIcon(group.getIcon() == null ? new EmptyIcon() : group
+ .getIcon());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ this.bgDelegate.paint(c, (Graphics2D) g, false);
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return this.isInBorder(me);
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTaskPaneUI#getTitleHeight(java.awt
+ * .Component)
+ */
+ @Override
+ protected int getTitleHeight(Component c) {
+ return SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
+ .getComponentFontSize(c), super.getTitleHeight(c), 2, 3, false);
+ }
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTipOfTheDayUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTipOfTheDayUI.java
new file mode 100644
index 0000000..ced28e7
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTipOfTheDayUI.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import java.awt.BorderLayout;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.jdesktop.swingx.JXHeader;
+import org.jdesktop.swingx.JXTipOfTheDay;
+import org.jdesktop.swingx.plaf.UIManagerExt;
+import org.jdesktop.swingx.plaf.basic.BasicTipOfTheDayUI;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.animation.IconGlowTracker;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.icon.GlowingIcon;
+
+/**
+ * Substance-consistent UI delegate for {@link JXTipOfTheDay}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTipOfTheDayUI extends BasicTipOfTheDayUI {
+ static {
+ AnimationConfigurationManager.getInstance().allowAnimations(AnimationFacet.ICON_GLOW,
+ JXTipOfTheDay.class);
+ }
+
+ protected IconGlowTracker iconGlowTracker;
+
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceTipOfTheDayUI((JXTipOfTheDay) comp);
+ }
+
+ /**
+ * Creates a new UI delegate.
+ *
+ * @param tip
+ * Tip component.
+ */
+ public SubstanceTipOfTheDayUI(JXTipOfTheDay tip) {
+ super(tip);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTipOfTheDayUI#installComponents()
+ */
+ @Override
+ protected void installComponents() {
+ tipPane.setLayout(new BorderLayout());
+
+ // tip area
+ JPanel mainPane = new JPanel(new BorderLayout());
+ JXHeader didYouKnow = new JXHeader();
+ didYouKnow.setTitle(UIManagerExt.getString(
+ "TipOfTheDay.didYouKnowText", tipPane.getLocale()));
+ SubstanceLookAndFeel.setDecorationType(didYouKnow,
+ DecorationAreaType.GENERAL);
+
+ Icon infoIcon = SubstanceLookAndFeel.isToUseConstantThemesOnDialogs() ? SubstanceCoreUtilities
+ .getIcon("resource/22/dialog-information.png")
+ : SubstanceCoreUtilities.getThemedIcon(this.tipPane,
+ SubstanceCoreUtilities
+ .getIcon("resource/22/dialog-information.png"));
+
+ this.iconGlowTracker = new IconGlowTracker(didYouKnow);
+ didYouKnow.setIcon(new GlowingIcon(infoIcon, this.iconGlowTracker));
+ didYouKnow.setDescription("");
+
+ didYouKnow.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ iconGlowTracker.play();
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ iconGlowTracker.cancel();
+ }
+ });
+
+ mainPane.add("North", didYouKnow);
+
+ tipArea = new JPanel(new BorderLayout());
+ tipArea.setOpaque(false);
+ tipArea.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
+ tipArea.setBackground(UIManager.getColor("TextArea.background"));
+ mainPane.add("Center", tipArea);
+
+ tipPane.add("Center", mainPane);
+ }
+}
diff --git a/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTitledPanelUI.java b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTitledPanelUI.java
new file mode 100644
index 0000000..51f657f
--- /dev/null
+++ b/substance-swingx/src/main/java/org/pushingpixels/substance/swingx/SubstanceTitledPanelUI.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2005-2010 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.pushingpixels.substance.swingx;
+
+import javax.swing.JComponent;
+import javax.swing.border.*;
+import javax.swing.plaf.*;
+
+import org.jdesktop.swingx.JXPanel;
+import org.jdesktop.swingx.JXTitledPanel;
+import org.jdesktop.swingx.plaf.basic.BasicTitledPanelUI;
+import org.pushingpixels.lafwidget.utils.ShadowPopupBorder;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.border.SubstanceBorder;
+
+/**
+ * Substance-consistent UI delegate for {@link JXTitledPanel}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTitledPanelUI extends BasicTitledPanelUI {
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceTitledPanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTitledPanelUI#installComponents(org
+ * .jdesktop.swingx.JXTitledPanel)
+ */
+ @Override
+ protected void installComponents(JXTitledPanel titledPanel) {
+ super.installComponents(titledPanel);
+
+ Border currBorder = titledPanel.getBorder();
+ if ((currBorder == null) || (currBorder instanceof UIResource)) {
+ titledPanel
+ .setBorder(new BorderUIResource.CompoundBorderUIResource(
+ new EmptyBorder(2, 2, 2, 2), new CompoundBorder(
+ ShadowPopupBorder.getInstance(),
+ new SubstanceBorder())));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTitledPanelUI#createAndConfigureTopPanel
+ * (org.jdesktop.swingx.JXTitledPanel)
+ */
+ @Override
+ protected JXPanel createAndConfigureTopPanel(JXTitledPanel titledPanel) {
+ JXPanel result = super.createAndConfigureTopPanel(titledPanel);
+ SubstanceLookAndFeel.setDecorationType(result,
+ DecorationAreaType.HEADER);
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jdesktop.swingx.plaf.basic.BasicTitledPanelUI#uninstallDefaults(org
+ * .jdesktop.swingx.JXTitledPanel)
+ */
+ @Override
+ protected void uninstallDefaults(JXTitledPanel titledPanel) {
+ DecorationPainterUtils.clearDecorationType(topPanel);
+ super.uninstallDefaults(titledPanel);
+ }
+}
diff --git a/substance-swingx/src/main/resources/META-INF/substance-plugin.xml b/substance-swingx/src/main/resources/META-INF/substance-plugin.xml
new file mode 100644
index 0000000..cc1ed1b
--- /dev/null
+++ b/substance-swingx/src/main/resources/META-INF/substance-plugin.xml
@@ -0,0 +1,3 @@
+<laf-plugin>
+ <component-plugin-class>org.pushingpixels.substance.swingx.SubstanceSwingxPlugin</component-plugin-class>
+</laf-plugin>
diff --git a/substance-swingx/src/main/resources/resource/22/dialog-information.png b/substance-swingx/src/main/resources/resource/22/dialog-information.png
new file mode 100644
index 0000000..07cf010
Binary files /dev/null and b/substance-swingx/src/main/resources/resource/22/dialog-information.png differ
diff --git a/substance-swingx/src/main/resources/resource/32/login-new32.png b/substance-swingx/src/main/resources/resource/32/login-new32.png
new file mode 100644
index 0000000..027e912
Binary files /dev/null and b/substance-swingx/src/main/resources/resource/32/login-new32.png differ
diff --git a/substance-swingx/src/tools/java/docrobot/Configure.java b/substance-swingx/src/tools/java/docrobot/Configure.java
new file mode 100644
index 0000000..0cba587
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/Configure.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.awt.Window;
+
+public interface Configure {
+ public void configure(Window window);
+}
diff --git a/substance-swingx/src/tools/java/docrobot/ErrorPaneRobot.java b/substance-swingx/src/tools/java/docrobot/ErrorPaneRobot.java
new file mode 100644
index 0000000..bfa946f
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/ErrorPaneRobot.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.*;
+import org.fest.swing.timing.Condition;
+import org.fest.swing.timing.Pause;
+import org.jdesktop.swingx.JXErrorPane;
+import org.jdesktop.swingx.error.ErrorInfo;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+
+/**
+ * The base class for taking a single screenshot for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ErrorPaneRobot {
+ /**
+ * The associated Substance skin.
+ */
+ protected SubstanceSkin skin;
+
+ /**
+ * The screenshot filename.
+ */
+ protected String screenshotFilename;
+
+ protected JDialog dialog;
+
+ protected Robot robot;
+
+ /**
+ * Creates the new screenshot robot.
+ *
+ * @param skin
+ * Substance skin.
+ * @param screenshotFilename
+ * The screenshot filename.
+ */
+ public ErrorPaneRobot(SubstanceSkin skin, String screenshotFilename,
+ Robot robot) {
+ this.skin = skin;
+ this.screenshotFilename = screenshotFilename;
+ this.robot = robot;
+ }
+
+ /**
+ * Runs the screenshot process.
+ */
+ public void run() {
+ long start = System.currentTimeMillis();
+ // set the skin
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ SubstanceLookAndFeel.setSkin(skin);
+ JDialog.setDefaultLookAndFeelDecorated(true);
+ }
+ });
+ robot.waitForIdle();
+
+ final Frame owner = GuiActionRunner.execute(new GuiQuery<Frame>() {
+ @Override
+ protected Frame executeInEDT() throws Throwable {
+ return JOptionPane.getRootFrame();
+ }
+ });
+
+ // show dialog
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ try {
+ URL url = new URL("some wrong URL string");
+ } catch (MalformedURLException murle) {
+ String msg = "<html>An error just happened. Possible reasons:"
+ + "<ol><li>Development team hoped nobody would notice."
+ + "<li>The testers missed this scenario. Wait, we don't have testers."
+ + "<li>Didn't your momma teach you not to use Linux?"
+ + "</ol>"
+ + "In any case, it's all open source so it's all good. Fix it yourself.";
+ String details = "<html>Web resources should begin with \"http://\""
+ + " and cannot contain any spaces. Below are a few"
+ + " more guidelines.<ul></ul></html>";
+ JXErrorPane.showDialog(owner, new ErrorInfo(
+ "JXErrorPane example", msg, details, null, murle,
+ null, null));
+ }
+ }
+ });
+ robot.waitForIdle();
+
+ // get dialog
+ this.dialog = GuiActionRunner.execute(new GuiQuery<JDialog>() {
+ @Override
+ protected JDialog executeInEDT() throws Throwable {
+ return (JDialog) owner.getOwnedWindows()[owner
+ .getOwnedWindows().length - 1];
+ }
+ });
+ robot.waitForIdle();
+
+ // get the close button
+ final JButton closeButton = GuiActionRunner
+ .execute(new GuiQuery<JButton>() {
+ @Override
+ protected JButton executeInEDT() throws Throwable {
+ return getLoginButton(dialog.getContentPane());
+ }
+
+ private JButton getLoginButton(Component comp) {
+ if (comp instanceof JButton) {
+ JButton button = (JButton) comp;
+ if ("Close".equals(button.getText())) {
+ return button;
+ }
+ }
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++) {
+ JButton result = getLoginButton(cont
+ .getComponent(i));
+ if (result != null)
+ return result;
+ }
+ }
+ return null;
+ }
+ });
+
+ // move the mouse to the close button
+ if (closeButton != null) {
+ robot.moveMouse(closeButton);
+ }
+
+ // make screenshot
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ makeScreenshot();
+ }
+ });
+
+ // click the button
+ if (closeButton != null) {
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ closeButton.doClick();
+ }
+ });
+ }
+
+ Pause.pause(new Condition("Wait for the close button click") {
+ @Override
+ public boolean test() {
+ return !dialog.isDisplayable();
+ }
+ });
+
+ long end = System.currentTimeMillis();
+ System.out.println(this.getClass().getSimpleName() + " ["
+ + this.skin.getDisplayName() + "] : " + (end - start) + "ms");
+ }
+
+ /**
+ * Creates the screenshot and saves it on the disk.
+ */
+ public void makeScreenshot() {
+ BufferedImage bi = new BufferedImage(dialog.getWidth(), dialog
+ .getHeight(), BufferedImage.TYPE_INT_ARGB);
+ Map<Component, Boolean> map = new HashMap<Component, Boolean>();
+ RobotUtilities.makePreviewable(dialog, map);
+ Graphics g = bi.getGraphics();
+ dialog.paint(g);
+ try {
+ ImageIO
+ .write(bi, "png",
+ new File(this.screenshotFilename + ".png"));
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/ErrorPaneRunner.java b/substance-swingx/src/tools/java/docrobot/ErrorPaneRunner.java
new file mode 100644
index 0000000..4503df7
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/ErrorPaneRunner.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.awt.Component;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.skin.SkinInfo;
+import org.pushingpixels.substance.swingx.SubstanceErrorPaneUI;
+
+/**
+ * The main method for taking screenshots for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ErrorPaneRunner {
+ public static class MyErrorPaneUI extends SubstanceErrorPaneUI {
+ public static ComponentUI createUI(JComponent c) {
+ return new MyErrorPaneUI();
+ }
+
+ @Override
+ public JDialog getErrorDialog(Component owner) {
+ JDialog result = super.getErrorDialog(owner);
+ result.setModal(false);
+ return result;
+ }
+ }
+
+ /**
+ * Runs the specified screenshot robot.
+ *
+ * @param args
+ * Ignored.
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ UIManager.put("ErrorPaneUI", MyErrorPaneUI.class.getName());
+
+ String subfolder = "errorpane";
+ Map<String, SkinInfo> skins = SubstanceLookAndFeel.getAllSkins();
+ Robot robot = BasicRobot.robotWithNewAwtHierarchy();
+ for (Map.Entry<String, SkinInfo> skinEntry : skins.entrySet()) {
+ String key = skinEntry.getKey();
+ String normalized = key.toLowerCase().replaceAll(" ", "");
+ new ErrorPaneRobot((SubstanceSkin) Class.forName(
+ skinEntry.getValue().getClassName()).newInstance(),
+ "C:/jprojects/substance-swingx/www/images/" + subfolder
+ + "/" + normalized, robot).run();
+ }
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/FrameRobot.java b/substance-swingx/src/tools/java/docrobot/FrameRobot.java
new file mode 100644
index 0000000..1ee4535
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/FrameRobot.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.awt.Graphics;
+import java.awt.KeyboardFocusManager;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.GuiActionRunner;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.timing.Condition;
+import org.fest.swing.timing.Pause;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+
+/**
+ * The base class for taking a single screenshot for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class FrameRobot {
+ /**
+ * The associated Substance skin.
+ */
+ protected SubstanceSkin skin;
+
+ /**
+ * The screenshot filename.
+ */
+ protected String screenshotFilename;
+
+ /**
+ * The frame class name.
+ */
+ protected String frameClass;
+
+ /**
+ * Indicates whether the screenshot process is complete.
+ */
+ protected boolean done = false;
+
+ protected JFrame frame;
+
+ private Robot robot;
+
+ /**
+ * Creates the new screenshot robot.
+ *
+ * @param skin
+ * Substance skin.
+ * @param screenshotFilename
+ * The screenshot filename.
+ */
+ public FrameRobot(String frameClass, SubstanceSkin skin,
+ String screenshotFilename, Robot robot) {
+ this.frameClass = frameClass;
+ this.skin = skin;
+ this.screenshotFilename = screenshotFilename;
+ this.robot = robot;
+ }
+
+ /**
+ * Runs the screenshot process.
+ */
+ public void run() {
+ long start = System.currentTimeMillis();
+
+ // set the skin
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ SubstanceLookAndFeel.setSkin(skin);
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ }
+ });
+ robot.waitForIdle();
+
+ // create the frame
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ try {
+ frame = (JFrame) Class.forName(frameClass).newInstance();
+ } catch (Exception exc) {
+ exc.printStackTrace();
+ }
+ frame.setIconImage(SubstanceImageCreator.getColorSchemeImage(
+ null, new ImageIcon(FrameRobot.class.getClassLoader()
+ .getResource(
+ "test/resource/image-x-generic.png")),
+ SubstanceLookAndFeel
+ .getCurrentSkin(frame.getRootPane())
+ .getActiveColorScheme(
+ DecorationAreaType.NONE), 0.0f));
+
+ frame.setVisible(true);
+ }
+ });
+ robot.waitForIdle();
+
+ // remove the focus ring
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ KeyboardFocusManager.getCurrentKeyboardFocusManager()
+ .clearGlobalFocusOwner();
+ }
+ });
+
+ // make the screenshot
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ makeScreenshot();
+ }
+ });
+ robot.waitForIdle();
+
+ // dispose the frame
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ frame.dispose();
+ }
+ });
+ robot.waitForIdle();
+
+ // wait for the frame to become disposed
+ Pause.pause(new Condition("Wait for the frame to become disposed") {
+ @Override
+ public boolean test() {
+ return !frame.isDisplayable();
+ }
+ });
+
+ long end = System.currentTimeMillis();
+ System.out.println(this.getClass().getSimpleName() + " ["
+ + skin.getDisplayName() + "] : " + (end - start) + "ms");
+ }
+
+ /**
+ * Creates the screenshot and saves it on the disk.
+ */
+ public void makeScreenshot() {
+ BufferedImage bi = new BufferedImage(frame.getWidth(), frame
+ .getHeight(), BufferedImage.TYPE_INT_ARGB);
+ Graphics g = bi.getGraphics();
+ frame.paint(g);
+ try {
+ ImageIO.write(bi, "png", new File(screenshotFilename + ".png"));
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/HeaderFrame.java b/substance-swingx/src/tools/java/docrobot/HeaderFrame.java
new file mode 100644
index 0000000..356b065
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/HeaderFrame.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.awt.BorderLayout;
+import java.net.URL;
+
+import javax.swing.*;
+
+import org.jdesktop.swingx.JXHeader;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.skin.SubstanceBusinessBlackSteelLookAndFeel;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+
+public class HeaderFrame extends JFrame {
+ public HeaderFrame() {
+ super("JXHeader example");
+
+ this.setLayout(new BorderLayout());
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ URL iconUrl = cl.getResource("docrobot/applications-internet.png");
+ Icon icon = new ImageIcon(iconUrl);
+ icon = new ImageIcon(SubstanceImageCreator.getColorSchemeImage(null,
+ icon, SubstanceLookAndFeel.getCurrentSkin(this.getRootPane())
+ .getActiveColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE), 0.0f));
+ String title = "LGPL license";
+ String description = "This library is free software; you can redistribute it and/or"
+ + " modify it under the terms of the GNU Lesser General Public"
+ + " License.";
+
+ final JXHeader header = new JXHeader(title, description, icon);
+ this.add(header, BorderLayout.NORTH);
+
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ this.setSize(440, 300);
+ this.setLocationRelativeTo(null);
+ }
+
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ UIManager.setLookAndFeel(new SubstanceBusinessBlackSteelLookAndFeel());
+ // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ new HeaderFrame().setVisible(true);
+ }
+ });
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/HeaderRunner.java b/substance-swingx/src/tools/java/docrobot/HeaderRunner.java
new file mode 100644
index 0000000..5bbb9b9
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/HeaderRunner.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.util.Map;
+
+import javax.swing.JFrame;
+
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.skin.SkinInfo;
+
+/**
+ * The main method for taking screenshots for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class HeaderRunner {
+ /**
+ * Runs the specified screenshot robot.
+ *
+ * @param args
+ * Ignored.
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ String frameClass = "docrobot.HeaderFrame";
+ String subfolder = "header";
+ Map<String, SkinInfo> skins = SubstanceLookAndFeel.getAllSkins();
+ Robot robot = BasicRobot.robotWithNewAwtHierarchy();
+ for (Map.Entry<String, SkinInfo> skinEntry : skins.entrySet()) {
+ String key = skinEntry.getKey();
+ String normalized = key.toLowerCase().replaceAll(" ", "");
+ new FrameRobot(frameClass, (SubstanceSkin) Class.forName(
+ skinEntry.getValue().getClassName()).newInstance(),
+ "C:/jprojects/substance-swingx/www/images/" + subfolder
+ + "/" + normalized, robot).run();
+ }
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/LoginDialogRobot.java b/substance-swingx/src/tools/java/docrobot/LoginDialogRobot.java
new file mode 100644
index 0000000..930adc3
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/LoginDialogRobot.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.*;
+import org.fest.swing.timing.Condition;
+import org.fest.swing.timing.Pause;
+import org.jdesktop.swingx.JXLoginPane;
+import org.jdesktop.swingx.JXLoginPane.JXLoginDialog;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+
+/**
+ * The base class for taking a single screenshot for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LoginDialogRobot {
+ /**
+ * The associated Substance skin.
+ */
+ protected SubstanceSkin skin;
+
+ /**
+ * The screenshot filename.
+ */
+ protected String screenshotFilename;
+
+ protected JXLoginDialog dialog;
+
+ private Robot robot;
+
+ /**
+ * Creates the new screenshot robot.
+ *
+ * @param skin
+ * Substance skin.
+ * @param screenshotFilename
+ * The screenshot filename.
+ */
+ public LoginDialogRobot(SubstanceSkin skin, String screenshotFilename,
+ Robot robot) {
+ this.skin = skin;
+ this.screenshotFilename = screenshotFilename;
+ this.robot = robot;
+ }
+
+ /**
+ * Runs the screenshot process.
+ */
+ public void run() {
+ long start = System.currentTimeMillis();
+
+ // set the skin
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ SubstanceLookAndFeel.setSkin(skin);
+ JDialog.setDefaultLookAndFeelDecorated(true);
+ }
+ });
+ robot.waitForIdle();
+
+ // create the dialog
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ JXLoginPane loginPane = new JXLoginPane();
+ loginPane.setMessage("Sample login message");
+ loginPane.setBannerText("Sample banner");
+
+ dialog = new JXLoginDialog((Frame) null, loginPane);
+ dialog.setVisible(true);
+ }
+ });
+ robot.waitForIdle();
+
+ // wait one second
+ Pause.pause(1000);
+
+ // get the login button
+ final JButton loginButton = GuiActionRunner
+ .execute(new GuiQuery<JButton>() {
+ @Override
+ protected JButton executeInEDT() throws Throwable {
+ return getLoginButton(dialog.getContentPane());
+ }
+
+ private JButton getLoginButton(Component comp) {
+ if (comp instanceof JButton) {
+ JButton button = (JButton) comp;
+ if ("Login".equals(button.getText())) {
+ return button;
+ }
+ }
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++) {
+ JButton result = getLoginButton(cont
+ .getComponent(i));
+ if (result != null)
+ return result;
+ }
+ }
+ return null;
+ }
+ });
+
+ // move the mouse to the login button
+ if (loginButton != null) {
+ robot.moveMouse(loginButton);
+ }
+
+ // make screenshot
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ makeScreenshot();
+ }
+ });
+
+ // click the button
+ if (loginButton != null) {
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ loginButton.doClick();
+ }
+ });
+ }
+
+ Pause.pause(new Condition("Wait for the login button click") {
+ @Override
+ public boolean test() {
+ return !dialog.isDisplayable();
+ }
+ });
+
+ long end = System.currentTimeMillis();
+ System.out.println(this.getClass().getSimpleName() + " ["
+ + this.skin.getDisplayName() + "] : " + (end - start) + "ms");
+ }
+
+ private boolean dismiss(Component comp) {
+ if (comp instanceof JButton) {
+ JButton button = (JButton) comp;
+ if ("Login".equals(button.getText())) {
+ button.doClick();
+ return true;
+ }
+ }
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++) {
+ if (dismiss(cont.getComponent(i)))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Creates the screenshot and saves it on the disk.
+ */
+ public void makeScreenshot() {
+ BufferedImage bi = new BufferedImage(dialog.getWidth(), dialog
+ .getHeight(), BufferedImage.TYPE_INT_ARGB);
+ Map<Component, Boolean> map = new HashMap<Component, Boolean>();
+ RobotUtilities.makePreviewable(dialog.getRootPane(), map);
+ Graphics g = bi.getGraphics();
+ dialog.paint(g);
+ try {
+ ImageIO
+ .write(bi, "png",
+ new File(this.screenshotFilename + ".png"));
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/LoginDialogRunner.java b/substance-swingx/src/tools/java/docrobot/LoginDialogRunner.java
new file mode 100644
index 0000000..2d2e456
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/LoginDialogRunner.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.util.Map;
+
+import javax.swing.JFrame;
+
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.skin.SkinInfo;
+
+/**
+ * The main method for taking screenshots for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LoginDialogRunner {
+ /**
+ * Runs the specified screenshot robot.
+ *
+ * @param args
+ * Ignored.
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ String subfolder = "logindialog";
+ Map<String, SkinInfo> skins = SubstanceLookAndFeel.getAllSkins();
+ Robot robot = BasicRobot.robotWithNewAwtHierarchy();
+ for (Map.Entry<String, SkinInfo> skinEntry : skins.entrySet()) {
+ String key = skinEntry.getKey();
+ String normalized = key.toLowerCase().replaceAll(" ", "");
+ new LoginDialogRobot((SubstanceSkin) Class.forName(
+ skinEntry.getValue().getClassName()).newInstance(),
+ "C:/jprojects/substance-swingx/www/images/" + subfolder
+ + "/" + normalized, robot).run();
+ }
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/MonthViewFrame.java b/substance-swingx/src/tools/java/docrobot/MonthViewFrame.java
new file mode 100644
index 0000000..13e9cc4
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/MonthViewFrame.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.awt.FlowLayout;
+import java.util.Calendar;
+
+import javax.swing.*;
+
+import org.jdesktop.swingx.JXMonthView;
+import org.pushingpixels.substance.api.skin.SubstanceBusinessBlackSteelLookAndFeel;
+
+public class MonthViewFrame extends JFrame {
+ public MonthViewFrame() {
+ super("JXMonthView example");
+
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.DAY_OF_MONTH, 4);
+ JXMonthView monthView = new JXMonthView();
+ monthView.setShowingLeadingDays(true);
+ monthView.setShowingTrailingDays(true);
+ monthView.setShowingWeekNumber(true);
+
+ this.setLayout(new FlowLayout(FlowLayout.CENTER));
+ this.add(monthView);
+
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ this.setSize(350, 250);
+ this.setLocationRelativeTo(null);
+ }
+
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ UIManager.setLookAndFeel(new SubstanceBusinessBlackSteelLookAndFeel());
+ // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ new MonthViewFrame().setVisible(true);
+ }
+ });
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/MonthViewRunner.java b/substance-swingx/src/tools/java/docrobot/MonthViewRunner.java
new file mode 100644
index 0000000..0aae9d0
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/MonthViewRunner.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.util.Map;
+
+import javax.swing.JFrame;
+
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.skin.SkinInfo;
+
+/**
+ * The main method for taking screenshots for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class MonthViewRunner {
+ /**
+ * Runs the specified screenshot robot.
+ *
+ * @param args
+ * Ignored.
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+
+ String frameClass = "docrobot.MonthViewFrame";
+ String subfolder = "monthview";
+ Map<String, SkinInfo> skins = SubstanceLookAndFeel.getAllSkins();
+ Robot robot = BasicRobot.robotWithNewAwtHierarchy();
+ for (Map.Entry<String, SkinInfo> skinEntry : skins.entrySet()) {
+ String key = skinEntry.getKey();
+ String normalized = key.toLowerCase().replaceAll(" ", "");
+ new FrameRobot(frameClass, (SubstanceSkin) Class.forName(
+ skinEntry.getValue().getClassName()).newInstance(),
+ "C:/jprojects/substance-swingx/www/images/" + subfolder
+ + "/" + normalized, robot).run();
+ }
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/RobotUtilities.java b/substance-swingx/src/tools/java/docrobot/RobotUtilities.java
new file mode 100644
index 0000000..d8e536a
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/RobotUtilities.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.util.Map;
+
+import javax.swing.JComponent;
+
+public class RobotUtilities {
+ /**
+ * Makes the specified component and all its descendants previewable.
+ *
+ * @param comp
+ * Component.
+ * @param dbSnapshot
+ * The "snapshot" map that will contain the original
+ * double-buffer status of the specified component and all its
+ * descendants. Key is {@link JComponent}, value is
+ * {@link Boolean}.
+ */
+ public static void makePreviewable(Component comp,
+ Map<Component, Boolean> dbSnapshot) {
+ if (comp instanceof JComponent) {
+ JComponent jcomp = (JComponent) comp;
+ // if (jcomp.getParent() instanceof CellRendererPane) {
+ // System.out.println(jcomp.getClass().getSimpleName() + ":"
+ // + jcomp.hashCode());
+ // }
+ dbSnapshot.put(jcomp, Boolean.valueOf(jcomp.isDoubleBuffered()));
+ jcomp.setDoubleBuffered(false);
+ }
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++)
+ RobotUtilities
+ .makePreviewable(cont.getComponent(i), dbSnapshot);
+ }
+ }
+
+ /**
+ * Restores the regular (non-previewable) status of the specified component
+ * and all its descendants.
+ *
+ * @param comp
+ * Component.
+ * @param dbSnapshot
+ * The "snapshot" map that contains the original double-buffer
+ * status of the specified component and all its descendants. Key
+ * is {@link JComponent}, value is {@link Boolean}.
+ */
+ public static void restorePreviewable(Component comp,
+ Map<Component, Boolean> dbSnapshot) {
+ if (comp instanceof JComponent) {
+ JComponent jcomp = (JComponent) comp;
+ if (dbSnapshot.containsKey(comp)) {
+ jcomp.setDoubleBuffered(dbSnapshot.get(comp));
+ } else {
+ // this can happen in case the application has
+ // renderers (combos, ...). Take the property from the parent
+ Component parent = comp.getParent();
+ if (parent instanceof JComponent) {
+ jcomp.setDoubleBuffered(dbSnapshot.get(parent));
+ }
+ }
+ }
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++)
+ RobotUtilities.restorePreviewable(cont.getComponent(i),
+ dbSnapshot);
+ }
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/StatusBarFrame.java b/substance-swingx/src/tools/java/docrobot/StatusBarFrame.java
new file mode 100644
index 0000000..e3d2c19
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/StatusBarFrame.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.swing.*;
+
+import org.jdesktop.swingx.JXStatusBar;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.skin.TwilightSkin;
+
+public class StatusBarFrame extends JFrame {
+ public StatusBarFrame() {
+ super("JXStatusBar example");
+
+ this.setLayout(new BorderLayout());
+
+ JMenuBar jmb = new JMenuBar();
+ jmb.add(new JMenu("File"));
+ jmb.add(new JMenu("Edit"));
+ jmb.add(new JMenu("Source"));
+ jmb.add(new JMenu("Refactor"));
+ jmb.add(new JMenu("Navigate"));
+ jmb.add(new JMenu("Search"));
+ jmb.add(new JMenu("Project"));
+ this.setJMenuBar(jmb);
+
+ this.add(getToolbar("", 22), BorderLayout.NORTH);
+
+ JXStatusBar statusBar = new JXStatusBar();
+ this.add(statusBar, BorderLayout.SOUTH);
+
+ JLabel statusLabel = new JLabel("Smart Insert");
+ JXStatusBar.Constraint c1 = new JXStatusBar.Constraint();
+ c1.setFixedWidth(100);
+ statusBar.add(statusLabel, c1);
+
+ JXStatusBar.Constraint c2 = new JXStatusBar.Constraint(
+ JXStatusBar.Constraint.ResizeBehavior.FILL);
+ SimpleDateFormat sdf = new SimpleDateFormat("MMMMM dd, yyyy hh:mm aaa");
+ final JLabel tabLabel = new JLabel("Created on "
+ + sdf.format(new Date()));
+ statusBar.add(tabLabel, c2);
+
+ JLabel statusLabel2 = new JLabel("5 new messages");
+ JXStatusBar.Constraint c3 = new JXStatusBar.Constraint();
+ c3.setFixedWidth(90);
+ statusBar.add(statusLabel2, c3);
+
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ this.setSize(500, 350);
+ this.setLocationRelativeTo(null);
+ }
+
+ public static JToolBar getToolbar(String label, int size) {
+ JToolBar toolBar = new JToolBar();
+ // toolBar.setLayout(new BoxLayout(toolBar,BoxLayout.LINE_AXIS));
+ // toolBar.setFloatable(false);
+
+ JButton buttonCut = new JButton(getIcon(size + "/edit-cut"));
+ toolBar.add(buttonCut);
+ JButton buttonCopy = new JButton(getIcon(size + "/edit-copy"));
+ toolBar.add(buttonCopy);
+ JButton buttonPaste = new JButton(getIcon(size + "/edit-paste"));
+ toolBar.add(buttonPaste);
+ JButton buttonSelectAll = new JButton(
+ getIcon(size + "/edit-select-all"));
+ toolBar.add(buttonSelectAll);
+ JButton buttonDelete = new JButton(getIcon(size + "/edit-delete"));
+ toolBar.add(buttonDelete);
+ toolBar.addSeparator();
+
+ JToggleButton buttonFormatCenter = new JToggleButton(getIcon(size
+ + "/format-justify-center"));
+ toolBar.add(buttonFormatCenter);
+ JToggleButton buttonFormatLeft = new JToggleButton(getIcon(size
+ + "/format-justify-left"));
+ toolBar.add(buttonFormatLeft);
+ JToggleButton buttonFormatRight = new JToggleButton(getIcon(size
+ + "/format-justify-right"));
+ toolBar.add(buttonFormatRight);
+ JToggleButton buttonFormatFill = new JToggleButton(getIcon(size
+ + "/format-justify-fill"));
+ toolBar.add(buttonFormatFill);
+ toolBar.addSeparator();
+
+ toolBar.add(Box.createGlue());
+ JButton buttonExit = new JButton(getIcon(size + "/process-stop"));
+ buttonExit.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ System.exit(0);
+ }
+ });
+ toolBar.add(buttonExit);
+
+ return toolBar;
+ }
+
+ public static Icon getIcon(String iconName) {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ URL url = cl.getResource("test/check/icons/" + iconName + ".gif");
+ if (url != null)
+ return new ImageIcon(url);
+ url = cl.getResource("test/check/icons/" + iconName + ".png");
+ if (url != null)
+ return new ImageIcon(url);
+ return null;
+ }
+
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ SubstanceLookAndFeel.setSkin(new TwilightSkin());
+ // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ new StatusBarFrame().setVisible(true);
+ }
+ });
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/StatusBarRunner.java b/substance-swingx/src/tools/java/docrobot/StatusBarRunner.java
new file mode 100644
index 0000000..7d40004
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/StatusBarRunner.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.util.Map;
+
+import javax.swing.JFrame;
+
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.skin.SkinInfo;
+
+/**
+ * The main method for taking screenshots for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class StatusBarRunner {
+ /**
+ * Runs the specified screenshot robot.
+ *
+ * @param args
+ * Ignored.
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+
+ String frameClass = "docrobot.StatusBarFrame";
+ String subfolder = "statusbar";
+ Map<String, SkinInfo> skins = SubstanceLookAndFeel.getAllSkins();
+ Robot robot = BasicRobot.robotWithNewAwtHierarchy();
+
+ for (Map.Entry<String, SkinInfo> skinEntry : skins.entrySet()) {
+ String key = skinEntry.getKey();
+ String normalized = key.toLowerCase().replaceAll(" ", "");
+ try {
+ new FrameRobot(frameClass, (SubstanceSkin) Class.forName(
+ skinEntry.getValue().getClassName()).newInstance(),
+ "C:/jprojects/substance-swingx/www/images/" + subfolder
+ + "/" + normalized, robot).run();
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/TaskPaneFrame.java b/substance-swingx/src/tools/java/docrobot/TaskPaneFrame.java
new file mode 100644
index 0000000..3b2a716
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/TaskPaneFrame.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+
+import org.jdesktop.swingx.JXHyperlink;
+import org.jdesktop.swingx.JXTaskPane;
+import org.jdesktop.swingx.JXTaskPaneContainer;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceConstants.MenuGutterFillKind;
+import org.pushingpixels.substance.api.renderers.SubstanceDefaultListCellRenderer;
+import org.pushingpixels.substance.api.skin.SubstanceBusinessBlackSteelLookAndFeel;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+import com.jgoodies.forms.builder.DefaultFormBuilder;
+import com.jgoodies.forms.layout.FormLayout;
+
+public class TaskPaneFrame extends JFrame {
+ public TaskPaneFrame() {
+ super("JXTaskPane example");
+
+ this.setLayout(new BorderLayout());
+
+ JXTaskPaneContainer container = new JXTaskPaneContainer();
+
+ JXTaskPane taskPane1 = new JXTaskPane();
+ taskPane1.setTitle("Task pane 1");
+ // taskPane1.setSpecial(true);
+ container.add(taskPane1);
+
+ FormLayout lm = new FormLayout("right:pref, 4dlu, fill:pref:grow", "");
+ DefaultFormBuilder builder = new DefaultFormBuilder(lm);
+
+ builder.appendSeparator("Miscellaneous");
+
+ final JCheckBox useThemedDefaultIconsCheckBox = new JCheckBox(
+ "themed icons");
+ useThemedDefaultIconsCheckBox.setSelected(SubstanceCoreUtilities
+ .useThemedDefaultIcon(null));
+ builder.append("Themed icons", useThemedDefaultIconsCheckBox);
+
+ final JCheckBox useConstantThemesOnDialogs = new JCheckBox(
+ "constant themes");
+ useConstantThemesOnDialogs.setSelected(SubstanceLookAndFeel
+ .isToUseConstantThemesOnDialogs());
+ builder.append("Pane icons", useConstantThemesOnDialogs);
+
+ final JComboBox placementCombo = new JComboBox(new Object[] { "top",
+ "bottom", "left", "right" });
+ builder.append("Placement", placementCombo);
+
+ final JComboBox menuGutterFillCombo = new JComboBox(new Object[] {
+ MenuGutterFillKind.NONE, MenuGutterFillKind.SOFT,
+ MenuGutterFillKind.HARD, MenuGutterFillKind.SOFT_FILL,
+ MenuGutterFillKind.HARD_FILL });
+ menuGutterFillCombo.setRenderer(new SubstanceDefaultListCellRenderer() {
+ @Override
+ public Component getListCellRendererComponent(JList list,
+ Object value, int index, boolean isSelected,
+ boolean cellHasFocus) {
+ MenuGutterFillKind mgfk = (MenuGutterFillKind) value;
+ return super.getListCellRendererComponent(list, mgfk.name()
+ .toLowerCase(), index, isSelected, cellHasFocus);
+ }
+ });
+ menuGutterFillCombo.setSelectedItem(SubstanceCoreUtilities
+ .getMenuGutterFillKind());
+ builder.append("Menu fill", menuGutterFillCombo);
+
+ taskPane1.add(builder.getPanel());
+
+ JXTaskPane taskPane2 = new JXTaskPane();
+ taskPane2.setTitle("Task pane 2");
+ container.add(taskPane2);
+
+ JXHyperlink link = new JXHyperlink();
+ link.setText("Hyper link");
+ taskPane2.add(link);
+
+ JXTaskPane taskPane3 = new JXTaskPane();
+ taskPane3.setTitle("Collapsed");
+ taskPane3.setCollapsed(true);
+ container.add(taskPane3);
+
+ this.add(container, BorderLayout.WEST);
+
+ JMenuBar jmb = new JMenuBar();
+ jmb.add(new JMenu("File"));
+ jmb.add(new JMenu("Edit"));
+ jmb.add(new JMenu("Source"));
+ jmb.add(new JMenu("Refactor"));
+ jmb.add(new JMenu("Navigate"));
+ jmb.add(new JMenu("Search"));
+ jmb.add(new JMenu("Project"));
+ this.setJMenuBar(jmb);
+
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ this.setSize(500, 350);
+ this.setLocationRelativeTo(null);
+ }
+
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ UIManager.setLookAndFeel(new SubstanceBusinessBlackSteelLookAndFeel());
+ // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ new TaskPaneFrame().setVisible(true);
+ }
+ });
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/TaskPaneRunner.java b/substance-swingx/src/tools/java/docrobot/TaskPaneRunner.java
new file mode 100644
index 0000000..5a5aaa6
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/TaskPaneRunner.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.util.Map;
+
+import javax.swing.JFrame;
+
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.skin.SkinInfo;
+
+/**
+ * The main method for taking screenshots for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TaskPaneRunner {
+ /**
+ * Runs the specified screenshot robot.
+ *
+ * @param args
+ * Ignored.
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+
+ String frameClass = "docrobot.TaskPaneFrame";
+ String subfolder = "taskpane";
+ Map<String, SkinInfo> skins = SubstanceLookAndFeel.getAllSkins();
+ Robot robot = BasicRobot.robotWithNewAwtHierarchy();
+ for (Map.Entry<String, SkinInfo> skinEntry : skins.entrySet()) {
+ String key = skinEntry.getKey();
+ String normalized = key.toLowerCase().replaceAll(" ", "");
+ new FrameRobot(frameClass, (SubstanceSkin) Class.forName(
+ skinEntry.getValue().getClassName()).newInstance(),
+ "C:/jprojects/substance-swingx/www/images/" + subfolder
+ + "/" + normalized, robot).run();
+ }
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/TitledPanelFrame.java b/substance-swingx/src/tools/java/docrobot/TitledPanelFrame.java
new file mode 100644
index 0000000..369deef
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/TitledPanelFrame.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+
+import javax.swing.*;
+
+import org.jdesktop.swingx.JXTitledPanel;
+import org.pushingpixels.substance.api.skin.SubstanceBusinessBlackSteelLookAndFeel;
+
+public class TitledPanelFrame extends JFrame {
+ public TitledPanelFrame() {
+ super("JXTitledPanel example");
+
+ this.setLayout(new BorderLayout());
+
+ this.add(new JXTitledPanel("Left panel"), BorderLayout.WEST);
+ JPanel center = new JPanel(new GridLayout(2, 1, 0, 0));
+ center.add(new JXTitledPanel("Top panel"));
+ center.add(new JXTitledPanel("Bottom panel"));
+ this.add(center, BorderLayout.CENTER);
+
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ this.setSize(500, 350);
+ this.setLocationRelativeTo(null);
+ }
+
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ UIManager.setLookAndFeel(new SubstanceBusinessBlackSteelLookAndFeel());
+ // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ new TitledPanelFrame().setVisible(true);
+ }
+ });
+ }
+}
diff --git a/substance-swingx/src/tools/java/docrobot/TitledPanelRunner.java b/substance-swingx/src/tools/java/docrobot/TitledPanelRunner.java
new file mode 100644
index 0000000..f5c8a9a
--- /dev/null
+++ b/substance-swingx/src/tools/java/docrobot/TitledPanelRunner.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2005-2008 Kirill Grouchnikov, based on work by
+ * Sun Microsystems, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package docrobot;
+
+import java.util.Map;
+
+import javax.swing.JFrame;
+
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.skin.SkinInfo;
+
+/**
+ * The main method for taking screenshots for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TitledPanelRunner {
+ /**
+ * Runs the specified screenshot robot.
+ *
+ * @param args
+ * Ignored.
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+
+ String frameClass = "docrobot.TitledPanelFrame";
+ String subfolder = "titledpanel";
+ Map<String, SkinInfo> skins = SubstanceLookAndFeel.getAllSkins();
+ Robot robot = BasicRobot.robotWithNewAwtHierarchy();
+ for (Map.Entry<String, SkinInfo> skinEntry : skins.entrySet()) {
+ String key = skinEntry.getKey();
+ String normalized = key.toLowerCase().replaceAll(" ", "");
+ new FrameRobot(frameClass, (SubstanceSkin) Class.forName(
+ skinEntry.getValue().getClassName()).newInstance(),
+ "C:/jprojects/substance-swingx/www/images/" + subfolder
+ + "/" + normalized, robot).run();
+ }
+ }
+}
diff --git a/substance-swingx/src/tools/resources/docrbot/applications-internet.png b/substance-swingx/src/tools/resources/docrbot/applications-internet.png
new file mode 100644
index 0000000..096e848
Binary files /dev/null and b/substance-swingx/src/tools/resources/docrbot/applications-internet.png differ
diff --git a/substance-swingx/src/tools/resources/docrbot/tips.properties b/substance-swingx/src/tools/resources/docrbot/tips.properties
new file mode 100644
index 0000000..8d57691
--- /dev/null
+++ b/substance-swingx/src/tools/resources/docrbot/tips.properties
@@ -0,0 +1,3 @@
+tip.1.description=This is the first time! Plain text.
+tip.2.description=<html>This is <b>another tip</b>, it uses HTML!
+tip.3.description=A third one
\ No newline at end of file
diff --git a/substance/build.gradle b/substance/build.gradle
new file mode 100755
index 0000000..f42b7a4
--- /dev/null
+++ b/substance/build.gradle
@@ -0,0 +1,237 @@
+configurations {
+ testCompile { extendsFrom compile }
+ toolsCompile { extendsFrom compile }
+}
+
+sourceSets {
+ main
+ test
+ tools {
+ compileClasspath = sourceSets.main.output + sourceSets.test.output + configurations.toolsCompile + configurations.testCompile
+ }
+}
+
+
+dependencies {
+ compile module('net.jcip:jcip-annotations:1.0') {
+ transitive = false
+ }
+
+ compile project(path: ":trident")
+ compile project(path: ":laf-widget", transitive: false)
+ compile project(path: ":laf-plugin", transitive: false)
+ testCompile group: 'com.jgoodies', name: 'forms', version: '1.2.0'
+ testCompile group: 'org.swinglabs.swingx', name: 'swingx-core', version: '1.6.3'
+ toolsCompile group: 'org.easytesting', name: 'fest-swing', version: '1.2.1'
+ toolsCompile group: 'asm', name: 'asm-all', version: '2.2.3'
+}
+
+task augmentation(dependsOn: classes) {
+ description = "Performs code augmentaiton for the laf-plugin and laf-widget libraries on the substance jar classes"
+
+ doLast {
+ def augmentClassPath = configurations.toolsCompile.asPath
+
+ ant.taskdef(name: 'delegate-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentTask", classpath: augmentClassPath)
+ ant.taskdef(name: 'delegate-update-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentUpdateTask", classpath: augmentClassPath)
+ ant.taskdef(name: 'laf-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentMainTask", classpath: augmentClassPath)
+ ant.taskdef(name: 'icon-ghosting-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentIconGhostingTask", classpath: augmentClassPath)
+ ant.taskdef(name: 'container-ghosting-augment', classname: "org.pushingpixels.lafwidget.ant.AugmentContainerGhostingTask", classpath: augmentClassPath)
+
+ def verboseAugmentation = false
+
+ // Delegate augmentation
+ ant.'delegate-update-augment'(verbose: verboseAugmentation, pattern: ".*UI\u002Eclass") {
+ classpathset(dir: sourceSets.main.output.classesDir)
+ }
+
+ ant.'delegate-augment'(verbose: verboseAugmentation, pattern: ".*UI\u002Eclass") {
+ classpathset(dir: sourceSets.main.output.classesDir)
+ }
+
+ // Icon ghosting augmentation
+ ant.'icon-ghosting-augment'(verbose: verboseAugmentation) {
+ classpathset(dir: sourceSets.main.output.classesDir)
+ iconghosting(className: "org.pushingpixels.substance.internal.ui.SubstanceButtonUI", methodName: "paintIcon")
+ iconghosting(className: "org.pushingpixels.substance.internal.ui.SubstanceToggleButtonUI", methodName: "paintIcon")
+ }
+
+ // Container ghosting augmentation
+ ant.'container-ghosting-augment'(verbose: verboseAugmentation) {
+ classpathset(dir: sourceSets.main.output.classesDir)
+ containerghosting(className: "org.pushingpixels.substance.internal.ui.SubstanceDesktopPaneUI", toInjectAfterOriginal: "true")
+ containerghosting(className: "org.pushingpixels.substance.internal.ui.SubstanceMenuBarUI", toInjectAfterOriginal: "true")
+ containerghosting(className: "org.pushingpixels.substance.internal.ui.SubstanceMenuUI", toInjectAfterOriginal: "true")
+ containerghosting(className: "org.pushingpixels.substance.internal.ui.SubstancePanelUI", toInjectAfterOriginal: "true")
+ containerghosting(className: "org.pushingpixels.substance.internal.ui.SubstanceScrollBarUI", toInjectAfterOriginal: "true")
+ containerghosting(className: "org.pushingpixels.substance.internal.ui.SubstanceToolBarUI", toInjectAfterOriginal: "true")
+ }
+ }
+}
+
+jar {
+ dependsOn augmentation
+ dependsOn toolsClasses
+
+ manifest {
+ attributes(
+ "Substance-Distribution": "Full",
+ "Substance-Version": version,
+ "Substance-VersionName": versionKey,
+ )
+ }
+
+}
+
+task liteJar(type: Jar) {
+ dependsOn augmentation
+ dependsOn toolsClasses
+
+ classifier = 'lite'
+
+ from sourceSets.main.output
+ exclude 'org/pushingpixels/substance/internal/contrib/randelshofer/**'
+ exclude 'org/pushingpixels/substance/internal/contrib/xoetrope/**'
+ exclude 'org/pushingpixels/substance/internal/ui/SubstanceColorChooserUI*'
+ exclude 'org/pushingpixels/lafwidget/ant/**'
+
+ manifest {
+ attributes(
+ "Substance-Distribution": "Lite",
+ "Substance-Version": version,
+ "Substance-VersionName": versionKey,
+ )
+ }
+}
+
+task testJar(type: Jar) {
+ classifier = 'tst'
+
+ from sourceSets.test.output
+
+ manifest {
+ attributes(
+ "Substance-Version": version,
+ "Substance-VersionName": versionKey,
+ )
+ }
+}
+
+task testLiteJar(type: Jar) {
+ classifier = 'tst-lite'
+
+ from sourceSets.test.output
+
+ manifest {
+ attributes(
+ "Substance-Version": version,
+ "Substance-VersionName": versionKey,
+ )
+ }
+}
+
+
+task toolsJar(type: Jar) {
+ classifier = 'tools'
+ from sourceSets.tools.output
+ manifest {
+ attributes(
+ "Substance-Version": version,
+ "Substance-VersionName": versionKey,
+ )
+ }
+}
+
+task distroJar(type: Jar) {
+ dependsOn toolsJar, liteJar, testJar, testLiteJar, jar
+ classifier = 'all'
+ from(projectDir)
+ include "lib/**"
+ include "src/**"
+ include "gradle.properties"
+ include "build.gradle"
+ exclude "**/*.vsd"
+}
+
+
+artifacts {
+ archives toolsJar
+ archives liteJar
+ archives testJar
+ distro toolsJar
+ distro liteJar
+}
+
+uploadPublished {
+ repositories {
+ mavenDeployer {
+ configurePOM(pom)
+ }
+ }
+}
+
+install {
+ configurePOM(repositories.mavenInstaller.pom)
+}
+
+private def configurePOM(def pom) {
+ configureBasePom(pom)
+ pom.project {
+ name "substance"
+ description "A fork of @kirilcool's substance project"
+ url "http://insubstantial.github.com/insubstantial/substance/"
+ licenses {
+ license {
+ name 'BSD License'
+ url 'http://www.opensource.org/licenses/bsd-license.php'
+ distribution 'repo'
+ comments "Does not cover the Xoetrope Color Wheel"
+ }
+ license {
+ name 'Mozilla Public License 1.1'
+ url 'http://www.opensource.org/licenses/mozilla1.1'
+ distribution 'repo'
+ comments "Covers the Xoetrope Color Wheel"
+ }
+ }
+ }
+
+ // deal with a gradle bug where transitive=false is not passed into the generated POM
+ pom.whenConfigured {cpom ->
+ cpom.dependencies.each {dep ->
+ switch (dep.artifactId) {
+ case 'jcip-annotations':
+ dep.scope = 'provided'
+ break;
+ case 'trident':
+ dep.classifier = 'swing'
+ break
+ }
+ }
+ }
+
+}
+
+
+task jitterbug(type: JavaExec) {
+ main = 'tools.jitterbug.JitterbugEditor'
+ debug = Boolean.valueOf(System.getProperty('debug', 'false'))
+ classpath = sourceSets.tools.compileClasspath + sourceSets.tools.runtimeClasspath
+}
+
+task testCheck(type: JavaExec) {
+ main = 'test.Check'
+ debug = Boolean.valueOf(System.getProperty('debug', 'true'))
+ classpath = sourceSets.test.runtimeClasspath
+}
+
+task testFoo(type: JavaExec) {
+ main = 'Foo'
+ debug = Boolean.valueOf(System.getProperty('debug', 'false'))
+ classpath = sourceSets.test.runtimeClasspath
+}
+task testTridentLeakSnippet(type: JavaExec) {
+ main = 'TridentLeakSnippet'
+ debug = Boolean.valueOf(System.getProperty('debug', 'false'))
+ classpath = sourceSets.test.runtimeClasspath
+}
diff --git a/substance/doc-robot-schemes.sh b/substance/doc-robot-schemes.sh
new file mode 100644
index 0000000..b7de86c
--- /dev/null
+++ b/substance/doc-robot-schemes.sh
@@ -0,0 +1,34 @@
+set JAVA="/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/bin/java"
+
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.AquaScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.BarbyPinkScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.BottleGreenScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.BrownScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.CharcoalScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.CremeScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.DarkVioletScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.EbonyScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.JadeForestScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.LightAquaScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.LimeGreenScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.OliveScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.OrangeScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.PurpleScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.RaspberryScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.SepiaScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.SteelBlueScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.SunGlareScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.SunsetScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.TerracottaScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.UltramarineScheme
+
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.DerivedSaturatedScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.DerivedDesaturatedScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.DerivedInvertedScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.DerivedNegatedScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.DerivedHueShiftedScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.DerivedShadedScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.DerivedTintedScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.DerivedTonedScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.DerivedShiftedScheme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.schemes.DerivedShiftedBackgroundScheme
diff --git a/substance/doc-robot-skins.sh b/substance/doc-robot-skins.sh
new file mode 100644
index 0000000..416b457
--- /dev/null
+++ b/substance/doc-robot-skins.sh
@@ -0,0 +1,27 @@
+alias JAVA="/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/bin/java"
+
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Autumn
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Business
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.BusinessBlackSteel
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.BusinessBlueSteel
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.ChallengerDeep
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.CeruleanSkin
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Creme
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.CremeCoffee
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Dust
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.DustCoffee
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.EmeraldDusk
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Gemini
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Graphite
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.GraphiteGlass
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.GraphiteAqua
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Magellan
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Mariner
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.MistSilver
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.MistAqua
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Moderate
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Nebula
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.NebulaBrickWall
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Raven
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Sahara
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.skins.Twilight
diff --git a/substance/doc-robot-watermarks.sh b/substance/doc-robot-watermarks.sh
new file mode 100644
index 0000000..64b8c69
--- /dev/null
+++ b/substance/doc-robot-watermarks.sh
@@ -0,0 +1,10 @@
+set JAVA="/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/bin/java"
+
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.watermarks.CrosshatchWatermark
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.watermarks.NullWatermark
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.watermarks.StripesWatermark
+
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.watermarks.ImageWatermarkAngelina
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.watermarks.ImageWatermarkBeyonce
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.watermarks.ImageWatermarkDominic
+JAVA -DSubstance.controlFont="Lucida Grande 12" -cp ./drop/substance-tools.jar:./drop/substance.jar:./drop/substance-tst.jar:./lib/forms-1.2.0.jar:./lib/test/fest-swing-1.2.jar:./lib/test/fest-reflect-1.2.jar:./lib/test/fest-util-1.1.2.jar tools.docrobot.RobotMain tools.docrobot.watermarks.ImageWatermarkTerry
diff --git a/substance/settings.gradle b/substance/settings.gradle
new file mode 100755
index 0000000..c12582e
--- /dev/null
+++ b/substance/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'substance'
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/ColorSchemeAssociationKind.java b/substance/src/main/java/org/pushingpixels/substance/api/ColorSchemeAssociationKind.java
new file mode 100644
index 0000000..dbb6910
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/ColorSchemeAssociationKind.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+import java.util.*;
+
+import javax.swing.JCheckBox;
+import javax.swing.JTabbedPane;
+
+/**
+ * Allows associating different color schemes to different visual parts of UI
+ * components. For example, the {@link JCheckBox} has three different visual
+ * areas:
+ * <ul>
+ * <li>Border - assciated with {@link #BORDER}</li>
+ * <li>Fill - associated with {@link #FILL}</li>
+ * <li>Check mark - associated with {@link #MARK}</li>
+ * </ul>
+ *
+ * Applications can create custom instances of this class to further refine the
+ * control over the painting. In this case, the custom UI delegates must be
+ * created to use these new association kinds.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.1
+ */
+public class ColorSchemeAssociationKind {
+ /**
+ * All known association kind values.
+ */
+ private static Set<ColorSchemeAssociationKind> values = new HashSet<ColorSchemeAssociationKind>();
+
+ /**
+ * Name for this association kind.
+ */
+ private String name;
+
+ /**
+ * Fallback for this association kind. This is used when no color scheme is
+ * associated with this kind. For example, {@link #TAB_BORDER} specifies
+ * that its fallback is {@link #BORDER}. When the {@link JTabbedPane} UI
+ * delegate is painting the tabs, it will try to use the color scheme
+ * associated with {@link #TAB_BORDER}. If none was registered, it will fall
+ * back to use the color scheme associated with {@link #BORDER}, and if that
+ * is not registered as well, will use the color scheme associated with
+ * {@link #FILL}.
+ */
+ private ColorSchemeAssociationKind fallback;
+
+ /**
+ * Creates a new association kind.
+ *
+ * @param name
+ * Association kind name.
+ * @param fallback
+ * Fallback association kind. This is used when no color scheme
+ * is associated with this kind. For example, {@link #TAB_BORDER}
+ * specifies that its fallback is {@link #BORDER}. When the
+ * {@link JTabbedPane} UI delegate is painting the tabs, it will
+ * try to use the color scheme associated with
+ * {@link #TAB_BORDER}. If none was registered, it will fall back
+ * to use the color scheme associated with {@link #BORDER}, and
+ * if that is not registered as well, will use the color scheme
+ * associated with {@link #FILL}.
+ */
+ public ColorSchemeAssociationKind(String name,
+ ColorSchemeAssociationKind fallback) {
+ this.name = name;
+ this.fallback = fallback;
+ values.add(this);
+ }
+
+ @Override
+ public String toString() {
+ return this.name;
+ }
+
+ /**
+ * The default visual area that is used for the inner part of most controls.
+ */
+ public static final ColorSchemeAssociationKind FILL = new ColorSchemeAssociationKind(
+ "fill", null);
+
+ /**
+ * Visual area of separators.
+ */
+ public static final ColorSchemeAssociationKind SEPARATOR = new ColorSchemeAssociationKind(
+ "separator", FILL);
+
+ /**
+ * Fill visual area of the tabs.
+ */
+ public static final ColorSchemeAssociationKind TAB = new ColorSchemeAssociationKind(
+ "tab", FILL);
+
+ /**
+ * Border visual area of non-tab controls.
+ */
+ public static final ColorSchemeAssociationKind BORDER = new ColorSchemeAssociationKind(
+ "border", FILL);
+
+ /**
+ * Visual area of marks. Used for painting check marks of checkboxes and
+ * radio buttons, as well as arrow icons of combo boxes, spinners and more.
+ */
+ public static final ColorSchemeAssociationKind MARK = new ColorSchemeAssociationKind(
+ "mark", BORDER);
+
+ /**
+ * Border visual area of the tabs.
+ */
+ public static final ColorSchemeAssociationKind TAB_BORDER = new ColorSchemeAssociationKind(
+ "tabBorder", BORDER);
+
+ /**
+ * Highlight visual areas for lists, tables, trees and menus.
+ */
+ public static final ColorSchemeAssociationKind HIGHLIGHT = new ColorSchemeAssociationKind(
+ "highlight", FILL);
+
+ /**
+ * Highlight visual areas for text components.
+ */
+ public static final ColorSchemeAssociationKind TEXT_HIGHLIGHT = new ColorSchemeAssociationKind(
+ "textHighlight", HIGHLIGHT);
+
+ /**
+ * Border visual areas for highlighted regions of lists, tables, trees and
+ * menus.
+ */
+ public static final ColorSchemeAssociationKind HIGHLIGHT_BORDER = new ColorSchemeAssociationKind(
+ "highlightBorder", BORDER);
+
+ /**
+ * Visual area of marks in highlighted regions of lists, tables, trees and
+ * menus.
+ */
+ public static final ColorSchemeAssociationKind HIGHLIGHT_MARK = new ColorSchemeAssociationKind(
+ "highlightMark", MARK);
+
+ /**
+ * Returns all available association kinds.
+ *
+ * @return All available association kinds.
+ */
+ public static Set<ColorSchemeAssociationKind> values() {
+ return Collections.unmodifiableSet(values);
+ }
+
+ /**
+ * Returns the fallback for this association kind.
+ *
+ * @return The fallback for this association kind.
+ */
+ public ColorSchemeAssociationKind getFallback() {
+ return fallback;
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/ColorSchemeSingleColorQuery.java b/substance/src/main/java/org/pushingpixels/substance/api/ColorSchemeSingleColorQuery.java
new file mode 100644
index 0000000..ece2bd3
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/ColorSchemeSingleColorQuery.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+import java.awt.Color;
+
+/**
+ * Defines a query that returns a single color based on a color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface ColorSchemeSingleColorQuery {
+ /**
+ * Returns a single color based on the specified color scheme.
+ *
+ * @param scheme
+ * The color scheme.
+ * @return The color based on the specified color scheme.
+ */
+ public Color query(SubstanceColorScheme scheme);
+
+ /**
+ * Core implementation of the {@link ColorSchemeSingleColorQuery} interface
+ * that returns the ultra light color of the specified color scheme.
+ */
+ public static final ColorSchemeSingleColorQuery ULTRALIGHT = new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getUltraLightColor();
+ }
+ };
+
+ /**
+ * Core implementation of the {@link ColorSchemeSingleColorQuery} interface
+ * that returns the extra light color of the specified color scheme.
+ */
+ public static final ColorSchemeSingleColorQuery EXTRALIGHT = new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getExtraLightColor();
+ }
+ };
+
+ /**
+ * Core implementation of the {@link ColorSchemeSingleColorQuery} interface
+ * that returns the light color of the specified color scheme.
+ */
+ public static final ColorSchemeSingleColorQuery LIGHT = new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getLightColor();
+ }
+ };
+
+ /**
+ * Core implementation of the {@link ColorSchemeSingleColorQuery} interface
+ * that returns the mid color of the specified color scheme.
+ */
+ public static final ColorSchemeSingleColorQuery MID = new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getMidColor();
+ }
+ };
+
+ /**
+ * Core implementation of the {@link ColorSchemeSingleColorQuery} interface
+ * that returns the dark color of the specified color scheme.
+ */
+ public static final ColorSchemeSingleColorQuery DARK = new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getDarkColor();
+ }
+ };
+
+ /**
+ * Core implementation of the {@link ColorSchemeSingleColorQuery} interface
+ * that returns the ultra dark color of the specified color scheme.
+ */
+ public static final ColorSchemeSingleColorQuery ULTRADARK = new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getUltraDarkColor();
+ }
+ };
+
+ /**
+ * Core implementation of the {@link ColorSchemeSingleColorQuery} interface
+ * that returns the foreground color of the specified color scheme.
+ */
+ public static final ColorSchemeSingleColorQuery FOREGROUND = new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getForegroundColor();
+ }
+ };
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/ColorSchemeTransform.java b/substance/src/main/java/org/pushingpixels/substance/api/ColorSchemeTransform.java
new file mode 100644
index 0000000..1628a23
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/ColorSchemeTransform.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+/**
+ * Defines transformation on a color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface ColorSchemeTransform {
+ /**
+ * Transforms the specified color scheme.
+ *
+ * @param scheme
+ * The original color scheme to transform.
+ * @return The transformed color scheme.
+ */
+ public SubstanceColorScheme transform(SubstanceColorScheme scheme);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/ComponentState.java b/substance/src/main/java/org/pushingpixels/substance/api/ComponentState.java
new file mode 100644
index 0000000..eec424a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/ComponentState.java
@@ -0,0 +1,714 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+import java.util.*;
+
+import javax.swing.*;
+
+import org.pushingpixels.substance.api.skin.NebulaSkin;
+
+/**
+ * <p>
+ * Instances of this class correspond to states of Swing core and custom
+ * controls. This class provides a number of predefined static instances to
+ * cover most action-based controls such as buttons, check boxes and menu items.
+ * In addition, application code can define custom component states that create
+ * fine grained mapping between arbitrary states of controls and specific color
+ * scheme bundles in custom skins.
+ * </p>
+ *
+ * <p>
+ * Each component state is defined by two arrays of component state facets
+ * (available in {@link ComponentStateFacet} class). The first array specifies
+ * the facets that are <b>on</b>, and the second array specifies the facets that
+ * are <b>off</b>. For example, when a selected toggle button is pressed, it
+ * transitions to {@link #PRESSED_SELECTED} state. This state has
+ * {@link ComponentStateFacet#ENABLE}, {@link ComponentStateFacet#SELECTION} and
+ * {@link ComponentStateFacet#PRESS} as its <b>on</b> facets. If a selected
+ * toggle button is disabled, it has {@link ComponentStateFacet#SELECTION} in
+ * its <b>on</b> facets and {@link ComponentStateFacet#ENABLE} in its <b>off</b>
+ * facets.
+ * </p>
+ *
+ * <p>
+ * The {@link ComponentStateFacet} class defines a number of core facets. The
+ * {@link ComponentStateFacet#ENABLE} facet is universal - it is relevant for
+ * all Swing controls. Other facets apply to a wider range of controls. For
+ * example, {@link ComponentStateFacet#ROLLOVER} facet applies to all controls
+ * that can show rollover effects - including buttons, menu items, comboboxes,
+ * sliders, scrollbars and many more. Some facets apply to a very narrow range
+ * of controls. For exaple, {@link ComponentStateFacet#EDITABLE} is only
+ * relevant for editable controls, such as text components, editable comboboxes
+ * or spinners.
+ * </p>
+ *
+ * <p>
+ * The static instances of {@link ComponentState} defined in this class do not
+ * aim to cover all possible combinations of on and off facets. In addition to
+ * making this class to unwieldy, it is not possible to do since application
+ * code can define its own facets. Instead, Substance provides three ways to
+ * fine-tune the mapping between the component states and the color schemes used
+ * to paint the components.
+ * </p>
+ *
+ * <ol>
+ * <li>When the skin is queried for the color scheme that matches the specific
+ * component state - let's say {@link ComponentState#PRESSED_SELECTED} - the
+ * skinning layer first looks for the exact state (as passed to
+ * {@link SubstanceColorSchemeBundle#registerColorScheme(SubstanceColorScheme, ColorSchemeAssociationKind, ComponentState...)}
+ * or similar APIs). If the exact match is found, it is used. If there is no
+ * exact match, the skinning layer will look at all color schemes registered for
+ * the specific color scheme association kind in the matching color scheme
+ * bundle. The decision is made based on how "close" the registered component
+ * state is to the component state of the currently painted component. For
+ * example, {@link ComponentState#PRESSED_SELECTED} is a better match for
+ * {@link ComponentState#PRESSED_UNSELECTED} than
+ * {@link ComponentState#ROLLOVER_SELECTED} - since the
+ * {@link ComponentStateFacet#PRESS} has more weight than the
+ * {@link ComponentStateFacet#ROLLOVER} in the decision process. The skinning
+ * layer will choose the "closest" registered component state that is
+ * sufficiently close. For example, {@link ComponentState#DISABLED_SELECTED}
+ * will never be chosen for {@link ComponentState#SELECTED}, even if there are
+ * no other registered component states. This way the application code can
+ * register a few color schemes in the specific bundle, and have all other
+ * states "fall back" to the smaller subset of states.</li>
+ * <li>Facets such as {@link ComponentStateFacet#DETERMINATE} or
+ * {@link ComponentStateFacet#EDITABLE} are relevant only for a small subset of
+ * controls. In order to simplify the API signature of {@link ComponentState},
+ * these facets are not part of any of the predefined static states in this
+ * class. Instead, they are used internally in the matching UI delegates (such
+ * as for progress bar or text components) to find the best match among all the
+ * registered states of the current skin. The specific skin can define its own
+ * {@link ComponentState} instances that use these facets. For example,
+ * {@link NebulaSkin} defines a number of component states that use the
+ * {@link ComponentStateFacet#DETERMINATE} facet, and maps the matching color
+ * schemes. At runtime, the procedure described in the previous item will match
+ * the state of the specific progress bar to the states defined in this skin,
+ * and use the matching color schemes.</li>
+ * <li>Custom application components may have facets that do not directly map to
+ * the core facets defined in the {@link ComponentStateFacet} class. In this
+ * case, the application code can create its own facet instances, and its own
+ * component states that use those facets in the on and off lists. Part of the
+ * custom code will be in the UI delegates that compute the current state of the
+ * custom component using the new facets. Other part of the custom code will be
+ * in the skin definition that maps the component states defined with the new
+ * facets to the specific color schemes.</li>
+ * </ol>
+ *
+ * <p>
+ * Note that you do not have to create explicit dependency between custom
+ * component states used in the skin definition and custom component states used
+ * in the painting routines (in the UI delegates). In fact, the custom component
+ * states defined in the Substance UI delegate for progress bar are not
+ * accessible to the application code. The recommended way to separate the skin
+ * definition from the model lookups in the painting is:
+ * </p>
+ *
+ * <ul>
+ * <li>The skin definition defines a sufficiently broad set of custom component
+ * states that use the new facets. Note that you do not have to create a custom
+ * state for every possible permutation of new facets (along with the relevant
+ * core facets). A well defined set of component states will provide a good
+ * fallback state for every relevant permutation of facets, keeping the skin
+ * definition small and manageable.</li>
+ * <li>The UI delegate that queries the component model will use accurate
+ * component states that account for all the relevant on and off facets -
+ * including the core facets defined in the {@link ComponentStateFacet} class.
+ * When this (perhaps elaborate) state is passed to
+ * {@link SubstanceColorSchemeBundle#getColorScheme(ColorSchemeAssociationKind, ComponentState)}
+ * API, the the procedure described above will match the this state to one of
+ * the "base" states defined in your skin, and use the matching color scheme.</li>
+ * </ul>
+ *
+ * <p>
+ * Note that the matching algorithm only looks at the facets in the <b>on</b>
+ * and <b>off</b> lists, and ignores the component state name. This allows you
+ * to create a broad component state in your skin, and a number of narrow
+ * component states during the painting - and have the Substance skinning layer
+ * find the best match.
+ * </p>
+ *
+ * <p>
+ * When the matching algorithm cannot find a sufficiently close match, the
+ * skinning layer will fall back on one of the three base color schemes passed
+ * to the
+ * {@link SubstanceColorSchemeBundle#SubstanceColorSchemeBundle(SubstanceColorScheme, SubstanceColorScheme, SubstanceColorScheme)}
+ * constructor. States with {@link ComponentStateFacet#ENABLE} in their off list
+ * will fall back to the disabled color scheme. The
+ * {@link ComponentState#ENABLED} will fall back to the enabled color scheme.
+ * The rest of the states will fall back to the active color scheme. To change
+ * the fallback behavior pass a non-null fallback color scheme to the
+ * {@link ComponentState#ComponentState(String, ComponentState, ComponentStateFacet[], ComponentStateFacet[])}
+ * constructor as the second parameter.
+ * </p>
+ *
+ * @author Kirill Grouchnikov
+ */
+public final class ComponentState {
+ private static Set<ComponentState> allStates = new HashSet<ComponentState>();
+
+ /**
+ * Disabled default. Used for disabled buttons that have been marked as
+ * <code>default</code> with {@link JRootPane#setDefaultButton(JButton)}
+ * API.
+ */
+ public static final ComponentState DISABLED_DEFAULT = new ComponentState(
+ "disabled default",
+ new ComponentStateFacet[] { ComponentStateFacet.DEFAULT },
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE });
+
+ /**
+ * Default. Used for enabled buttons that have been marked as
+ * <code>default</code> with {@link JRootPane#setDefaultButton(JButton)}
+ * API.
+ */
+ public static final ComponentState DEFAULT = new ComponentState("default",
+ new ComponentStateFacet[] { ComponentStateFacet.DEFAULT,
+ ComponentStateFacet.ENABLE }, null);
+
+ /**
+ * Disabled selected.
+ */
+ public static final ComponentState DISABLED_SELECTED = new ComponentState(
+ "disabled selected",
+ new ComponentStateFacet[] { ComponentStateFacet.SELECTION },
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE });
+
+ /**
+ * Disabled and not selected.
+ */
+ public static final ComponentState DISABLED_UNSELECTED = new ComponentState(
+ "disabled unselected", null, new ComponentStateFacet[] {
+ ComponentStateFacet.ENABLE, ComponentStateFacet.SELECTION });
+
+ /**
+ * Pressed selected.
+ */
+ public static final ComponentState PRESSED_SELECTED = new ComponentState(
+ "pressed selected", new ComponentStateFacet[] {
+ ComponentStateFacet.SELECTION, ComponentStateFacet.PRESS,
+ ComponentStateFacet.ENABLE }, null);
+
+ /**
+ * Pressed and not selected.
+ */
+ public static final ComponentState PRESSED_UNSELECTED = new ComponentState(
+ "pressed unselected", new ComponentStateFacet[] {
+ ComponentStateFacet.PRESS, ComponentStateFacet.ENABLE },
+ new ComponentStateFacet[] { ComponentStateFacet.SELECTION });
+
+ /**
+ * Selected.
+ */
+ public static final ComponentState SELECTED = new ComponentState(
+ "selected",
+ new ComponentStateFacet[] { ComponentStateFacet.SELECTION,
+ ComponentStateFacet.ENABLE }, null);
+
+ /**
+ * Selected and rolled over.
+ */
+ public static final ComponentState ROLLOVER_SELECTED = new ComponentState(
+ "rollover selected", new ComponentStateFacet[] {
+ ComponentStateFacet.SELECTION,
+ ComponentStateFacet.ROLLOVER, ComponentStateFacet.ENABLE },
+ null);
+
+ /**
+ * Armed.
+ */
+ public static final ComponentState ARMED = new ComponentState("armed",
+ new ComponentStateFacet[] { ComponentStateFacet.ARM,
+ ComponentStateFacet.ENABLE }, null);
+
+ /**
+ * Armed and rolled over.
+ */
+ public static final ComponentState ROLLOVER_ARMED = new ComponentState(
+ "rollover armed", new ComponentStateFacet[] {
+ ComponentStateFacet.ROLLOVER, ComponentStateFacet.ARM,
+ ComponentStateFacet.ENABLE }, null);
+
+ /**
+ * Not selected and rolled over.
+ */
+ public static final ComponentState ROLLOVER_UNSELECTED = new ComponentState(
+ "rollover unselected", new ComponentStateFacet[] {
+ ComponentStateFacet.ROLLOVER, ComponentStateFacet.ENABLE },
+ new ComponentStateFacet[] { ComponentStateFacet.SELECTION });
+
+ /**
+ * Enabled state.
+ */
+ public static final ComponentState ENABLED = new ComponentState("enabled",
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE }, null);
+
+ /**
+ * Facets that are turned on for this state. For example,
+ * {@link #ROLLOVER_SELECTED} contains {@link ComponentStateFacet#ROLLOVER}
+ * and {@link ComponentStateFacet#SELECTION}.
+ */
+ private Set<ComponentStateFacet> facetsTurnedOn;
+
+ /**
+ * Facets that are turned on for this state. For example,
+ * {@link #DISABLED_UNSELECTED} contains {@link ComponentStateFacet#ENABLE}
+ * and {@link ComponentStateFacet#SELECTION}.
+ */
+ private Set<ComponentStateFacet> facetsTurnedOff;
+
+ private Map<ComponentStateFacet, Boolean> mapping;
+
+ private String name;
+
+ private ComponentState hardFallback;
+
+ /**
+ * Creates a new component state.
+ *
+ * @param name
+ * Component state name. Does not have to be unique. The name is
+ * only used in the {@link #toString()}.
+ * @param facetsOn
+ * Indicates that are turned on for this state. For example,
+ * {@link #ROLLOVER_SELECTED} should pass both
+ * {@link ComponentStateFacet#ROLLOVER} and
+ * {@link ComponentStateFacet#SELECTION}.
+ * @param facetsOff
+ * Indicates that are turned on for this state. For example,
+ * {@link #DISABLED_UNSELECTED} should pass both
+ * {@link ComponentStateFacet#ENABLE} and
+ * {@link ComponentStateFacet#SELECTION}.
+ */
+ public ComponentState(String name, ComponentStateFacet[] facetsOn,
+ ComponentStateFacet[] facetsOff) {
+ this(name, null, facetsOn, facetsOff);
+ }
+
+ /**
+ * Creates a new component state.
+ *
+ * @param name
+ * Component state name. Does not have to be unique. The name is
+ * only used in the {@link #toString()}.
+ * @param hardFallback
+ * The fallback state that will be used in
+ * {@link SubstanceColorSchemeBundle#getColorScheme(ColorSchemeAssociationKind, ComponentState)}
+ * in case {@link #bestFit(Collection)} returns <code>null</code>
+ * @param facetsOn
+ * Indicates that are turned on for this state. For example,
+ * {@link #ROLLOVER_SELECTED} should pass both
+ * {@link ComponentStateFacet#ROLLOVER} and
+ * {@link ComponentStateFacet#SELECTION}.
+ * @param facetsOff
+ * Indicates that are turned on for this state. For example,
+ * {@link #DISABLED_UNSELECTED} should pass both
+ * {@link ComponentStateFacet#ENABLE} and
+ * {@link ComponentStateFacet#SELECTION}.
+ */
+ public ComponentState(String name, ComponentState hardFallback,
+ ComponentStateFacet[] facetsOn, ComponentStateFacet[] facetsOff) {
+ if (name == null) {
+ throw new IllegalArgumentException(
+ "Component state name must be non-null");
+ }
+ this.name = name;
+ this.hardFallback = hardFallback;
+ this.facetsTurnedOn = new HashSet<ComponentStateFacet>();
+ if (facetsOn != null) {
+ for (ComponentStateFacet facetOn : facetsOn) {
+ this.facetsTurnedOn.add(facetOn);
+ }
+ }
+ this.facetsTurnedOff = new HashSet<ComponentStateFacet>();
+ if (facetsOff != null) {
+ for (ComponentStateFacet facetOff : facetsOff) {
+ this.facetsTurnedOff.add(facetOff);
+ }
+ }
+ this.mapping = new HashMap<ComponentStateFacet, Boolean>();
+ allStates.add(this);
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(this.name);
+ sb.append(" : on {");
+ if (this.facetsTurnedOn != null) {
+ String sep = "";
+ for (ComponentStateFacet on : this.facetsTurnedOn) {
+ sb.append(sep);
+ sep = ", ";
+ sb.append(on.toString());
+ }
+ }
+ sb.append("} : off {");
+ if (this.facetsTurnedOff != null) {
+ String sep = "";
+ for (ComponentStateFacet off : this.facetsTurnedOff) {
+ sb.append(sep);
+ sep = ", ";
+ sb.append(off.toString());
+ }
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Returns indication whether <code>this</code> component state is "active"
+ * under the specified facet. For example, {@link #ROLLOVER_SELECTED} will
+ * return <code>true</code> for both {@link ComponentStateFacet#ROLLOVER}
+ * and {@link ComponentStateFacet#SELECTION}.
+ *
+ * @param stateFacet
+ * State facet.
+ * @return <code>true</code> if <code>this</code> component state is
+ * "active" under the specified facet (for example,
+ * {@link #ROLLOVER_SELECTED} will return <code>true</code> for both
+ * {@link ComponentStateFacet#ROLLOVER} and
+ * {@link ComponentStateFacet#SELECTION}), <code>false</code>
+ * otherwise.
+ */
+ public boolean isFacetActive(ComponentStateFacet stateFacet) {
+ Boolean result = this.mapping.get(stateFacet);
+ if (result != null)
+ return result;
+ if ((facetsTurnedOn != null) && facetsTurnedOn.contains(stateFacet)) {
+ this.mapping.put(stateFacet, Boolean.TRUE);
+ return true;
+ }
+ this.mapping.put(stateFacet, Boolean.FALSE);
+ return false;
+ }
+
+ /**
+ * Checks whether this state is disabled. A disabled state has
+ * {@link ComponentStateFacet#ENABLE} facet in its <code>off</code> set.
+ *
+ * @return <code>true</code> if this state is disabled, <code>false</code>
+ * otherwise.
+ */
+ public boolean isDisabled() {
+ return !this.isFacetActive(ComponentStateFacet.ENABLE);
+ }
+
+ /**
+ * Returns all active component states.
+ *
+ * @return All active component states.
+ */
+ public static ComponentState[] getActiveStates() {
+ List<ComponentState> states = new LinkedList<ComponentState>();
+ for (ComponentState state : allStates) {
+ if (state.isActive())
+ states.add(state);
+ }
+ return states.toArray(new ComponentState[0]);
+ }
+
+ /**
+ * Returns all component states.
+ *
+ * @return All component states
+ */
+ public static Set<ComponentState> getAllStates() {
+ return Collections.synchronizedSet(allStates);
+ }
+
+ public boolean isActive() {
+ if (this == ComponentState.ENABLED)
+ return false;
+ if (!this.isFacetActive(ComponentStateFacet.ENABLE))
+ return false;
+ return true;
+ }
+
+ /**
+ * Retrieves component state based on the button model (required parameter)
+ * and component itself (optional parameter).
+ *
+ * @param model
+ * Button model (required).
+ * @param component
+ * Component (optional).
+ * @return The matching component state.
+ */
+ public static ComponentState getState(ButtonModel model,
+ JComponent component) {
+ return getState(model, component, false);
+ }
+
+ /**
+ * Returns the state of the specified button.
+ *
+ * @param button
+ * Button.
+ * @return The state of the specified button.
+ */
+ public static ComponentState getState(AbstractButton button) {
+ return getState(button.getModel(), button, false);
+ }
+
+ /**
+ * Retrieves component state based on the button model (required parameter)
+ * and button itself (optional parameter).
+ *
+ * @param model
+ * Button model (required).
+ * @param component
+ * Component (optional).
+ * @param toIgnoreSelection
+ * If <code>true</code>, the {@link ButtonModel#isSelected()}
+ * will not be checked. This can be used for tracking transitions
+ * on menu items that use <code>armed</code> state instead, when
+ * we don't want to use different rollover themes for selected
+ * and unselected checkbox and radio button menu items (to
+ * preserve consistent visual appearence of highlights).
+ * @return The matching component state.
+ */
+ public static ComponentState getState(ButtonModel model,
+ JComponent component, boolean toIgnoreSelection) {
+ // if (!SwingUtilities.isEventDispatchThread())
+ // throw new IllegalArgumentException("Accessing outside EDT");
+
+ boolean isRollover = model.isRollover();
+
+ // fix for defect 103 - no rollover effects on menu items
+ // that are not in the selected menu path
+ if (component instanceof MenuElement) {
+ MenuElement[] selectedMenuPath = MenuSelectionManager
+ .defaultManager().getSelectedPath();
+ for (MenuElement elem : selectedMenuPath) {
+ if (elem == component) {
+ isRollover = true;
+ break;
+ }
+ }
+ }
+
+ if (component != null) {
+ if (component instanceof JButton) {
+ JButton jb = (JButton) component;
+ if (jb.isDefaultButton()) {
+ if (model.isEnabled()) {
+ // check for rollover
+ if (jb.isRolloverEnabled()
+ && jb.getModel().isRollover()) {
+ if (model.isSelected())
+ return ROLLOVER_SELECTED;
+ else
+ return ROLLOVER_UNSELECTED;
+ }
+ if ((!model.isPressed()) && (!model.isArmed()))
+ return DEFAULT;
+ } else
+ return DISABLED_DEFAULT;
+ }
+ }
+ }
+
+ boolean isRolloverEnabled = true;
+ if (component instanceof AbstractButton)
+ isRolloverEnabled = ((AbstractButton) component)
+ .isRolloverEnabled();
+ if (!model.isEnabled()) {
+ if (model.isSelected())
+ return DISABLED_SELECTED;
+ return DISABLED_UNSELECTED;
+ } else if (model.isArmed() && model.isPressed()) {
+ if (model.isSelected())
+ return PRESSED_SELECTED;
+ return PRESSED_UNSELECTED;
+ } else if (!toIgnoreSelection && model.isSelected()) {
+ if (((component == null) || isRolloverEnabled) && isRollover)
+ return ROLLOVER_SELECTED;
+ return SELECTED;
+ } else if (model.isArmed()) {
+ if (((component == null) || isRolloverEnabled) && isRollover)
+ return ROLLOVER_ARMED;
+ return ARMED;
+ } else if (((component == null) || isRolloverEnabled) && isRollover)
+ return ROLLOVER_UNSELECTED;
+
+ return ENABLED;
+ }
+
+ /**
+ * Returns the component state that matches the specified parameters.
+ *
+ * @param isEnabled
+ * Enabled flag.
+ * @param isRollover
+ * Rollover flag.
+ * @param isSelected
+ * Selected flag.
+ * @return The component state that matches the specified parameters.
+ */
+ public static ComponentState getState(boolean isEnabled,
+ boolean isRollover, boolean isSelected) {
+ if (!isEnabled) {
+ if (isSelected)
+ return DISABLED_SELECTED;
+ return DISABLED_UNSELECTED;
+ }
+ if (isSelected) {
+ if (isRollover)
+ return ROLLOVER_SELECTED;
+ return SELECTED;
+ }
+ if (isRollover)
+ return ROLLOVER_UNSELECTED;
+ return ENABLED;
+ }
+
+ private int fitValue(ComponentState state) {
+ int value = 0;
+ if (this.facetsTurnedOn != null) {
+ for (ComponentStateFacet on : this.facetsTurnedOn) {
+ if (state.facetsTurnedOn == null) {
+ value -= on.value / 2;
+ } else {
+ if (state.facetsTurnedOn.contains(on)) {
+ value += on.value;
+ } else {
+ value -= on.value / 2;
+ }
+ if (state.facetsTurnedOff.contains(on)) {
+ value -= on.value;
+ }
+ }
+ }
+ }
+ if (this.facetsTurnedOff != null) {
+ for (ComponentStateFacet off : this.facetsTurnedOff) {
+ if (state.facetsTurnedOff == null) {
+ value -= off.value / 2;
+ } else {
+ if (state.facetsTurnedOff.contains(off)) {
+ value += off.value;
+ } else {
+ value -= off.value / 2;
+ }
+ if (state.facetsTurnedOn.contains(off)) {
+ value -= off.value;
+ }
+ }
+ }
+ }
+ // / System.out.println("Fit value is " + value + " for ");
+ // / System.out.println("\t" + this);
+ // System.out.println("\t" + state);
+ return value;
+ }
+
+ public ComponentState bestFit(Collection<ComponentState> states) {
+ ComponentState bestFit = null;
+ int bestFitValue = 0;
+ for (ComponentState state : states) {
+ if (this.isActive() != state.isActive())
+ continue;
+ int currFitValue = state.fitValue(this) + this.fitValue(state);
+ if (bestFit == null) {
+ bestFit = state;
+ bestFitValue = currFitValue;
+ } else {
+ if (currFitValue > bestFitValue) {
+ bestFit = state;
+ bestFitValue = currFitValue;
+ }
+ }
+ }
+ // fit value must be positive
+ if (bestFitValue > 0)
+ return bestFit;
+ return null;
+ }
+
+ public ComponentState getHardFallback() {
+ return hardFallback;
+ }
+
+ @Override
+ public int hashCode() {
+ if (this.facetsTurnedOn.isEmpty() && this.facetsTurnedOff.isEmpty())
+ return 0;
+
+ if (this.facetsTurnedOn.isEmpty()) {
+ boolean isFirst = true;
+ int result = 0;
+ for (ComponentStateFacet off : this.facetsTurnedOff) {
+ if (isFirst) {
+ result = ~off.hashCode();
+ isFirst = false;
+ } else {
+ result = result & ~off.hashCode();
+ }
+ }
+ return result;
+ } else {
+ boolean isFirst = true;
+ int result = 0;
+ for (ComponentStateFacet on : this.facetsTurnedOn) {
+ if (isFirst) {
+ result = on.hashCode();
+ isFirst = false;
+ } else {
+ result = result & on.hashCode();
+ }
+ }
+ for (ComponentStateFacet off : this.facetsTurnedOff) {
+ result = result & ~off.hashCode();
+ }
+ return result;
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ComponentState))
+ return false;
+
+ ComponentState second = (ComponentState) obj;
+ if (this.facetsTurnedOn.size() != second.facetsTurnedOn.size())
+ return false;
+ if (this.facetsTurnedOff.size() != second.facetsTurnedOff.size())
+ return false;
+
+ if (!this.facetsTurnedOn.containsAll(second.facetsTurnedOn))
+ return false;
+ if (!this.facetsTurnedOff.containsAll(second.facetsTurnedOff))
+ return false;
+
+ return true;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/ComponentStateFacet.java b/substance/src/main/java/org/pushingpixels/substance/api/ComponentStateFacet.java
new file mode 100644
index 0000000..29aa90d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/ComponentStateFacet.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+import javax.swing.JProgressBar;
+import javax.swing.JRootPane;
+import javax.swing.text.JTextComponent;
+
+/**
+ * Defies a single facet of core and custom {@link ComponentState}s. See
+ * Javadocs of the {@link ComponentState} class for more information on state
+ * facets.
+ *
+ * <p>
+ * This class is experimental API and is likely to change in the next few
+ * releases.
+ * </p>
+ */
+public final class ComponentStateFacet {
+ int value;
+
+ String name;
+
+ /**
+ * Facet that describes the enabled bit.
+ */
+ public static final ComponentStateFacet ENABLE = new ComponentStateFacet(
+ "enable", 0);
+
+ /**
+ * Facet that describes the rollover bit.
+ */
+ public static final ComponentStateFacet ROLLOVER = new ComponentStateFacet(
+ "rollover", 10);
+
+ /**
+ * Facet that describes the selection bit.
+ */
+ public static final ComponentStateFacet SELECTION = new ComponentStateFacet(
+ "selection", 10);
+
+ /**
+ * Facet that describes the press bit.
+ */
+ public static final ComponentStateFacet PRESS = new ComponentStateFacet(
+ "press", 50);
+
+ /**
+ * Facet that describes the arm bit. This is relevant for menu items.
+ */
+ public static final ComponentStateFacet ARM = new ComponentStateFacet(
+ "arm", 10);
+
+ /**
+ * Facet that describes the default bit. This is relevant for buttons which
+ * can be set as default with the
+ * {@link JRootPane#setDefaultButton(javax.swing.JButton)} API.
+ */
+ public static final ComponentStateFacet DEFAULT = new ComponentStateFacet(
+ "default", 500);
+
+ /**
+ * Facet that describes the determinate bit. This is relevant for
+ * {@link JProgressBar} control and its
+ * {@link JProgressBar#setIndeterminate(boolean)} API.
+ */
+ public static final ComponentStateFacet DETERMINATE = new ComponentStateFacet(
+ "determinate", 10);
+
+ /**
+ * Facet that describes the editable bit. This is relevant for
+ * {@link JTextComponent} derived controls and its
+ * {@link JTextComponent#setEditable(boolean)} API.
+ */
+ public static final ComponentStateFacet EDITABLE = new ComponentStateFacet(
+ "editable", 50);
+
+ /**
+ * Creates a new facet.
+ *
+ * @param name
+ * Facet name.
+ * @param value
+ * Facet value. This is used in the matching algorithm described
+ * in the javadocs of {@link ComponentState}. The larger the
+ * value, the more importance is given to the specific facet.
+ */
+ public ComponentStateFacet(String name, int value) {
+ this.name = name;
+ if (value < 0) {
+ throw new IllegalArgumentException(
+ "Facet value must be non-negative");
+ }
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return this.name + ":" + this.value;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/DecorationAreaType.java b/substance/src/main/java/org/pushingpixels/substance/api/DecorationAreaType.java
new file mode 100644
index 0000000..35ae305
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/DecorationAreaType.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+/**
+ * Enumeration of available decoration area types. This class is part of
+ * officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.3
+ */
+public final class DecorationAreaType {
+ String displayName;
+
+ public DecorationAreaType(String displayName) {
+ this.displayName = displayName;
+ }
+
+ @Override
+ public String toString() {
+ return this.displayName;
+ }
+
+ /**
+ * Title pane of top-level windows (frames, dialogs).
+ */
+ public final static DecorationAreaType PRIMARY_TITLE_PANE = new DecorationAreaType(
+ "Primary title pane");
+
+ /**
+ * Title pane of top-level windows (frames, dialogs) when not active.
+ */
+ public final static DecorationAreaType PRIMARY_TITLE_PANE_INACTIVE = new DecorationAreaType(
+ "Primary title pane Inactive");
+
+ /**
+ * Title pane of non top-level windows (internal frames, desktop icons).
+ */
+ public final static DecorationAreaType SECONDARY_TITLE_PANE = new DecorationAreaType(
+ "Secondary title pane");
+
+ /**
+ * Title pane of non top-level windows (internal frames, desktop icons) when not active.
+ */
+ public final static DecorationAreaType SECONDARY_TITLE_PANE_INACTIVE = new DecorationAreaType(
+ "Secondary title pane Inactive");
+
+ /**
+ * Tool bar.
+ */
+ public final static DecorationAreaType TOOLBAR = new DecorationAreaType(
+ "Toolbar");
+
+ /**
+ * Any area that can be placed in the top portion of its window. Menu bar is
+ * an example of a core Swing component. <code>JXHeader</code> and
+ * <code>JXTitledPanel</code> titled area (components from <a
+ * href="https://swingx.dev.java.net">SwingX</a> suite) are examples of
+ * third-party components.
+ */
+ public final static DecorationAreaType HEADER = new DecorationAreaType(
+ "Header");
+
+ /**
+ * Any area that can be placed in the bottom portion of its window.
+ * <code>JXStatusBar</code> component from <a
+ * href="https://swingx.dev.java.net">SwingX</a> suite is an example of a
+ * third-party component.
+ */
+ public final static DecorationAreaType FOOTER = new DecorationAreaType(
+ "Footer");
+
+ /**
+ * Any general area that does not fit for the other types.
+ * <code>JXTaskPaneContainer</code> component from <a
+ * href="https://swingx.dev.java.net">SwingX</a> suite is an example of a
+ * third-party component.
+ */
+ public final static DecorationAreaType GENERAL = new DecorationAreaType(
+ "General");
+
+ /**
+ * The default decoration area type. Components placed in areas with this
+ * type do not get any special background decoration painting.
+ */
+ public final static DecorationAreaType NONE = new DecorationAreaType("None");
+
+ public String getDisplayName() {
+ return this.displayName;
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/SchemeBaseColors.java b/substance/src/main/java/org/pushingpixels/substance/api/SchemeBaseColors.java
new file mode 100644
index 0000000..c730c9b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/SchemeBaseColors.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.trait.SubstanceTrait;
+
+/**
+ * Interface for base color scheme colors.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SchemeBaseColors extends SubstanceTrait {
+ /**
+ * Retrieves the foreground color.
+ *
+ * @return Foreground color.
+ */
+ public Color getForegroundColor();
+
+ /**
+ * Retrieves the ultra-light color.
+ *
+ * @return Ultra-light color.
+ */
+ public Color getUltraLightColor();
+
+ /**
+ * Retrieves the extra color.
+ *
+ * @return Extra color.
+ */
+ public Color getExtraLightColor();
+
+ /**
+ * Retrieves the light color.
+ *
+ * @return Light color.
+ */
+ public Color getLightColor();
+
+ /**
+ * Retrieves the medium color.
+ *
+ * @return Medium color.
+ */
+ public Color getMidColor();
+
+ /**
+ * Retrieves the dark color.
+ *
+ * @return Dark color.
+ */
+ public Color getDarkColor();
+
+ /**
+ * Retrieves the ultra-dark color.
+ *
+ * @return Ultra-dark color.
+ */
+ public Color getUltraDarkColor();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/SchemeDerivedColors.java b/substance/src/main/java/org/pushingpixels/substance/api/SchemeDerivedColors.java
new file mode 100644
index 0000000..355bb64
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/SchemeDerivedColors.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+import java.awt.Color;
+
+/**
+ * Interface for derived color scheme colors.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SchemeDerivedColors {
+ /**
+ * Returns the watermark stamp color for <code>this</code> scheme.
+ *
+ * @return Watermark stamp color for <code>this</code> scheme.
+ */
+ public Color getWatermarkStampColor();
+
+ /**
+ * Returns the watermark light color for <code>this</code> scheme.
+ *
+ * @return Watermark light color for <code>this</code> scheme.
+ */
+ public Color getWatermarkLightColor();
+
+ /**
+ * Returns the watermark dark color for <code>this</code> scheme.
+ *
+ * @return Watermark dark color for <code>this</code> scheme.
+ */
+ public Color getWatermarkDarkColor();
+
+ /**
+ * Returns the line color for <code>this</code> scheme.
+ *
+ * @return The line color for <code>this</code> scheme.
+ */
+ public Color getLineColor();
+
+ /**
+ * Returns the selection background color for <code>this</code> scheme.
+ *
+ * @return The selection background color for <code>this</code> scheme.
+ */
+ public Color getSelectionBackgroundColor();
+
+ /**
+ * Returns the selection foreground color for <code>this</code> scheme.
+ *
+ * @return The selection foreground color for <code>this</code> scheme.
+ */
+ public Color getSelectionForegroundColor();
+
+ /**
+ * Returns the background fill color for <code>this</code> scheme.
+ *
+ * @return The background fill color for <code>this</code> scheme.
+ */
+ public Color getBackgroundFillColor();
+
+ /**
+ * Returns the text background fill color for <code>this</code> scheme.
+ *
+ * @return The text background fill color for <code>this</code> scheme.
+ */
+ public Color getTextBackgroundFillColor();
+
+ /**
+ * Returns the focus ring color for <code>this</code> scheme.
+ *
+ * @return The focus ring color for <code>this</code> scheme.
+ */
+ public Color getFocusRingColor();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/SchemeDerivedColorsResolver.java b/substance/src/main/java/org/pushingpixels/substance/api/SchemeDerivedColorsResolver.java
new file mode 100644
index 0000000..6fad645
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/SchemeDerivedColorsResolver.java
@@ -0,0 +1,107 @@
+package org.pushingpixels.substance.api;
+
+import java.awt.Color;
+
+
+/**
+ * {@code SchemeDerivedColorResolver}s must be immutable. The resolvers are passed to derived color
+ * schemes to ensure that derived scheme resolve derived colors in the same way as the base scheme.
+ *
+ * @author Karl Schaefer
+ */
+public interface SchemeDerivedColorsResolver {
+ /**
+ * Determines if this resolver is for dark color schemes.
+ *
+ * @return <code>true</code> if it should be used in dark schemes
+ */
+ boolean isDark();
+
+ /**
+ * Inverts this resolver, for use with inverted color schemes and switching from light to dark
+ * schemes or vice versa.
+ * <p>
+ * Some resolvers may not support this option. They may choose to throw an
+ * {@code UnsupportedOperationException} in that case. Instead of throwing the exception
+ * developers may choose to simply return {@code this} signifying that the resolver cannot be
+ * inverted. Another option would be to use assertions, allowing the developers to discover
+ * mistakes during creation, but still being useful for clients:
+ *
+ * <pre>
+ * public void SchemeDerivedColorsResolver invert() {
+ * assert false : "this resolver cannot be inverted";
+ *
+ * return this;
+ * }
+ * </pre>
+ *
+ *
+ * @return an inversion of this resolver
+ * @throws UnsupportedOperationException
+ * if this resolver cannot be inverted
+ */
+ SchemeDerivedColorsResolver invert();
+
+ /**
+ * Resolves a derived color for a given color scheme.
+ *
+ * @return the watermark stamp color for the supplied scheme.
+ */
+ Color getWatermarkStampColor(SubstanceColorScheme colorScheme);
+
+ /**
+ * Returns the watermark light color for <code>this</code> scheme.
+ *
+ * @return Watermark light color for <code>this</code> scheme.
+ */
+ Color getWatermarkLightColor(SubstanceColorScheme colorScheme);
+
+ /**
+ * Returns the watermark dark color for <code>this</code> scheme.
+ *
+ * @return Watermark dark color for <code>this</code> scheme.
+ */
+ Color getWatermarkDarkColor(SubstanceColorScheme colorScheme);
+
+ /**
+ * Returns the line color for <code>this</code> scheme.
+ *
+ * @return The line color for <code>this</code> scheme.
+ */
+ Color getLineColor(SubstanceColorScheme colorScheme);
+
+ /**
+ * Returns the selection background color for <code>this</code> scheme.
+ *
+ * @return The selection background color for <code>this</code> scheme.
+ */
+ Color getSelectionBackgroundColor(SubstanceColorScheme colorScheme);
+
+ /**
+ * Returns the selection foreground color for <code>this</code> scheme.
+ *
+ * @return The selection foreground color for <code>this</code> scheme.
+ */
+ Color getSelectionForegroundColor(SubstanceColorScheme colorScheme);
+
+ /**
+ * Returns the background fill color for <code>this</code> scheme.
+ *
+ * @return The background fill color for <code>this</code> scheme.
+ */
+ Color getBackgroundFillColor(SubstanceColorScheme colorScheme);
+
+ /**
+ * Returns the text background fill color for <code>this</code> scheme.
+ *
+ * @return The text background fill color for <code>this</code> scheme.
+ */
+ Color getTextBackgroundFillColor(SubstanceColorScheme colorScheme);
+
+ /**
+ * Returns the focus ring color for <code>this</code> scheme.
+ *
+ * @return The focus ring color for <code>this</code> scheme.
+ */
+ Color getFocusRingColor(SubstanceColorScheme colorScheme);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/SubstanceColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/SubstanceColorScheme.java
new file mode 100644
index 0000000..e838363
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/SubstanceColorScheme.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.trait.SubstanceTrait;
+
+/**
+ * General interface for color schemes.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SubstanceColorScheme extends SubstanceTrait, SchemeBaseColors,
+ SchemeDerivedColors {
+ /**
+ * Returns indication whether this color scheme uses dark colors. Note that
+ * this method may be removed in the future.
+ *
+ * @return <code>true</code> if this color scheme uses dark colors,
+ * <code>false</code> otherwise.
+ */
+ public boolean isDark();
+
+ /**
+ * Creates a shift version of <code>this</code> scheme.
+ *
+ * @param backgroundShiftColor
+ * Shift color for background colors. Should have full opacity.
+ * @param backgroundShiftFactor
+ * Value in 0.0...1.0 range. Larger values shift more towards the
+ * specified color.
+ * @param foregroundShiftColor
+ * Shift color for foreground colors. Should have full opacity.
+ * @param foregroundShiftFactor
+ * Value in 0.0...1.0 range. Larger values shift more towards the
+ * specified color.
+ * @return Shift version of <code>this</code> scheme.
+ */
+ public SubstanceColorScheme shift(Color backgroundShiftColor,
+ double backgroundShiftFactor, Color foregroundShiftColor,
+ double foregroundShiftFactor);
+
+ /**
+ * Creates a shift version of <code>this</code> scheme.
+ *
+ * @param backgroundShiftColor
+ * Shift color for background colors. Should have full opacity.
+ * @param backgroundShiftFactor
+ * Value in 0.0...1.0 range. Larger values shift more towards the
+ * specified color.
+ * @return Shift version of <code>this</code> scheme that does not change
+ * the foreground color.
+ */
+ public SubstanceColorScheme shiftBackground(Color backgroundShiftColor,
+ double backgroundShiftFactor);
+
+ /**
+ * Creates a tinted (shifted towards white) version of <code>this</code>
+ * color scheme.
+ *
+ * @param tintFactor
+ * Value in 0.0...1.0 range. Larger values shift more towards
+ * white color.
+ * @return Tinted version of <code>this</code> scheme.
+ */
+ public SubstanceColorScheme tint(double tintFactor);
+
+ /**
+ * Creates a toned (shifted towards gray) version of <code>this</code> color
+ * scheme.
+ *
+ * @param toneFactor
+ * Value in 0.0...1.0 range. Larger values shift more towards
+ * gray color.
+ * @return Toned version of <code>this</code> scheme.
+ */
+ public SubstanceColorScheme tone(double toneFactor);
+
+ /**
+ * Creates a shaded (shifted towards black) version of <code>this</code>
+ * color scheme.
+ *
+ * @param shadeFactor
+ * Value in 0.0...1.0 range. Larger values shift more towards
+ * black color.
+ * @return Shaded version of <code>this</code> scheme.
+ */
+ public SubstanceColorScheme shade(double shadeFactor);
+
+ /**
+ * Creates a saturated or desaturated version of <code>this</code> scheme.
+ * The value and brightness stay the same.
+ *
+ * @param saturateFactor
+ * Value in -1.0...1.0 range. Positive values create more
+ * saturated colors. Negative values create more desaturated
+ * colors.
+ * @return Saturated version of <code>this</code> scheme.
+ */
+ public SubstanceColorScheme saturate(double saturateFactor);
+
+ /**
+ * Creates an inverted version of <code>this</code> scheme.
+ *
+ * @return Inverted version of <code>this</code> scheme.
+ */
+ public SubstanceColorScheme invert();
+
+ /**
+ * Creates a negated version of <code>this</code> scheme.
+ *
+ * @return Negated version of <code>this</code> scheme.
+ */
+ public SubstanceColorScheme negate();
+
+ /**
+ * Creates a hue-shifted (in HSB space) version of <code>this</code> color
+ * scheme.
+ *
+ * @param hueShiftFactor
+ * Value in -1.0...1.0 range.
+ * @return Hue-shifted version of <code>this</code> scheme.
+ */
+ public SubstanceColorScheme hueShift(double hueShiftFactor);
+
+ /**
+ * This method is a fluent-interface builder utility for setting the display
+ * name for this color scheme. The implementation must return the same
+ * <code>this</code> instance.
+ *
+ * @param colorSchemeDisplayName
+ * New display name for this color scheme.
+ * @return This color scheme.
+ */
+ public SubstanceColorScheme named(String colorSchemeDisplayName);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/SubstanceColorSchemeBundle.java b/substance/src/main/java/org/pushingpixels/substance/api/SubstanceColorSchemeBundle.java
new file mode 100644
index 0000000..5084546
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/SubstanceColorSchemeBundle.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+import java.awt.Component;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.pushingpixels.substance.internal.colorscheme.BlendBiColorScheme;
+
+/**
+ * Color scheme bundle. Defines the visual appearance of a single decoration
+ * area of a skin.
+ *
+ * @author Kirill Grouchnikov
+ * @see DecorationAreaType
+ * @see ColorSchemeAssociationKind
+ * @see SubstanceSkin
+ */
+public class SubstanceColorSchemeBundle {
+ /**
+ * The active color scheme of this bundle.
+ */
+ protected SubstanceColorScheme activeColorScheme;
+
+ /**
+ * The enabled color scheme of this bundle.
+ */
+ protected SubstanceColorScheme enabledColorScheme;
+
+ /**
+ * The disabled color scheme of this bundle.
+ */
+ protected SubstanceColorScheme disabledColorScheme;
+
+ /**
+ * Maps from component state to the alpha channel applied on color scheme.
+ * This map doesn't have to contain entries for all {@link ComponentState}
+ * instances.
+ */
+ protected Map<ComponentState, Float> stateAlphaMap;
+
+ /**
+ * Maps from component state to the alpha channel applied on highlight color
+ * scheme. This map doesn't have to contain entries for all
+ * {@link ComponentState} instances.
+ */
+ protected Map<ComponentState, Float> stateHighlightSchemeAlphaMap;
+
+ /**
+ * If there is no explicitly registered color scheme for pressed component
+ * state, this field will contain a synthesized color scheme for a pressed
+ * state.
+ */
+ protected SubstanceColorScheme pressedScheme;
+
+ /**
+ * If there is no explicitly registered color scheme for the disabled
+ * selected component state, this field will contain a synthesized color
+ * scheme for the disabled selected state.
+ */
+ protected SubstanceColorScheme disabledSelectedScheme;
+
+ /**
+ * If there is no explicitly registered color scheme for the selected
+ * component state, this field will contain a synthesized color scheme for
+ * the selected state.
+ */
+ protected SubstanceColorScheme selectedScheme;
+
+ /**
+ * If there is no explicitly registered color scheme for the rollover
+ * selected component state, this field will contain a synthesized color
+ * scheme for the rollover selected state.
+ */
+ protected SubstanceColorScheme rolloverSelectedScheme;
+
+ /**
+ * Maps from color scheme association kinds to the map of color schemes.
+ * Different visual parts of controls in the specific decoration are can be
+ * painted with different color schemes. For example, a rollover button can
+ * use a light orange scheme for the gradient fill and a dark gray scheme
+ * for the border. In this case, this map will have:
+ *
+ * <ul>
+ * <li>An entry with key {@link ColorSchemeAssociationKind#FILL}. This entry
+ * has a map entry with key {@link ComponentState#SELECTED} and value that
+ * points to the light orange scheme.</li>
+ * <li>An entry with key {@link ColorSchemeAssociationKind#BORDER}. This
+ * entry has a map entry with key {@link ComponentState#SELECTED} and value
+ * that points to the dark gray scheme.</li>
+ * </ul>
+ */
+ protected Map<ColorSchemeAssociationKind, Map<ComponentState, SubstanceColorScheme>> colorSchemeMap;
+
+ protected Map<ColorSchemeAssociationKind, Map<ComponentState, ComponentState>> bestFillMap;
+
+ /**
+ * Creates a new color scheme bundle.
+ *
+ * @param activeColorScheme
+ * The active color scheme of this bundle.
+ * @param enabledColorScheme
+ * The enabled color scheme of this bundle.
+ * @param disabledColorScheme
+ * The disabled color scheme of this bundle.
+ */
+ public SubstanceColorSchemeBundle(SubstanceColorScheme activeColorScheme,
+ SubstanceColorScheme enabledColorScheme,
+ SubstanceColorScheme disabledColorScheme) {
+ this.activeColorScheme = activeColorScheme;
+ this.enabledColorScheme = enabledColorScheme;
+ this.disabledColorScheme = disabledColorScheme;
+ this.stateAlphaMap = new HashMap<ComponentState, Float>();
+ // ComponentState.class);
+ this.stateHighlightSchemeAlphaMap = new HashMap<ComponentState, Float>();
+ // ComponentState.class);
+
+ this.colorSchemeMap = new HashMap<ColorSchemeAssociationKind, Map<ComponentState, SubstanceColorScheme>>();
+ for (ColorSchemeAssociationKind associationKind : ColorSchemeAssociationKind
+ .values()) {
+ this.colorSchemeMap.put(associationKind,
+ new HashMap<ComponentState, SubstanceColorScheme>());
+ }
+
+ this.bestFillMap = new HashMap<ColorSchemeAssociationKind, Map<ComponentState, ComponentState>>();
+ for (ColorSchemeAssociationKind associationKind : ColorSchemeAssociationKind
+ .values()) {
+ this.bestFillMap.put(associationKind,
+ new HashMap<ComponentState, ComponentState>());
+ }
+ }
+
+ /**
+ * Registers a color scheme for the specific component state.
+ *
+ * @param stateColorScheme
+ * Color scheme for the specified component state.
+ * @param alpha
+ * Alpha channel for the color scheme.
+ * @param states
+ * Component states.
+ */
+ public void registerColorScheme(SubstanceColorScheme stateColorScheme,
+ float alpha, ComponentState... states) {
+ if (states != null) {
+ for (ComponentState state : states) {
+ this.colorSchemeMap.get(ColorSchemeAssociationKind.FILL).put(
+ state, stateColorScheme);
+ this.stateAlphaMap.put(state, alpha);
+ }
+ }
+ }
+
+ /**
+ * Registers a color scheme for the specific component state.
+ *
+ * @param stateColorScheme
+ * Color scheme for the specified component state.
+ * @param states
+ * Component states.
+ */
+ public void registerColorScheme(SubstanceColorScheme stateColorScheme,
+ ComponentState... states) {
+ this.registerColorScheme(stateColorScheme, 1.0f, states);
+ }
+
+ /**
+ * Registers a highlight color scheme for the specific component state if
+ * the component state is not <code>null</code>, or a global highlight color
+ * scheme otherwise.
+ *
+ * @param stateHighlightScheme
+ * Highlight color scheme for the specified component state.
+ * @param states
+ * Component states. If <code>null</code>, the specified color
+ * scheme will be applied for all states left unspecified.
+ */
+ public void registerHighlightColorScheme(
+ SubstanceColorScheme stateHighlightScheme, ComponentState... states) {
+ if ((states == null) || (states.length == 0)) {
+ for (ComponentState state : ComponentState.getAllStates()) {
+ if (this.colorSchemeMap.get(
+ ColorSchemeAssociationKind.HIGHLIGHT)
+ .containsKey(state))
+ continue;
+ if (state.isDisabled())
+ continue;
+ if (state == ComponentState.ENABLED)
+ continue;
+ // this.stateHighlightColorSchemeMap.put(state,
+ // stateHighlightScheme);
+ this.colorSchemeMap.get(ColorSchemeAssociationKind.HIGHLIGHT)
+ .put(state, stateHighlightScheme);
+ }
+ } else {
+ for (ComponentState state : states) {
+ this.colorSchemeMap.get(ColorSchemeAssociationKind.HIGHLIGHT)
+ .put(state, stateHighlightScheme);
+ }
+ }
+ }
+
+ /**
+ * Registers a highlight color scheme for the specific component state if
+ * the component state is not <code>null</code>, or a global highlight color
+ * scheme otherwise.
+ *
+ * @param highlightScheme
+ * Highlight color scheme for the specified component states.
+ * @param alpha
+ * Alpha channel for the highlight color scheme.
+ * @param states
+ * Component states. If <code>null</code>, the specified color
+ * scheme will be applied for all states left unspecified.
+ */
+ public void registerHighlightColorScheme(
+ SubstanceColorScheme highlightScheme, float alpha,
+ ComponentState... states) {
+ if (highlightScheme == null) {
+ throw new IllegalArgumentException("Cannot pass null color scheme");
+ }
+
+ if ((states == null) || (states.length == 0)) {
+ for (ComponentState state : ComponentState.getAllStates()) {
+ if (state.isDisabled())
+ continue;
+ if (state == ComponentState.ENABLED)
+ continue;
+ if (!this.colorSchemeMap.get(
+ ColorSchemeAssociationKind.HIGHLIGHT)
+ .containsKey(state))
+ this.colorSchemeMap.get(
+ ColorSchemeAssociationKind.HIGHLIGHT).put(state,
+ highlightScheme);
+ if (!this.stateHighlightSchemeAlphaMap.containsKey(state))
+ this.stateHighlightSchemeAlphaMap.put(state, alpha);
+ }
+ } else {
+ for (ComponentState state : states) {
+ this.colorSchemeMap.get(ColorSchemeAssociationKind.HIGHLIGHT)
+ .put(state, highlightScheme);
+ this.stateHighlightSchemeAlphaMap.put(state, alpha);
+ }
+ }
+ }
+
+ /**
+ * Returns the color scheme of the specified component in the specified
+ * component state.
+ *
+ * @param componentState
+ * Component state.
+ * @return The color scheme of the component in the specified component
+ * state.
+ */
+ public SubstanceColorScheme getColorScheme(ComponentState componentState) {
+ SubstanceColorScheme registered = this.colorSchemeMap.get(
+ ColorSchemeAssociationKind.FILL).get(componentState);
+ if (registered != null)
+ return registered;
+
+ // if (componentState.isActive()) {
+ // for now look for the best fit only on active states
+ Map<ComponentState, ComponentState> bestFitForFill = this.bestFillMap
+ .get(ColorSchemeAssociationKind.FILL);
+ if (!bestFitForFill.containsKey(componentState)) {
+ Collection<ComponentState> registeredStates = this.colorSchemeMap
+ .get(ColorSchemeAssociationKind.FILL).keySet();
+ bestFitForFill.put(componentState, componentState
+ .bestFit(registeredStates));
+ }
+ ComponentState bestFit = bestFitForFill.get(componentState);
+ if (bestFit != null) {
+ registered = this.colorSchemeMap.get(
+ ColorSchemeAssociationKind.FILL).get(bestFit);
+ if (registered != null)
+ return registered;
+ }
+ // }
+
+ if (componentState.isFacetActive(ComponentStateFacet.PRESS)) {
+ if (this.pressedScheme == null) {
+ this.pressedScheme = this.activeColorScheme.shade(0.2)
+ .saturate(0.1);
+ }
+ return this.pressedScheme;
+ }
+ if (componentState == ComponentState.DISABLED_SELECTED) {
+ if (this.disabledSelectedScheme == null) {
+ this.disabledSelectedScheme = new BlendBiColorScheme(
+ this.activeColorScheme, this.disabledColorScheme, 0.25);
+ }
+ return this.disabledSelectedScheme;
+ }
+ if (componentState == ComponentState.SELECTED) {
+ if (this.selectedScheme == null) {
+ this.selectedScheme = this.activeColorScheme.saturate(0.2);
+ }
+ return this.selectedScheme;
+ }
+ if (componentState == ComponentState.ROLLOVER_SELECTED) {
+ if (this.rolloverSelectedScheme == null) {
+ this.rolloverSelectedScheme = this.activeColorScheme.tint(0.1)
+ .saturate(0.1);
+ }
+ return this.rolloverSelectedScheme;
+ }
+
+ ComponentState hardFallback = componentState.getHardFallback();
+ if (hardFallback != null)
+ return this.getColorScheme(hardFallback);
+
+ if (componentState == ComponentState.ENABLED)
+ return this.enabledColorScheme;
+ if (componentState.isDisabled())
+ return this.disabledColorScheme;
+ return this.activeColorScheme;
+ }
+
+ /**
+ * Returns the alpha channel of the highlight color scheme of the component.
+ *
+ * @param comp
+ * Component.
+ * @param componentState
+ * Component state.
+ * @return Highlight color scheme alpha channel.
+ */
+ public float getHighlightAlpha(Component comp, ComponentState componentState) {
+ Float registered = this.stateHighlightSchemeAlphaMap
+ .get(componentState);
+ if (registered != null)
+ return registered;
+
+ return -1.0f;
+ }
+
+ /**
+ * Returns the alpha channel of the color scheme of the component.
+ *
+ * @param comp
+ * Component.
+ * @param componentState
+ * Component state.
+ * @return Color scheme alpha channel.
+ */
+ public float getAlpha(Component comp, ComponentState componentState) {
+ Float registered = this.stateAlphaMap.get(componentState);
+ if (registered != null)
+ return registered;
+
+ return -1.0f;
+ }
+
+ /**
+ * Returns the active color scheme of this bundle.
+ *
+ * @return The active color scheme of this bundle.
+ */
+ public SubstanceColorScheme getActiveColorScheme() {
+ return activeColorScheme;
+ }
+
+ /**
+ * Returns the enabled color scheme of this bundle.
+ *
+ * @return The enabled color scheme of this bundle.
+ */
+ public SubstanceColorScheme getEnabledColorScheme() {
+ return enabledColorScheme;
+ }
+
+ /**
+ * Returns the disabled color scheme of this bundle.
+ *
+ * @return The disabled color scheme of this bundle.
+ */
+ public SubstanceColorScheme getDisabledColorScheme() {
+ return disabledColorScheme;
+ }
+
+ /**
+ * Registers the color scheme to be used for the specified visual area of
+ * controls under the specified states. For example, if the light orange
+ * scheme has to be used for gradient fill of rollover selected and rollover
+ * controls, the parameters would be:
+ *
+ * <ul>
+ * <li><code>scheme</code>=light orange scheme</li>
+ * <li>
+ * <code>associationKind</code>={@link ColorSchemeAssociationKind#FILL}</li>
+ * <li>
+ * <code>states</code>={@link ComponentState#ROLLOVER_SELECTED},
+ * {@link ComponentState#ROLLOVER_UNSELECTED}</li>
+ * </ul>
+ *
+ * @param scheme
+ * Color scheme.
+ * @param associationKind
+ * Color scheme association kind that specifies the visual areas
+ * of controls to be painted with this color scheme.
+ * @param states
+ * Component states that further restrict the usage of the
+ * specified color scheme.
+ * @since version 5.1
+ */
+ public void registerColorScheme(SubstanceColorScheme scheme,
+ ColorSchemeAssociationKind associationKind,
+ ComponentState... states) {
+ if (scheme == null) {
+ throw new IllegalArgumentException("Cannot pass null color scheme");
+ }
+
+ if ((states == null) || (states.length == 0)) {
+ for (ComponentState state : ComponentState.getAllStates()) {
+ if (this.colorSchemeMap.get(associationKind).containsKey(state))
+ continue;
+ this.colorSchemeMap.get(associationKind).put(state, scheme);
+ }
+ } else {
+ for (ComponentState state : states) {
+ this.colorSchemeMap.get(associationKind).put(state, scheme);
+ }
+ }
+ }
+
+ /**
+ * Returns the color scheme to be used for painting the specified visual
+ * area of the component under the specified component state.
+ *
+ * @param associationKind
+ * Color scheme association kind.
+ * @param componentState
+ * Component state.
+ * @return Color scheme to be used for painting the specified visual area of
+ * the component under the specified component state.
+ * @see #registerColorScheme(SubstanceColorScheme, ComponentState...)
+ * @since version 5.1
+ */
+ public SubstanceColorScheme getColorScheme(
+ ColorSchemeAssociationKind associationKind,
+ ComponentState componentState) {
+ if (associationKind == ColorSchemeAssociationKind.FILL)
+ return this.getColorScheme(componentState);
+
+ SubstanceColorScheme registered = this.colorSchemeMap.get(
+ associationKind).get(componentState);
+ if (registered != null)
+ return registered;
+
+ // if (componentState.isActive()) {
+ // for now look for the best fit only on active states
+ Map<ComponentState, ComponentState> bestFitForState = this.bestFillMap
+ .get(associationKind);
+ if (!bestFitForState.containsKey(componentState)) {
+ Collection<ComponentState> registeredStates = this.colorSchemeMap
+ .get(associationKind).keySet();
+ bestFitForState.put(componentState, componentState
+ .bestFit(registeredStates));
+ }
+ ComponentState bestFit = bestFitForState.get(componentState);
+ if (bestFit != null) {
+ registered = this.colorSchemeMap.get(associationKind).get(bestFit);
+ if (registered != null)
+ return registered;
+ }
+ // }
+
+ ColorSchemeAssociationKind fallback = associationKind.getFallback();
+ if (fallback == null)
+ return null;
+
+ return getColorScheme(fallback, componentState);
+ }
+
+ /**
+ * Creates a new color scheme bundle that has the same settings as this
+ * color scheme bundle with the addition of applying the specified color
+ * scheme transformation on all the relevant color schemes
+ *
+ * @param transform
+ * Color scheme transformation.
+ * @return The new color scheme bundle.
+ */
+ SubstanceColorSchemeBundle transform(ColorSchemeTransform transform) {
+ // transform the basic schemes
+ SubstanceColorSchemeBundle result = new SubstanceColorSchemeBundle(
+ transform.transform(this.activeColorScheme), transform
+ .transform(this.enabledColorScheme), transform
+ .transform(this.disabledColorScheme));
+
+ for (Map.Entry<ColorSchemeAssociationKind, Map<ComponentState, SubstanceColorScheme>> entry : this.colorSchemeMap
+ .entrySet()) {
+ for (Map.Entry<ComponentState, SubstanceColorScheme> subEntry : entry
+ .getValue().entrySet()) {
+ result.colorSchemeMap.get(entry.getKey()).put(
+ subEntry.getKey(),
+ transform.transform(subEntry.getValue()));
+ }
+ }
+
+ // alphas are the same
+ if (this.stateAlphaMap != null) {
+ result.stateAlphaMap = new HashMap<ComponentState, Float>(
+ this.stateAlphaMap);
+ }
+
+ // highlight alphas are the same
+ if (this.stateHighlightSchemeAlphaMap != null) {
+ result.stateHighlightSchemeAlphaMap = new HashMap<ComponentState, Float>(
+ this.stateHighlightSchemeAlphaMap);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the set of all component states that have non-trivial alpha
+ * associated with them. Non-trivial alpha is a value that is strictly less
+ * than 1.0.
+ *
+ * @return All component states that have associated non-trivial alpha
+ * values.
+ */
+ Set<ComponentState> getStatesWithAlpha() {
+ Set<ComponentState> result = new HashSet<ComponentState>();// EnumSet.noneOf(ComponentState.class);
+ for (Map.Entry<ComponentState, Float> alphaEntry : this.stateAlphaMap
+ .entrySet()) {
+ if (alphaEntry.getValue() < 1.0f) {
+ result.add(alphaEntry.getKey());
+ }
+ }
+ return result;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/SubstanceConstants.java b/substance/src/main/java/org/pushingpixels/substance/api/SubstanceConstants.java
new file mode 100644
index 0000000..2b5925d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/SubstanceConstants.java
@@ -0,0 +1,661 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+
+import javax.swing.*;
+
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * <b>Substance</b> constants.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceConstants {
+ /**
+ * Enumerates available sides.
+ *
+ * @author Kirill Grouchnikov
+ * @see SubstanceLookAndFeel#BUTTON_OPEN_SIDE_PROPERTY
+ * @see SubstanceLookAndFeel#BUTTON_SIDE_PROPERTY
+ */
+ public static enum Side {
+ /**
+ * Left side.
+ */
+ LEFT,
+
+ /**
+ * Right side.
+ */
+ RIGHT,
+
+ /**
+ * Top side.
+ */
+ TOP,
+
+ /**
+ * Bottom side.
+ */
+ BOTTOM;
+ }
+
+ /**
+ * Enumerates focus indication kinds.
+ *
+ * @author Kirill Grouchnikov
+ * @see SubstanceLookAndFeel#FOCUS_KIND
+ */
+ public enum FocusKind {
+ /**
+ * No focus indication.
+ */
+ NONE {
+ @Override
+ public void paintFocus(Component mainComp, Component focusedComp,
+ TransitionAwareUI transitionAwareUI, Graphics2D graphics,
+ Shape focusShape, Rectangle textRect, int extraPadding) {
+ }
+ },
+
+ /**
+ * Focus indication around the text.
+ */
+ TEXT {
+ @Override
+ public void paintFocus(Component mainComp, Component focusedComp,
+ TransitionAwareUI transitionAwareUI, Graphics2D graphics,
+ Shape focusShape, Rectangle textRect, int extraPadding) {
+ if (textRect == null)
+ return;
+ if ((textRect.width == 0) || (textRect.height == 0))
+ return;
+
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(mainComp);
+ float dashLength = getDashLength(fontSize);
+ float dashGap = getDashGap(fontSize);
+ float dashPhase = (dashLength + dashGap)
+ * (1.0f - transitionAwareUI.getTransitionTracker()
+ .getFocusLoopPosition());
+
+ graphics.setStroke(new BasicStroke(SubstanceSizeUtils
+ .getFocusStrokeWidth(fontSize), BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_ROUND, 0.0f, new float[] { dashLength,
+ dashGap }, dashPhase));
+
+ int delta = ((mainComp instanceof JComboBox) || (mainComp instanceof JSpinner)) ? 0
+ : 1;
+ GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(
+ textRect.width + 2 * delta, textRect.height,
+ SubstanceSizeUtils
+ .getClassicButtonCornerRadius(fontSize), null);
+
+ graphics.translate(textRect.x - delta, textRect.y);
+ graphics.draw(contour);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.utils.SubstanceConstants.FocusKind
+ * #isAnimated ()
+ */
+ @Override
+ public boolean isAnimated() {
+ return true;
+ }
+ },
+
+ /**
+ * Focus indication around the whole component.
+ */
+ ALL {
+ @Override
+ public void paintFocus(Component mainComp, Component focusedComp,
+ TransitionAwareUI transitionAwareUI, Graphics2D graphics,
+ Shape focusShape, Rectangle textRect, int extraPadding) {
+ if ((focusShape == null)
+ && ((mainComp instanceof AbstractButton)
+ && !(mainComp instanceof JCheckBox) && !(mainComp instanceof JRadioButton))) {
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(mainComp);
+ if (shaper == null)
+ return;
+
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(mainComp);
+ float dashLength = getDashLength(fontSize);
+ float dashGap = getDashGap(fontSize);
+ float dashPhase = (dashLength + dashGap)
+ * (1.0f - transitionAwareUI.getTransitionTracker()
+ .getFocusLoopPosition());
+ graphics.setStroke(new BasicStroke(SubstanceSizeUtils
+ .getFocusStrokeWidth(fontSize),
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 0.0f,
+ new float[] { dashLength, dashGap }, dashPhase));
+
+ Shape contour = shaper.getButtonOutline(
+ (AbstractButton) mainComp, null, mainComp
+ .getWidth(), mainComp.getHeight(), false);
+ graphics.draw(contour);
+ // }
+ } else {
+ // graphics.translate(textRect.x - 1, textRect.y - 1);
+ graphics.translate(1, 1);
+ Shape contour = (focusShape != null) ? focusShape
+ : SubstanceOutlineUtilities
+ .getBaseOutline(
+ mainComp.getWidth() - 2,
+ mainComp.getHeight() - 2,
+ SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(mainComp)),
+ null);
+
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(mainComp);
+ float dashLength = getDashLength(fontSize);
+ float dashGap = getDashGap(fontSize);
+ float dashPhase = (dashLength + dashGap)
+ * (1.0f - transitionAwareUI.getTransitionTracker()
+ .getFocusLoopPosition());
+ graphics.setStroke(new BasicStroke(SubstanceSizeUtils
+ .getFocusStrokeWidth(fontSize),
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 0.0f,
+ new float[] { dashLength, dashGap }, dashPhase));
+ graphics.draw(contour);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.utils.SubstanceConstants.FocusKind
+ * #isAnimated ()
+ */
+ @Override
+ public boolean isAnimated() {
+ return true;
+ }
+ },
+
+ /**
+ * Focus indication around the whole component, but moved 1 pixel inside
+ * the component.
+ */
+ ALL_INNER {
+ @Override
+ public void paintFocus(Component mainComp, Component focusedComp,
+ TransitionAwareUI transitionAwareUI, Graphics2D graphics,
+ Shape focusShape, Rectangle textRect, int extraPadding) {
+
+ if ((focusShape == null)
+ && ((mainComp instanceof AbstractButton)
+ && !(mainComp instanceof JCheckBox) && !(mainComp instanceof JRadioButton))) {
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(mainComp);
+ if (shaper == null)
+ return;
+
+ if (shaper.isProportionate()) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(mainComp);
+ float dashLength = getDashLength(fontSize);
+ float dashGap = getDashGap(fontSize);
+ float dashPhase = (dashLength + dashGap)
+ * (1.0f - transitionAwareUI
+ .getTransitionTracker()
+ .getFocusLoopPosition());
+ float focusStrokeWidth = SubstanceSizeUtils
+ .getFocusStrokeWidth(fontSize);
+ graphics.setStroke(new BasicStroke(focusStrokeWidth,
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND,
+ 0.0f, new float[] { dashLength, dashGap },
+ dashPhase));
+ int insetsPix = extraPadding;
+ Insets insets = new Insets(insetsPix, insetsPix,
+ insetsPix, insetsPix);
+
+ Shape contour = shaper.getButtonOutline(
+ (AbstractButton) mainComp, insets, mainComp
+ .getWidth(), mainComp.getHeight(),
+ false);
+ graphics.draw(contour);
+ }
+ } else {
+ graphics.translate(extraPadding / 2, extraPadding / 2);
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(mainComp);
+ Shape contour = (focusShape != null) ? focusShape
+ : SubstanceOutlineUtilities.getBaseOutline(mainComp
+ .getWidth()
+ - extraPadding, mainComp.getHeight()
+ - extraPadding, SubstanceSizeUtils
+ .getClassicButtonCornerRadius(fontSize),
+ null);
+
+ float dashLength = getDashLength(fontSize);
+ float dashGap = getDashGap(fontSize);
+ float dashPhase = (dashLength + dashGap)
+ * (1.0f - transitionAwareUI.getTransitionTracker()
+ .getFocusLoopPosition());
+
+ graphics.setStroke(new BasicStroke(SubstanceSizeUtils
+ .getFocusStrokeWidth(fontSize),
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 0.0f,
+ new float[] { dashLength, dashGap }, dashPhase));
+ graphics.draw(contour);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.utils.SubstanceConstants.FocusKind
+ * #isAnimated ()
+ */
+ @Override
+ public boolean isAnimated() {
+ return true;
+ }
+ },
+
+ /**
+ * Focus indication around the whole component, but moved 1 pixel inside
+ * the component.
+ */
+ ALL_STRONG_INNER {
+ @Override
+ public void paintFocus(Component mainComp, Component focusedComp,
+ TransitionAwareUI transitionAwareUI, Graphics2D graphics,
+ Shape focusShape, Rectangle textRect, int extraPadding) {
+ if ((focusShape == null)
+ && ((mainComp instanceof AbstractButton)
+ && !(mainComp instanceof JCheckBox) && !(mainComp instanceof JRadioButton))) {
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(mainComp);
+ if (shaper == null)
+ return;
+
+ if (shaper.isProportionate()) {
+ Insets insets = new Insets(extraPadding, extraPadding,
+ extraPadding, extraPadding);
+
+ Shape contour = shaper.getButtonOutline(
+ (AbstractButton) mainComp, insets, mainComp
+ .getWidth(), mainComp.getHeight(),
+ false);
+ graphics.draw(contour);
+ }
+ } else {
+ graphics.translate(extraPadding / 2, extraPadding / 2);
+ Shape contour = (focusShape != null) ? focusShape
+ : SubstanceOutlineUtilities
+ .getBaseOutline(
+ mainComp.getWidth() - extraPadding,
+ mainComp.getHeight() - extraPadding,
+ SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(mainComp)),
+ null);
+
+ graphics.draw(contour);
+ }
+ }
+ },
+
+ /**
+ * Focus indication under the component text.
+ */
+ UNDERLINE {
+ @Override
+ public void paintFocus(Component mainComp, Component focusedComp,
+ TransitionAwareUI transitionAwareUI, Graphics2D graphics,
+ Shape focusShape, Rectangle textRect, int extraPadding) {
+ if (textRect == null)
+ return;
+
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(mainComp);
+ float dashLength = getDashLength(fontSize);
+ float dashGap = getDashGap(fontSize);
+ float dashPhase = (dashLength + dashGap)
+ * (1.0f - transitionAwareUI.getTransitionTracker()
+ .getFocusLoopPosition());
+
+ graphics.setStroke(new BasicStroke(SubstanceSizeUtils
+ .getFocusStrokeWidth(fontSize), BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_ROUND, 0.0f, new float[] { dashLength,
+ dashGap }, dashPhase));
+
+ graphics.translate(textRect.x - 1, textRect.y);
+ graphics.drawLine(0, textRect.height - 1, textRect.width,
+ textRect.height - 1);
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.utils.SubstanceConstants.FocusKind
+ * #isAnimated ()
+ */
+ @Override
+ public boolean isAnimated() {
+ return true;
+ }
+ },
+
+ /**
+ * Strong focus indication under the component text.
+ */
+ STRONG_UNDERLINE {
+ @Override
+ public void paintFocus(Component mainComp, Component focusedComp,
+ TransitionAwareUI transitionAwareUI, Graphics2D graphics,
+ Shape focusShape, Rectangle textRect, int extraPadding) {
+ if (textRect == null)
+ return;
+
+ graphics.translate(textRect.x - 1, textRect.y);
+ graphics.drawLine(0, textRect.height - 1, textRect.width,
+ textRect.height - 1);
+ }
+ };
+
+ /**
+ * Paints the focus ring on the specified component.
+ *
+ * @param mainComp
+ * The main component for the focus painting.
+ * @param focusedComp
+ * The actual component that has the focus. For example, the
+ * main component can be a {@link JSpinner}, while the
+ * focused component is a text field inside the the spinner
+ * editor.
+ * @param graphics
+ * Graphics context.
+ * @param focusShape
+ * Focus shape. May be <code>null</code> - in this case, the
+ * bounds of <code>mainComp</code> will be used.
+ * @param textRect
+ * Text rectangle (if relevant).
+ * @param extraPadding
+ * Extra padding between the component bounds and the focus
+ * ring painting.
+ */
+ public abstract void paintFocus(Component mainComp,
+ Component focusedComp, TransitionAwareUI transitionAwareUI,
+ Graphics2D graphics, Shape focusShape, Rectangle textRect,
+ int extraPadding);
+
+ /**
+ * Returns DPI-aware dash length for dash-based focus painting.
+ *
+ * @param fontSize
+ * The font size of the component for focus painting.
+ * @return DPI-aware dash length for dash-based focus painting.
+ */
+ protected static float getDashLength(int fontSize) {
+ return 2.0f + SubstanceSizeUtils.getExtraPadding(fontSize);
+ }
+
+ /**
+ * Returns DPI-aware dash gap for dash-based focus painting.
+ *
+ * @param fontSize
+ * The font size of the component for focus painting.
+ * @return DPI-aware dash gap for dash-based focus painting.
+ */
+ protected static float getDashGap(int fontSize) {
+ return getDashLength(fontSize) / 2.0f;
+ }
+
+ /**
+ * Returns indication whether <code>this</code> focus kind can be
+ * animated. For example, focus rings painted with solid lines are
+ * generally static.
+ *
+ * @return <code>true</code> if <code>this</code> focus kind can be
+ * animated, <code>false</code> otherwise.
+ */
+ public boolean isAnimated() {
+ return false;
+ }
+ }
+
+ /**
+ * Enumerates of image-based watermarks kinds.
+ *
+ * @author Kirill Grouchnikov
+ * @see org.pushingpixels.substance.api.watermark.SubstanceImageWatermark#setKind(org.pushingpixels.substance.api.SubstanceConstants.ImageWatermarkKind)
+ * @see org.pushingpixels.substance.api.watermark.SubstanceImageWatermark#getKind()
+ */
+ public enum ImageWatermarkKind {
+ /**
+ * The default behaviour. The image is centered in the screen and scaled
+ * down if necessary.
+ */
+ SCREEN_CENTER_SCALE,
+
+ /**
+ * The image is tiled starting from the screen top-left corner and not
+ * scaled.
+ */
+ SCREEN_TILE,
+
+ /**
+ * The image is anchored to the top-left corner of the application frame
+ * and not scaled.
+ */
+ APP_ANCHOR,
+
+ /**
+ * The image is anchored to the center of the application frame and not
+ * scaled.
+ */
+ APP_CENTER,
+
+ /**
+ * The image is tiled starting from the top-left corner of the
+ * application frame and not scaled.
+ */
+ APP_TILE
+ }
+
+ /**
+ * Enumerates possible modes of closing tabs.
+ *
+ * @author Kirill Grouchnikov
+ * @see SubstanceLookAndFeel#TABBED_PANE_CLOSE_CALLBACK
+ */
+ public enum TabCloseKind {
+ /**
+ * Indicates that no tabs should be closed.
+ */
+ NONE,
+
+ /**
+ * Indicates that the specified tab should be closed.
+ */
+ THIS,
+
+ /**
+ * Indicates that all tabs should be closed.
+ */
+ ALL,
+
+ /**
+ * Indicates that all tabs except the specified should be closed.
+ */
+ ALL_BUT_THIS
+ }
+
+ /**
+ * Enumerates possible button policies for scroll panes.
+ *
+ * @author Kirill Grouchnikov
+ * @see SubstanceLookAndFeel#SCROLL_PANE_BUTTONS_POLICY
+ */
+ public enum ScrollPaneButtonPolicyKind {
+ /**
+ * The <code>empty</code> button policy - no buttons.
+ */
+ NONE,
+
+ /**
+ * The <code>opposite</code> (default) button policy - the decrease
+ * button is on one side of the scroll bar, and the increase button is
+ * on the other side of the scroll bar.
+ */
+ OPPOSITE,
+
+ /**
+ * The <code>adjacent</code> button policy - both the decrease button
+ * and the increase button are on the same side of the scroll bar
+ * adjacent to each other (like on Mac).
+ */
+ ADJACENT,
+
+ /**
+ * The <code>multiple</code> button policy - there are two decrease
+ * buttons on the opposite side of the scroll bar and the increase
+ * button is adjacent to the second decrease button. This combines the
+ * {@link #OPPOSITE} and the {@link #ADJACENT} policies together.
+ */
+ MULTIPLE,
+
+ /**
+ * The <code>multiple both</code> button policy - there are two pairs of
+ * decrease-increase buttons on the opposite sides of the scroll bar.
+ * This extends the {@link #MULTIPLE} policy.
+ */
+ MULTIPLE_BOTH
+ }
+
+ /**
+ * Enumerates possible values for menu gutter fill kind.
+ *
+ * @author Kirill Grouchnikov
+ * @see SubstanceLookAndFeel#MENU_GUTTER_FILL_KIND
+ */
+ public enum MenuGutterFillKind {
+ /**
+ * The <code>none</code> fill kind - draws no background in the menu
+ * gutter.
+ */
+ NONE,
+
+ /**
+ * The <code>soft fill</code> fill kind - draws light fill background in
+ * the menu gutter.
+ */
+ SOFT_FILL,
+
+ /**
+ * The <code>hard fill</code> fill kind - draws darker fill background
+ * in the menu gutter.
+ */
+ HARD_FILL,
+
+ /**
+ * The <code>soft</code> fill kind - draws gradient ranging from darker
+ * to light in the menu gutter.
+ */
+ SOFT,
+
+ /**
+ * The <code>hard</code> (default) fill kind - draws gradient ranging
+ * from darker to light in the menu gutter.
+ */
+ HARD
+ }
+
+ /**
+ * Tab content pane border kind.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.1
+ */
+ public enum TabContentPaneBorderKind {
+ /**
+ * The content pane has full border on all sides plus an additional line
+ * along the tab placement side (as in Firefox 2.0, Internet Explorer
+ * 7.0 and Nimbus). This is the default kind starting from version 4.1.
+ */
+ DOUBLE_FULL,
+
+ /**
+ * The content pane has full single border on all sides. This has been
+ * the default kind prior to version 4.1.
+ */
+ SINGLE_FULL,
+
+ /**
+ * The content pane has double border along the tab placement side.
+ */
+ DOUBLE_PLACEMENT,
+
+ /**
+ * The content pane has single border along the tab placement side.
+ */
+ SINGLE_PLACEMENT
+ }
+
+ /**
+ * Enumerates configurable Substance-specific widget types for
+ * {@link SubstanceLookAndFeel#setWidgetVisible(javax.swing.JRootPane, boolean, org.pushingpixels.substance.api.SubstanceConstants.SubstanceWidgetType...)}
+ * API.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public enum SubstanceWidgetType {
+ /**
+ * Menu search widget.
+ */
+ MENU_SEARCH,
+
+ /**
+ * Title pane heap status widget.
+ */
+ TITLE_PANE_HEAP_STATUS
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/SubstanceLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/SubstanceLookAndFeel.java
new file mode 100755
index 0000000..da95fb5
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/SubstanceLookAndFeel.java
@@ -0,0 +1,2463 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.plaf.IconUIResource;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicLookAndFeel;
+
+import org.pushingpixels.lafplugin.*;
+import org.pushingpixels.lafwidget.LafWidgetRepository;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.lafwidget.utils.LookUtils;
+import org.pushingpixels.substance.api.SubstanceConstants.MenuGutterFillKind;
+import org.pushingpixels.substance.api.SubstanceConstants.SubstanceWidgetType;
+import org.pushingpixels.substance.api.combo.ComboPopupPrototypeCallback;
+import org.pushingpixels.substance.api.fonts.*;
+import org.pushingpixels.substance.api.inputmaps.InputMapSet;
+import org.pushingpixels.substance.api.inputmaps.SubstanceInputMapUtilities;
+import org.pushingpixels.substance.api.shaper.*;
+import org.pushingpixels.substance.api.skin.SkinChangeListener;
+import org.pushingpixels.substance.api.skin.SkinInfo;
+import org.pushingpixels.substance.api.tabbed.BaseTabCloseListener;
+import org.pushingpixels.substance.api.tabbed.TabCloseCallback;
+import org.pushingpixels.substance.internal.contrib.jgoodies.looks.common.ShadowPopupFactory;
+import org.pushingpixels.substance.internal.fonts.FontPolicies;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.plugin.SubstanceSkinPlugin;
+import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * <p>
+ * Main class for <b>Substance </b> look and feel. <b>All</b> static methods in
+ * this class should be called when Substance is the currently set look and feel
+ * unless explicitly stated otherwise.
+ * </p>
+ *
+ * <p>
+ * Since version 5.0 this class is abstract. There are three options to use
+ * Substance:
+ * </p>
+ *
+ * <ul>
+ * <li>Use one of the core skin-based look-and-feels in the
+ * <code>org.pushingpixels.substance.skin</code> package.</li>
+ * <li>Extend this class and pass a skin instance to the
+ * {@link SubstanceLookAndFeel#SubstanceLookAndFeel(SubstanceSkin)} constructor.
+ * </li>
+ * <li>Call {@link SubstanceLookAndFeel#setSkin(String)} or
+ * {@link SubstanceLookAndFeel#setSkin(SubstanceSkin)} static methods. These
+ * methods do not require Substance to be the current look-and-feel.</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class SubstanceLookAndFeel extends BasicLookAndFeel {
+ /**
+ * The name of plugin configuration XML resource name. This is used for the
+ * <a href="https://laf-plugin.dev.java.net">laf-plugin</a> support layer of
+ * third-party components.
+ */
+ public static final String PLUGIN_XML = "META-INF/substance-plugin.xml";
+
+ /**
+ * Plugin manager for component plugins.
+ */
+ private static ComponentPluginManager componentPlugins;
+
+ /**
+ * Plugin manager for skin plugins.
+ */
+ private static PluginManager skinPlugins;
+
+ /**
+ * List of all listeners on skin changes.
+ */
+ protected final static Set<SkinChangeListener> skinChangeListeners = new HashSet<SkinChangeListener>();
+
+ /**
+ * List of all listeners on changing locales.
+ */
+ protected final static Set<LocaleChangeListener> localeChangeListeners = new HashSet<LocaleChangeListener>();
+
+ /**
+ * Indicates whether option dialogs (error, question, warning, info) should
+ * use constant color schemes for icon coloring. Note that since version
+ * 4.0, the default setting is <code>true</code> (use constant color
+ * scheme). To use color scheme-consistent coloring, call
+ * {@link #setToUseConstantThemesOnDialogs(boolean)} and pass
+ * <code>false</code>.
+ *
+ * @see #isToUseConstantThemesOnDialogs()
+ * @see #setToUseConstantThemesOnDialogs(boolean)
+ */
+ private static boolean toUseConstantThemesOnDialogs = true;
+
+ /**
+ * Change listener on keyboard focus manager - fix for defect 208.
+ */
+ protected PropertyChangeListener focusOwnerChangeListener;
+
+ /**
+ * The current keyboard focus manager - fix for defect 208.
+ */
+ protected KeyboardFocusManager currentKeyboardFocusManager;
+
+ /**
+ * Smart tree scroll animation facet. Disabled by default, use
+ * {@link AnimationConfigurationManager#allowAnimations(AnimationFacet)} to
+ * enable. </p>
+ *
+ * <p>
+ * Smart tree scroll is relevant for scroll panes containing a tree. When
+ * enabled, it automatically scrolls the tree horizontally when the viewport
+ * shows mainly empty area (especially relevant for multi-level trees with
+ * narrow viewports).
+ * </p>
+ *
+ * @since 4.0
+ */
+ public final static AnimationFacet TREE_SMART_SCROLL_ANIMATION_KIND = new AnimationFacet(
+ "substancelaf.treeSmartScrollAnimationKind", false);
+
+ /**
+ * Client property name for requesting that watermark should be painted on
+ * the component and its descendants. This property can be set either as
+ * client property on some component or as global property on
+ * {@link UIManager}. The value should be either {@link Boolean#TRUE} or
+ * {@link Boolean#FALSE}.
+ *
+ * <p>
+ * In order to compute whether the current watermark should be painted on a
+ * given component, its hierarchy is traversed bottom up. The first
+ * component that has this property set defines the watermark visibility. If
+ * neither component nor its ancestors define this property, the global
+ * setting on {@link UIManager} is checked. If there is no global setting,
+ * the watermark is <b>not</b> ignored (it is painted).
+ * </p>
+ *
+ * <p>
+ * There is special default setting for trees, tables, lists and text
+ * components. These show watermark only when this property is explicitly
+ * set to {@link Boolean#TRUE} on the component itself, one of its ancestors
+ * or the {@link UIManager}.
+ * </p>
+ *
+ * @since version 5.0
+ */
+ public static final String WATERMARK_VISIBLE = "substancelaf.watermark.visible";
+
+ /**
+ * Client property name for ignoring the default (minimum) dimension for a
+ * single button. This property can be set either on the specific button or
+ * as a global setting on {@link UIManager}. The value should be either
+ * {@link Boolean#TRUE} or {@link Boolean#FALSE}.
+ * <p>
+ * Note that {@link SubstanceButtonShaper} implementations are not required
+ * to respect this property. The current implementations of the default
+ * {@link StandardButtonShaper} and {@link ClassicButtonShaper} respect this
+ * property.
+ * </p>
+ *
+ * <p>
+ * Example of marking a button to ignore minimum dimension settings:
+ * </p>
+ * <code>
+ * JButton button = new JButton("text");<br>
+ * button.putClientProperty(SubstanceLookAndFeel.BUTTON_NO_MIN_SIZE_PROPERTY, <br>
+ * Boolean.TRUE);
+ * </code>
+ * <p>
+ * Example of marking all application buttons to ignore minimum dimension
+ * settings:
+ * </p>
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.BUTTON_NO_MIN_SIZE_PROPERTY, <br>
+ * Boolean.TRUE);
+ * </code>
+ *
+ * @since version 2.1
+ */
+ public static final String BUTTON_NO_MIN_SIZE_PROPERTY = "substancelaf.buttonnominsize";
+
+ /**
+ * Client property name for specifying that a single button / all
+ * application buttons should not paint the background. This property can be
+ * set on the specific button, its parent or as a global setting on
+ * {@link UIManager}. The value should be either {@link Boolean#TRUE} or
+ * {@link Boolean#FALSE}. Note that unlike the {@link #FLAT_PROPERTY}, a
+ * button marked with this property will <b>never</b> show the background
+ * (will always be painted flat).
+ *
+ * <p>
+ * Example of marking a button to never paint background:
+ * </p>
+ * <code>
+ * JButton button = new JButton("text");<br>
+ * button.putClientProperty(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY, <br>
+ * Boolean.TRUE);
+ * </code>
+ *
+ * <p>
+ * Example of marking all application buttons to never paint background:
+ * </p>
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY, <br>
+ * Boolean.TRUE);
+ * </code>
+ *
+ * @since version 2.3
+ * @see #FLAT_PROPERTY
+ */
+ public static final String BUTTON_PAINT_NEVER_PROPERTY = "substancelaf.buttonpaintnever";
+
+ /**
+ * Client property name for specifying a straight side for a single button.
+ * This property must be set on the specific button. The value can be:
+ *
+ * <p>
+ * <ul>
+ * <li>A value in {@link SubstanceConstants.Side} enum.
+ * <li>Set of values in {@link SubstanceConstants.Side} enum.
+ * </ul>
+ *
+ * <p>
+ * Note that the {@link SubstanceButtonShaper} implementations are not
+ * required to respect this property. The default
+ * {@link StandardButtonShaper} and {@link ClassicButtonShaper} respect this
+ * property.
+ * </p>
+ *
+ * <p>
+ * Example of marking a button to have straight north side:
+ * </p>
+ * <code>
+ * JButton button = new JButton("text");<br>
+ * button.putClientProperty(SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY,<br>
+ * SubstanceConstants.Side.RIGHT);
+ * </code>
+ *
+ * @since version 2.1
+ * @see #BUTTON_OPEN_SIDE_PROPERTY
+ */
+ public static final String BUTTON_SIDE_PROPERTY = "substancelaf.buttonside";
+
+ /**
+ * Client property name for specifying an open side for a single button.
+ * This property must be set on the specific button. The value can be:
+ *
+ * <p>
+ * <ul>
+ * <li>A value in {@link SubstanceConstants.Side} enum.
+ * <li>Set of values in {@link SubstanceConstants.Side} enum.
+ * </ul>
+ * </p>
+ * <p>
+ * Example of marking a button to have open top and west sides:
+ * </p>
+ * <code>
+ * JButton button = new JButton("text");<br>
+ * Set<Side> openSides = EnumSet.of(Side.TOP, Side.WEST);<br>
+ * button.putClientProperty(SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY, <br>
+ * openSides);
+ * </code>
+ *
+ * @since version 3.1
+ * @see #BUTTON_SIDE_PROPERTY
+ */
+ public static final String BUTTON_OPEN_SIDE_PROPERTY = "substancelaf.buttonopenSide";
+
+ /**
+ * Client property name for specifying the corner radius for buttons.
+ * Currently, this property is respected only on toolbar buttons. This
+ * property can be set on the specific toolbar button, on the specific
+ * toolbar (will hold for all buttons in the toolbar) or as a global setting
+ * on {@link UIManager}. The value should be a positive {@link Float}.
+ *
+ * <p>
+ * Example of specifying a (toolbar) button to have corner radius of 5
+ * pixels:
+ * </p>
+ * <code>
+ * JButton button = new JButton("text");<br>
+ * button.putClientProperty(SubstanceLookAndFeel.CORNER_RADIUS, <br>
+ * Float.valueOf(5.0f));
+ * </code>
+ *
+ * <p>
+ * Example of specifying all buttons of a toolbar to have corner radius of 3
+ * pixels:
+ * </p>
+ * <code>
+ * JToolBar toolbar = new JToolBar("toolbar");<br>
+ * toolbar.putClientProperty(SubstanceLookAndFeel.CORNER_RADIUS, <br>
+ * Float.valueOf(3.0f));
+ * </code>
+ *
+ * <p>
+ * Example of specifying all toolbar buttons to have corner radius of 0
+ * pixels:
+ * </p>
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.CORNER_RADIUS, Float.valueOf(0.0f));
+ * </code>
+ *
+ * @since version 3.0
+ */
+ public static final String CORNER_RADIUS = "substancelaf.cornerRadius";
+
+ /**
+ * Property name for specifying that the component should be painted flat
+ * (no background / border) when it's inactive. This property should be
+ * specified on a specific component or its parent and must have either
+ * {@link Boolean#TRUE} or {@link Boolean#FALSE} value.
+ *
+ * <p>
+ * Example how to mark a button to appear flat:
+ * </p>
+ *
+ * <code>
+ * JButton button = new JButton("text");<br>
+ * button.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY, <br>
+ * Boolean.TRUE);
+ * </code>
+ *
+ * @since version 3.0
+ * @see #BUTTON_PAINT_NEVER_PROPERTY
+ */
+ public static final String FLAT_PROPERTY = "substancelaf.componentFlat";
+
+ /**
+ * VM property name for specifying the heap status trace file. The trace
+ * file will contain information on the status of heap. The property value
+ * is used as a filename for tracing the heap status. Example for specifying
+ * the trace file name:
+ *
+ * <p>
+ * <code>
+ * -Dsubstancelaf.heapStatusTraceFile=C:/temp/myApp.heap.log
+ * </code>
+ * </p>
+ *
+ * @since version 5.0
+ */
+ public static final String HEAP_STATUS_TRACE_FILE = "substancelaf.heapStatusTraceFile";
+
+ /**
+ * Client property name for specifying that contents of a frame, dialog,
+ * internal frame, desktop icon or tab have been modified and not saved. The
+ * property can be set on:
+ * <p>
+ * <ul>
+ * <li>{@link JRootPane} - the <b>close</b> button of the title pane of the
+ * matching frame / dialog will be animated (in case that the frame / dialog
+ * have decorated title pane). In case the root pane belongs to a
+ * {@link JInternalFrame} and that frame is iconified (to a
+ * {@link javax.swing.JInternalFrame.JDesktopIcon}), the close button of the its desktop
+ * icon is animated as well.</li>
+ * <li>{@link JComponent} in a {@link JTabbedPane}. Based on the
+ * {@link #TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION} property presence,
+ * either the entire tab or its close button area is animated. In this case,
+ * this property must be set on the tab component itself, <b>not</b> on one
+ * of its child components.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * The animation cycles between red, orange and yellow color schemes. In
+ * most cases (all but tabs not marked with
+ * {@link #TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION} property), the
+ * animation will be visible only when the mouse hovers over the close
+ * button of the matching container (frame, dialog, internal frame, desktop
+ * icon, tab). The tooltip of the close button is changed as well to reflect
+ * that the container contents are marked as modified.
+ * </p>
+ *
+ * <p>
+ * Here is a sample text editing application that illustrates the use of
+ * this property. Once the contents of the text pane are changed, the frame
+ * is marked as modified. The <b>Save</b> button marks the frame as
+ * not-modified. In the real application, the listener on this button will
+ * need to persist the changes as well.
+ * </p>
+ *
+ * <code>
+ * public class Changer extends JFrame {<br>
+ * public Changer() {<br>
+ * super("Changer");<br>
+ * <br>
+ * this.setLayout(new BorderLayout());<br>
+ * JTextPane textArea = new JTextPane();<br>
+ * this.add(textArea, BorderLayout.CENTER);<br>
+ * textArea.getDocument().addDocumentListener(new
+ * DocumentListener() {<br>
+ * private void handleChange() {<br>
+ * getRootPane().putClientProperty(<br>
+ * SubstanceLookAndFeel.WINDOW_MODIFIED,
+ * Boolean.TRUE);<br>
+ * }<br>
+ * <br>
+ * public void
+ * changedUpdate(DocumentEvent e) {<br>
+ * handleChange();<br>
+ * }<br>
+ * <br>
+ * public void
+ * insertUpdate(DocumentEvent e) {<br>
+ * handleChange();<br>
+ * }<br>
+ * <br>
+ * public void
+ * removeUpdate(DocumentEvent e) {<br>
+ * handleChange();<br>
+ * }<br>
+ * });<br>
+ * <br>
+ * JPanel buttons = new JPanel(new
+ * FlowLayout(FlowLayout.RIGHT));<br>
+ * JButton saveButton = new JButton("Save");<br>
+ * saveButton.addActionListener(new ActionListener() {<br>
+ * public void
+ * actionPerformed(ActionEvent e) {<br>
+ * getRootPane().putClientProperty(<br>
+ * SubstanceLookAndFeel.WINDOW_MODIFIED,
+ * Boolean.FALSE);<br>
+ * }<br>
+ * });<br>
+ * <br>
+ * buttons.add(saveButton);<br>
+ * this.add(buttons, BorderLayout.SOUTH);<br>
+ * <br>
+ * this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);<br>
+ * }<br>
+ * <br>
+ * public static void main(String ... args) {<br>
+ * try {<br>
+ * UIManager.setLookAndFeel(new
+ * SubstanceLookAndFeel());<br>
+ * }<br>
+ * catch (Exception exc) {}<br>
+ * JFrame.setDefaultLookAndFeelDecorated(true);<br>
+ * Changer ch = new Changer();<br>
+ * ch.setPreferredSize(new Dimension(200, 200));<br>
+ * ch.setSize(ch.getPreferredSize());<br>
+ * ch.setLocationRelativeTo(null);<br>
+ * ch.setVisible(true);<br>
+ * }<br> }
+ * </code>
+ *
+ * @since version 2.1
+ */
+ public final static String WINDOW_MODIFIED = "windowModified";
+
+ /**
+ * <p>UIManager property name for specifying that whether any JRootPane with
+ * Substance window decorations should be attentive to the active or selected
+ * state of the respective JFrame or JInternalFrame. This property can only be
+ * specified in the {@link UIManager} for this release. The value should be
+ * either {@link Boolean#TRUE} or {@link Boolean#FALSE}. The initial value
+ * depends on the specific skin chosen and will be false if not specified.
+ * </p>
+ *
+ * <p>When active this property will cause the title pane and borders of
+ * JFrames and JInternal frames to toggle between {DecorationAreaType#PRIMARY_TITLE_PANE}
+ * / {DecorationAreaType#PRIMARY_TITLE_PANE_INACTIVE} or {DecorationAreaType#SECONDARY_TITLE_PANE}
+ * / {DecorationAreaType#SECONDARY_TITLE_PANE_INACTIVE} respectively when the
+ * respective containers active or selected property changes. If the Title pane of the
+ * window decoration is change to any other DecorationAreaType, then the type will not
+ * be toggled.</p>
+ *
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.WINDOW_AUTO_DEACTIVATE, <br>
+ * Boolean.TRUE);<br>
+ * </code>
+ *
+ * @since version 6.3
+ */
+ public final static String WINDOW_AUTO_DEACTIVATE = "windowAutoDeactivate";
+
+ /**
+ * <p>VM property name for specifying that whether any JRootPane with
+ * Substance window decorations should be drawn with rounded corners in it's
+ * decoration frame. This property is specified globally in via a system property.
+ * The value will be parsed by {Boolean.valueOf}, unless it is unset or an empty
+ * string, then it will default to true. A true value only enables rounded windows,
+ * they can be turned off via the UIManager or client properties. A false value
+ * disables <i>all</i> rounded corners and is used to remove artifacts from legacy
+ * video cards. This value is read when the Substance look and feel is initialized,
+ * and is not consulted later.
+ * </p>
+ *
+ * <p>When unset or set to true this property will cause the title pane and borders of
+ * JFrames and JInternal frames to be rounded.
+ *
+ * <p>
+ * <code>
+ * -Dsubstancelaf.windowRoundedCorners=False
+ * </code>
+ * </p>
+ *
+ * @since version 7.1
+ */
+ public final static String WINDOW_ROUNDED_CORNERS_PROPERTY = "substancelaf.windowRoundedCorners";
+
+ /**
+ * <p>Client property name for specifying that whether any JRootPane with
+ * Substance window decorations should be drawn with rounded corners in it's
+ * decoration frame. This property can be specified per-window or globally in
+ * the UIManager or via client properties. The value should be
+ * either {@link Boolean#TRUE} or {@link Boolean#FALSE}. This value is subject
+ * to being globally set to false if the system property is set to false. When unset
+ * it defauls to true.
+ * </p>
+ *
+ * <p>When unset or set to true this property will cause the title pane and borders of
+ * JFrames and JInternal frames to be rounded.
+ *
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.WINDOW_ROUNDED_CORNERS, <br>
+ * Boolean.FALSE);<br>
+ * // OR <br/>
+ * someFrameOrDialog.putClientProperty(SubstanceLookAndFeel.WINDOW_ROUNDED_CORNERS, <br>
+ * Boolean.FALSE);<br>
+ * // for specific JInternalFrames, JFrames, or JDialogs
+ *
+ * </code>
+ *
+ * @since version 7.1
+ */
+ public final static String WINDOW_ROUNDED_CORNERS = "windowRoundedCorners";
+
+ /**
+ * Client property name for adding close buttons on tabs. This property can
+ * be specified on a single tab component, on a {@link JTabbedPane} itself
+ * (will hold for all tab components that don't define this property) or on
+ * {@link UIManager}. The value should be either {@link Boolean#TRUE} or
+ * {@link Boolean#FALSE}. By default, the close buttons are not displayed.
+ *
+ * <p>
+ * Example of setting that all tabs in the application will have close
+ * buttons:
+ * </p>
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY, <br>
+ * Boolean.TRUE);
+ * </code>
+ *
+ * <p>
+ * A more complex example:
+ * </p>
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY, <br>
+ * Boolean.TRUE);<br>
+ * JTabbedPane jtpMain = new JTabbedPane();<br>
+ * JTabbedPane jtpSecondary = new JTabbedPane();<br>
+ * jtpSecondary.putClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY, <br>
+ * Boolean.FALSE);<br>
+ * JPanel panelSecondary = new JPanel();<br>
+ * jtpMain.addTab(jtpSecondary);<br>
+ * jtpMain.addTab(panelSecondary);<br>
+ * JPanel tab1 = new JPanel();<br>
+ * JPanel tab2 = new JPanel();<br>
+ * tab2.putClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY, <br>
+ * Boolean.TRUE);<br>
+ * jtpSecondary.addTab(tab1);<br>
+ * jtpSecondary.addTab(tab2);
+ * </code>
+ *
+ * <p>
+ * In the example above, the first first-level child (<b>jtpSecondary</b>)
+ * doesn't have the close button (since it overrides the global setting).
+ * The second first-level child tab (<b>panelSecondary</b>) has close button
+ * (from the global <b>UIManager</b> setting). The first second-level tab
+ * doesn't have the close button (setting inherited from the parent
+ * <b>jtpSecondary</b> tab that overrides the global setting). The second
+ * second-level tab has the close button (since its setting overrides the
+ * parent setting).
+ * </p>
+ *
+ * @since version 2.1
+ * @see #TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION
+ * @see #TABBED_PANE_CLOSE_CALLBACK
+ */
+ public final static String TABBED_PANE_CLOSE_BUTTONS_PROPERTY = "substancelaf.tabbedpanehasclosebuttons";
+
+ /**
+ * Client property name for specifying that only the close button of a
+ * marked-as-modified tab component should pulsate. This property can be
+ * specified on a single tab component, on a {@link JTabbedPane} itself
+ * (will hold for all tab components that don't define this property) or on
+ * {@link UIManager}. The value should be either {@link Boolean#TRUE} or
+ * {@link Boolean#FALSE}. By default, the animation on modified tabs is on
+ * the entire tab rectangle. Note that this setting is only relevant for
+ * tabs marked with {@link #WINDOW_MODIFIED} property.
+ *
+ * <p>
+ * Example of setting that all tabs in the application will have modified
+ * animation on close button:
+ * </p>
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION, <br>
+ * Boolean.TRUE);
+ * </code>
+ *
+ * <p>
+ * A more complex example:
+ * </p>
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION, <br>
+ * Boolean.TRUE);<br>
+ * JTabbedPane jtpMain = new JTabbedPane();<br>
+ * JTabbedPane jtpSecondary = new JTabbedPane();<br>
+ * jtpSecondary.putClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION, <br>
+ * Boolean.FALSE);<br>
+ * JPanel panelSecondary = new JPanel();<br>
+ * jtpMain.addTab(jtpSecondary);<br>
+ * jtpMain.addTab(panelSecondary);<br>
+ * JPanel tab1 = new JPanel();<br>
+ * JPanel tab2 = new JPanel();<br>
+ * tab2.putClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION, <br>
+ * Boolean.TRUE);<br>
+ * jtpSecondary.addTab(tab1);<br>
+ * jtpSecondary.addTab(tab2);
+ * </code>
+ *
+ * <p>
+ * In the example above, the first first-level child (<b>jtpSecondary</b>)
+ * has the animation on the entire tab (since it overrides the global
+ * setting). The second first-level child tab (<b>panelSecondary</b>) has
+ * animation on the close button (from the global <b>UIManager</b> setting).
+ * The first second-level tab has the animation on the entire tab (setting
+ * inherited from the parent <b>jtpSecondary</b> tab that overrides the
+ * global setting). The second second-level tab has animation on the close
+ * button (since its setting overrides the parent setting).
+ * </p>
+ *
+ * @since version 2.2
+ * @see #TABBED_PANE_CLOSE_BUTTONS_PROPERTY
+ * @see #TABBED_PANE_CLOSE_CALLBACK
+ */
+ public final static String TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION = "substancelaf.tabbedpaneclosebuttonsmodifiedanimation";
+
+ /**
+ * Client property name for specifying the callback for deciding on the tab
+ * close type. This property can be specified on a single tab component, on
+ * a {@link JTabbedPane} itself (will hold for all tab components that don't
+ * define this property) or on {@link UIManager}. The value should be an
+ * instance of {@link TabCloseCallback}. Note that this setting is only
+ * relevant for tabs marked with {@link #TABBED_PANE_CLOSE_BUTTONS_PROPERTY}
+ * property.
+ *
+ * <p>
+ * Example of custom tab close callback set on a tabbed pane:
+ * </p>
+ * <code>
+ * TabCloseCallback closeCallback = new TabCloseCallback() {<br>
+ * public TabCloseKind onAreaClick(JTabbedPane tabbedPane,<br>
+ * int tabIndex, MouseEvent mouseEvent) {<br>
+ * if (mouseEvent.getButton() != MouseEvent.BUTTON3)<br>
+ * return TabCloseKind.NONE;<br>
+ * if (mouseEvent.isShiftDown()) {<br>
+ * return TabCloseKind.ALL;<br>
+ * }<br>
+ * return TabCloseKind.THIS;<br>
+ * }<br>
+ * <br>
+ * public TabCloseKind onCloseButtonClick(JTabbedPane tabbedPane,<br>
+ * int tabIndex, MouseEvent mouseEvent) {<br>
+ * if (mouseEvent.isAltDown()) {<br>
+ * return TabCloseKind.ALL_BUT_THIS;<br>
+ * }<br>
+ * if (mouseEvent.isShiftDown()) {<br>
+ * return TabCloseKind.ALL;<br>
+ * }<br>
+ * return TabCloseKind.THIS;<br>
+ * }<br>
+ * <br>
+ * public String getAreaTooltip(JTabbedPane tabbedPane, int tabIndex) {<br>
+ * return null;<br>
+ * }<br>
+ * <br>
+ * public String getCloseButtonTooltip(JTabbedPane tabbedPane,<br>
+ * int tabIndex) {<br>
+ * StringBuffer result = new StringBuffer();<br>
+ * result.append("<html><body>");<br>
+ * result.append("Mouse click closes <b>"<br>
+ * + tabbedPane.getTitleAt(tabIndex) + "</b> tab");<br>
+ * result.append("<br><b>Alt</b>-Mouse click closes all tabs but <b>"<br>
+ * + tabbedPane.getTitleAt(tabIndex) + "</b> tab");<br>
+ * result.append("<br><b>Shift</b>-Mouse click closes all tabs");<br>
+ * result.append("</body></html>");<br>
+ * return result.toString();<br>
+ * }<br>
+ * };<br>
+ *
+ * JTabbedPane jtp = new JTabbedPane();<br>
+ * jtp.putClientProperty(<br>
+ * SubstanceLookAndFeel.TABBED_PANE_CLOSE_CALLBACK, <br>
+ * closeCallback);
+ * </code>
+ *
+ * @since version 2.3
+ */
+ public final static String TABBED_PANE_CLOSE_CALLBACK = "substancelaf.tabbedpanecloseCallback";
+
+ /**
+ * Client property name for specifying the content pane border kind. This
+ * property can be specified either on a single {@link JTabbedPane} or on
+ * {@link UIManager}. The value should be one of
+ * {@link SubstanceConstants.TabContentPaneBorderKind} enum. By default, the
+ * border kind is
+ * {@link SubstanceConstants.TabContentPaneBorderKind#DOUBLE_FULL}.
+ *
+ * <p>
+ * Example of setting that all tabbed panes in the application have single
+ * full border (default setting prior to version 4.1):
+ * </p>
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.TABBED_PANE_CONTENT_BORDER_KIND, <br>
+ * TabContentPaneBorderKind.SINGLE_FULL);
+ * </code>
+ *
+ * <p>
+ * Example of specifying that the specific tabbed pane has single full
+ * border (default setting prior to version 4.1):
+ * </p>
+ * <code>
+ * JTabbedPane jtpMain = new JTabbedPane();<br>
+ * jtpMain.putClientProperty(SubstanceLookAndFeel.TABBED_PANE_CONTENT_BORDER_KIND, <br>
+ * TabContentPaneBorderKind.SINGLE_FULL);
+ * </code>
+ *
+ * @since version 4.1
+ */
+ public final static String TABBED_PANE_CONTENT_BORDER_KIND = "substancelaf.tabbedPaneContentBorderKind";
+
+
+ /**
+ * Client property name for specifying wheter tabs shown on the left or right
+ * will be rotated vertically. This property can be specified either on a
+ * single {@link JTabbedPane} or on * {@link UIManager}. The value should a
+ * Boolean. By default, the rotation is enabled.
+ *
+ * <p>
+ * Example of setting that all tabbed panes should not rotate the tabs:
+ * </p>
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.TABBED_PANE_ROTATE_SIDE_TABS, <br>
+ * Boolean.FALSE);
+ * </code>
+ *
+ * <p>
+ * Example of specifying that the specific tabbed pane tabbed panes should not rotate the tabs:
+ * </p>
+ * <code>
+ * JTabbedPane jtpMain = new JTabbedPane();<br>
+ * jtpMain.putClientProperty(SubstanceLookAndFeel.TABBED_PANE_ROTATE_SIDE_TABS, <br>
+ * Boolean.FALSE);
+ * </code>
+ *
+ * @since version 7.2
+ */
+ public final static String TABBED_PANE_ROTATE_SIDE_TABS = "substancelaf.rotate";
+
+ /**
+ * Client property name for specifying the leading vertical line on a table.
+ * Normally the presence of the leading vertical line on a table is driven
+ * by the presence or absence of a row header.
+ *
+ * <p>
+ * <ul>
+ * <li>The default <code>null</code> - The line is driven by the presence of row headers
+ * <li><code>true</code> - The line is always drawn.
+ * <li><code>false</code> - The line is never drawn.
+ * </ul>
+ * </p>
+
+ * <code>
+ * JTable jtable = new JTable();<br>
+ * jtable.putClientProperty(SubstanceLookAndFeel.TABLE_LEADING_VERTICAL_LINE, <br>
+ * false);
+ * </code>
+ *
+ * @since version 7.2
+ */
+ public final static String TABLE_LEADING_VERTICAL_LINE = "substancelaf.tableLeadingVerticalLine";
+
+ /**
+ * Client property name for specifying the trailing verticle line on a table.
+ * Normally the presence of the leading vertical line on a table is driven
+ * by the width of the table and the width of the children
+ *
+ * <p>
+ * <ul>
+ * <li>The default <code>null</code> - The line is driven by whether it fits within the view of the scroll pane (true) or not (false)
+ * <li><code>true</code> - The line is always drawn.
+ * <li><code>false</code> - The line is never drawn.
+ * </ul>
+ * </p>
+
+ * <code>
+ * JTable jtable = new JTable();<br>
+ * jtable.putClientProperty(SubstanceLookAndFeel.TABLE_TRAILING_VERTICAL_LINE, <br>
+ * false);
+ * </code>
+ *
+ * @since version 7.2
+ */
+ public final static String TABLE_TRAILING_VERTICAL_LINE = "substancelaf.tableTrailingVerticalLine";
+
+ /**
+ * Client property name for specifying combo popup flyout orientation. This
+ * property can be set on either a specific {@link JComboBox} or globally on
+ * {@link UIManager}. The value should be one of the {@link Integer}s below:
+ *
+ * <p>
+ * <ul>
+ * <li>The default {@link SwingConstants#SOUTH} - the popup is displayed
+ * directly below the combo aligned to the left.
+ * <li>{@link SwingConstants#NORTH} - the popup is displayed directly above
+ * the combo aligned to the left.
+ * <li>{@link SwingConstants#EAST} - the popup is displayed to the left of
+ * the combo aligned to the top.
+ * <li>{@link SwingConstants#WEST} - the popup is displayed to the right of
+ * the combo aligned to the top.
+ * <li>{@link SwingConstants#CENTER} - the popup is displayed centered
+ * vertically over the combo aligned to the left.
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * Note that the combo arrow changes in accordance with the combo popup
+ * flyout orientation. Example of setting a combobox with a custom flyout
+ * orientation:
+ * </p>
+ * <code>
+ * JComboBox cb = new JComboBox(<br>
+ * new Object[] { "Ester", "Jordi", "Jordina", "Jorge", "Sergi" });<br>
+ * cb.putClientProperty(SubstanceLookAndFeel.COMBO_BOX_POPUP_FLYOUT_ORIENTATION, <br>
+ * SwingConstants.CENTER);
+ * </code>
+ *
+ * @since version 2.3
+ * @see #COMBO_POPUP_PROTOTYPE
+ */
+ public final static String COMBO_BOX_POPUP_FLYOUT_ORIENTATION = "substancelaf.comboboxpopupFlyoutOrientation";
+
+ /**
+ * Client property name for specifying scroll pane button policy. This
+ * property can be set on either a specific {@link JScrollPane} or globally
+ * on {@link UIManager}. The value should be one of the
+ * {@link SubstanceConstants.ScrollPaneButtonPolicyKind} enum. Example of
+ * setting a scroll pane with a custom button policy:
+ *
+ * <p>
+ * <code>
+ * JScrollPane jsp = new JScrollPane(new JPanel());<br>
+ * jsp.putClientProperty(SubstanceLookAndFeel.SCROLL_PANE_BUTTONS_POLICY,<br>
+ * ScrollPaneButtonPolicyKind.MULTIPLE);
+ * </code>
+ *
+ * @since version 3.1
+ */
+ public final static String SCROLL_PANE_BUTTONS_POLICY = "substancelaf.scrollPaneButtonsPolicy";
+
+ /**
+ * Property name for specifying that extra UI elements (such as menu items
+ * in system menu or lock borders) should be shown. This property can be set
+ * as a global setting on {@link UIManager} or as a client property on a
+ * specific component. The value should be either {@link Boolean#TRUE} or
+ * {@link Boolean#FALSE}.
+ *
+ * <p>
+ * Example of setting this property on {@link UIManager}:
+ * </p>
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.SHOW_EXTRA_WIDGETS, Boolean.TRUE);
+ * SwingUtilities.updateComponentTree(myFrame);
+ * </code>
+ *
+ * @since version 5.0
+ */
+ public final static String SHOW_EXTRA_WIDGETS = "substancelaf.addWidgets";
+
+ /**
+ * Property name for specifying menu gutter fill kind. Menu gutter is the
+ * part of the menu where checkmarks and icons are painted. The value should
+ * be one of {@link MenuGutterFillKind} enum. This property can be set
+ * globally on the {@link UIManager}. The default value is
+ * {@link MenuGutterFillKind#HARD}.
+ *
+ * <p>
+ * Example of setting soft fill kind:
+ * </p>
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.MENU_GUTTER_FILL_KIND, MenuGutterFillKind.SOFT);
+ * </code>
+ *
+ * @since version 3.2
+ */
+ public final static String MENU_GUTTER_FILL_KIND = "substancelaf.menuGutterFillKind";
+
+ /**
+ * Client property name for specifying the kind of focus indication on
+ * buttons, check boxes and radio buttons. The value should be one of
+ * {@link SubstanceConstants.FocusKind} enum. This property can be set
+ * either on the specific component or as global property on
+ * {@link UIManager}.
+ *
+ * <p>
+ * In order to compute the kind of focus indication for some component, the
+ * component's hierarchy is traversed bottom up. The first component that
+ * has this property set, defines the focus indication kind. If neither
+ * component nor its ancestors define this property, the global setting on
+ * {@link UIManager} is checked. If there is no global setting, the default
+ * {@link SubstanceConstants.FocusKind#ALL_INNER} is used. Here is an
+ * example to illustrate the above:
+ * </p>
+ *
+ * <code>
+ * JPanel topPanel = new JPanel();<br>
+ * topPanel.putClientProperty(SubstanceLookAndFeel.FOCUS_KIND, FocusKind.UNDERLINE);<br>
+ * JPanel panel1 = new JPanel();<br>
+ * JButton b1 = new JButton("button1");<br>
+ * b1.putClientProperty(SubstanceLookAndFeel.FOCUS_KIND, FocusKind.TEXT);<br>
+ * JButton b2 = new JButton("button2");<br>
+ * JButton b3 = new JButton("button3");<br>
+ * b3.putClientProperty(SubstanceLookAndFeel.FOCUS_KIND, FocusKind.ALL_INNER);<br>
+ * panel1.add(b1);<br>
+ * panel1.add(b2);<br>
+ * topPanel.add(panel1);<br>
+ * topPanel.add(b3);<br>
+ * </code>
+ *
+ * <p>
+ * In the code above:
+ * </p>
+ * <ul>
+ * <li>Button <b>b1</b> will have {@link SubstanceConstants.FocusKind#NONE}
+ * focus kind which is set directly on the button.
+ * <li>Button <b>b2</b> will have
+ * {@link SubstanceConstants.FocusKind#UNDERLINE} focus kind which is
+ * inherited from its <b>topPanel</b> parent.
+ * <li>Button <b>b3</b> will have
+ * {@link SubstanceConstants.FocusKind#ALL_INNER} focus kind which is set
+ * directly on the button.
+ * </ul>
+ *
+ * @since 2.2
+ * @see SubstanceConstants.FocusKind
+ */
+ public final static String FOCUS_KIND = "substancelaf.focusKind";
+
+ /**
+ * Property name for specifying the combobox popup prototype display value
+ * which is used to compute the width of the popup at runtime. The property
+ * value should be one of:
+ *
+ * <p>
+ * <ul>
+ * <li>{@link ComboPopupPrototypeCallback} - will provide
+ * application-specific logic at runtime.
+ * <li>{@link Object} - will point to the prototype entry itself.
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * This property can be set either on a specific {@link JComboBox} or
+ * globally on {@link UIManager}.
+ * </p>
+ *
+ * <p>
+ * Here is an example of combo popup prototype set to a model element:
+ * </p>
+ * <code>
+ * JComboBox comboProto1 = new JComboBox(new Object[] { "aa", "aaaaa",<br>
+ * "aaaaaaaaaa", "this one is the one", "aaaaaaaaaaaaaaaaaaaaa" });<br>
+ * comboProto1.setPrototypeDisplayValue("aaaaa");<br>
+ * comboProto1.putClientProperty(SubstanceLookAndFeel.COMBO_POPUP_PROTOTYPE,<br>
+ * "this one is the one");
+ * </code>
+ *
+ * <p>
+ * Here is an example of combo popup prototype set to a dynamic callback.
+ * This callback always returns the last model element:
+ * </p>
+ * <code>
+ * JComboBox comboProto3 = new JComboBox(new Object[] { "aa", "aaaaa",<br>
+ * "this is not", "this one is not it",<br>
+ * "this one is it that is for the popup" });<br>
+ * comboProto3.setPrototypeDisplayValue("aaaaa");<br>
+ * comboProto3.putClientProperty(SubstanceLookAndFeel.COMBO_POPUP_PROTOTYPE,<br>
+ * new ComboPopupPrototypeCallback() {<br>
+ * public Object getPopupPrototypeDisplayValue(JComboBox jc) {<br>
+ * return jc.getModel().getElementAt(<br>
+ * jc.getModel().getSize() - 1);<br>
+ * }<br>
+ * });
+ * </code>
+ *
+ * @since version 3.0
+ * @see #COMBO_BOX_POPUP_FLYOUT_ORIENTATION
+ */
+ public final static String COMBO_POPUP_PROTOTYPE = "substancelaf.comboPopupPrototype";
+
+ /**
+ * VM property name for specifying the trace file. The trace file will
+ * contain output of the memory analyser which can be used to pinpoint the
+ * memory leaks. The property value is used as a filename for tracing the
+ * memory allocations. Example for specifying the trace file name:
+ *
+ * <p>
+ * <code>
+ * -Dsubstancelaf.traceFile=C:/temp/myApp.substance.log
+ * </code>
+ * </p>
+ *
+ * @since version 2.0
+ */
+ public final static String TRACE_FILE = "substancelaf.traceFile";
+
+ /**
+ * Client property name for specifying the number of echo characters for
+ * each password character. The value should be an instance of
+ * {@link Integer}, otherwise will be ignored. This property can be set
+ * either on a specific {@link JPasswordField} or globally on
+ * {@link UIManager}.
+ *
+ * <p>
+ * Example of having all password fields echo 3 characters per each typed
+ * user character:
+ * </p>
+ * <code>
+ * UIManager.put(SubstanceLookAndFeel.PASSWORD_ECHO_PER_CHAR, <br>
+ * new Integer(3));
+ * </code>
+ *
+ * <p>
+ * Example of having a specific password field echo 2 characters per each
+ * typed user character:
+ * </p>
+ * <code>
+ * JPasswordField jpf = new JPasswordField();<br>
+ * jpf.putClientProperty(SubstanceLookAndFeel.PASSWORD_ECHO_PER_CHAR, <br>
+ * new Integer(2));
+ * </code>
+ *
+ * @since version 2.2
+ */
+ public final static String PASSWORD_ECHO_PER_CHAR = "substancelaf.passwordEchoPerChar";
+
+ /**
+ * <p>
+ * Client property name for specifying that icons on controls such as
+ * buttons, toggle buttons, labels, tabs and menu items should match the
+ * color of the current color scheme when they are in default state. The
+ * control is in default state when it's not pressed, not selected, not
+ * armed and not rolled over. The value should be an instance of
+ * {@link Boolean}. By default, all controls show regular (full-color
+ * original) icons. The value can be set globally on {@link UIManager}.
+ * </p>
+ *
+ * @since version 3.3
+ */
+ public final static String USE_THEMED_DEFAULT_ICONS = "substancelaf.useThemedDefaultIcons";
+
+ /**
+ * <p>
+ * Client property name for specifying the colorization amount applied to
+ * the background and foreground of the current color scheme and the
+ * specific control. By default, when the application does not use any
+ * custom colors, all the controls are painted with the colors of the
+ * current color scheme / skin. The colors coming from the look-and-feel
+ * implement the marker {@link UIResource} interface which allows the UI
+ * delegates to differentiate between application-specific colors which are
+ * not changed, and the LAF-provide colors that are changed on LAF switch.
+ * </p>
+ *
+ * <p>
+ * This new client property installs the "smart colorization" mode which
+ * uses the colors of the current color scheme and the custom background /
+ * foreground colors (when installed by application) to colorize the
+ * relevant portions of the control. For example, on checkbox the custom
+ * background color will be used to colorize the check box itself, while the
+ * custom foreground color will be applied to the check box text and the
+ * check mark.
+ * </p>
+ *
+ * <p>
+ * The value of this property specifies the actual colorization amount.
+ * Value of 0.0 results in Substance completely <strong>ignoring</strong>
+ * the custom application background and foreground colors set on the
+ * components - no colorization. Values closer to 1.0 result in almost full
+ * usage of the custom application background and foreground colors set on
+ * the components. Note that in order to maintain the gradients (fill,
+ * border, etc), even value of 1.0 does not result in full custom color
+ * being applied to the relevant visuals of the control.
+ * </p>
+ *
+ * <p>
+ * This property can be specified globally on {@link UIManager}, applying on
+ * all controls, or on the specific component / container. In the later
+ * case, the value will be applied to the component / container itself and
+ * all its children that do not specify a custom value for this property.
+ * </p>
+ *
+ * <p>
+ * The default colorization amount (when this property is not set at all) is
+ * 0.5. This means that applications that install custom background /
+ * foreground colors on their UI controls will see them colorized with 50%
+ * "strength", even without setting this property.
+ * </p>
+ *
+ * <p>
+ * The value should be an instance of {@link Double} in 0.0-1.0 range.
+ * </p>
+ *
+ * <p>
+ * Example of marking a button to have a custom background color and
+ * colorizing it with 40%:
+ * </p>
+ * <code>
+ * JButton jb = new JButton("sample", myIcon);<br>
+ * jb.setBackground(Color.red);<br>
+ * jb.putClientProperty(SubstanceLookAndFeel.COLORIZATION_FACTOR, <br>
+ * new Double(0.4));
+ * </code>
+ *
+ * <p>
+ * Note that components in decoration areas registered on the current skin
+ * will ignore the colorization on custom background color. The background
+ * of such components is always painted by the skin's decoration painter to
+ * ensure consistent background painting of the relevant decoration area.
+ * </p>
+ *
+ * @since version 4.2
+ * @see Component#setBackground(Color)
+ * @see Component#setForeground(Color)
+ */
+ public final static String COLORIZATION_FACTOR = "substancelaf.colorizationFactor";
+
+ /**
+ * Internal client property name for storing application-specific font
+ * policy.
+ *
+ * @since version 3.3
+ * @see #setFontPolicy(FontPolicy)
+ * @see #getFontPolicy()
+ */
+ protected final static String SUBSTANCE_FONT_POLICY_KEY = "substancelaf.fontPolicyKey";
+
+ /**
+ * Internal client property name for storing application-specific input map
+ * set.
+ *
+ * @since version 6.1
+ * @see #setInputMapSet(InputMapSet)
+ * @see #getInputMapSet()
+ */
+ protected final static String SUBSTANCE_INPUT_MAP_SET_KEY = "substancelaf.inputMapSetKey";
+
+ /**
+ * Property name for specifying outline shaper. This property is used a
+ * client property that can be set on a specific control.
+ *
+ * <p>
+ * The value must be a {@link SubstanceButtonShaper} object.
+ * </p>
+ *
+ * <p>
+ * Example of using a {@link SubstanceButtonShaper} object as client
+ * property value:
+ * </p>
+ * <code>
+ * JButton b = new JButton("text");<br>
+ * b.putClientProperty(SubstanceLookAndFeel.BUTTON_SHAPER_PROPERTY, <br>
+ * new ClassicButtonShaper());
+ * </code>
+ *
+ * @since version 2.1
+ */
+ public static final String BUTTON_SHAPER_PROPERTY = "substancelaf.buttonShaper";
+
+ /**
+ * Property name for specifying a skin to be used on the specific root pane.
+ * This property can only be installed on a {@link JRootPane} and will
+ * affect all the controls in that root pane. The value must be an instance
+ * of {@link SubstanceSkin}. After setting this property, call
+ * {@link SwingUtilities#updateComponentTreeUI(Component)} on the matching
+ * window.
+ *
+ * @since version 5.0
+ * @see #getCurrentSkin(Component)
+ */
+ public static final String SKIN_PROPERTY = "substancelaf.skin";
+
+ /**
+ * Resource bundle for <b>Substance</b> labels.
+ */
+ private static ResourceBundle LABEL_BUNDLE = null;
+
+ /**
+ * Class loader for {@link #LABEL_BUNDLE}.
+ */
+ private static ClassLoader labelBundleClassLoader;
+
+ /**
+ * The skin of this look-and-feel instance.
+ */
+ protected SubstanceSkin skin;
+
+ /**
+ * The name of this look-and-feel instance.
+ */
+ protected String name;
+
+ /**
+ * Creates a new skin-based Substance look-and-feel. This is the only way to
+ * create an instance of {@link SubstanceLookAndFeel} class.
+ *
+ * @param skin
+ * Skin.
+ */
+ protected SubstanceLookAndFeel(SubstanceSkin skin) {
+ this.skin = skin;
+ this.name = "Substance " + skin.getDisplayName();
+
+ initPluginsIfNecessary();
+ }
+
+ /**
+ * Initializes the plugins if necessary.
+ */
+ protected static void initPluginsIfNecessary() {
+ if (SubstanceLookAndFeel.skinPlugins != null)
+ return;
+ SubstanceLookAndFeel.skinPlugins = new PluginManager(
+ SubstanceLookAndFeel.PLUGIN_XML, LafPlugin.TAG_MAIN,
+ SubstanceSkinPlugin.TAG_SKIN_PLUGIN_CLASS);
+ SubstanceLookAndFeel.componentPlugins = new ComponentPluginManager(
+ SubstanceLookAndFeel.PLUGIN_XML);
+ }
+
+ /**
+ * Retrieves the current label bundle.
+ *
+ * @return The current label bundle.
+ * @see #resetLabelBundle()
+ */
+ public static synchronized ResourceBundle getLabelBundle() {
+ if (SubstanceLookAndFeel.LABEL_BUNDLE == null) {
+ // fix for RFE 157 (allowing custom class loader for
+ // resource bundles which can remove server calls
+ // in applets)
+ if (SubstanceLookAndFeel.labelBundleClassLoader == null) {
+ SubstanceLookAndFeel.LABEL_BUNDLE = ResourceBundle
+ .getBundle(
+ "org.pushingpixels.substance.internal.resources.Labels",
+ Locale.getDefault());
+ } else {
+ SubstanceLookAndFeel.LABEL_BUNDLE = ResourceBundle
+ .getBundle(
+ "org.pushingpixels.substance.internal.resources.Labels",
+ Locale.getDefault(),
+ SubstanceLookAndFeel.labelBundleClassLoader);
+ }
+ for (LocaleChangeListener lcl : SubstanceLookAndFeel.localeChangeListeners)
+ lcl.localeChanged();
+ }
+ return SubstanceLookAndFeel.LABEL_BUNDLE;
+ }
+
+ /**
+ * Retrieves the label bundle for the specified locale.
+ *
+ * @param locale
+ * Locale.
+ * @return The label bundle for the specified locale.
+ */
+ public static synchronized ResourceBundle getLabelBundle(Locale locale) {
+ // fix for RFE 157 (allowing custom class loader for
+ // resource bundles which can remove server calls
+ // in applets)
+ if (SubstanceLookAndFeel.labelBundleClassLoader == null) {
+ return ResourceBundle.getBundle(
+ "org.pushingpixels.substance.internal.resources.Labels",
+ locale);
+ } else {
+ return ResourceBundle.getBundle(
+ "org.pushingpixels.substance.internal.resources.Labels",
+ locale, SubstanceLookAndFeel.labelBundleClassLoader);
+ }
+ }
+
+ /**
+ * Resets the current label bundle. Useful when the application changes
+ * Locale at runtime.
+ *
+ * @see #getLabelBundle()
+ */
+ public static synchronized void resetLabelBundle() {
+ SubstanceLookAndFeel.LABEL_BUNDLE = null;
+ LafWidgetRepository.resetLabelBundle();
+ }
+
+ /**
+ * Returns the current global skin. If the current look-and-feel is not
+ * Substance, this method returns <code>null</code>. This method is for
+ * internal use only. Applications should use the
+ * {@link #getCurrentSkin(Component)}.
+ *
+ * @return Current global skin.
+ * @see #getCurrentSkin(Component)
+ */
+ public static SubstanceSkin getCurrentSkin() {
+ LookAndFeel current = UIManager.getLookAndFeel();
+ if (current instanceof SubstanceLookAndFeel) {
+ return currentSkin;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the current skin for the specified component. If the current
+ * look-and-feel is not Substance, this method returns <code>null</code>.
+ *
+ * @param c
+ * Component. May be <code>null</code> - in this case the global
+ * current Substance skin will be returned.
+ * @return Current skin for the specified component.
+ * @see #SKIN_PROPERTY
+ * @see #getCurrentSkin()
+ */
+ public static SubstanceSkin getCurrentSkin(Component c) {
+ return SubstanceCoreUtilities.getSkin(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.LookAndFeel#getDescription()
+ */
+ @Override
+ public String getDescription() {
+ return "Substance Look and Feel by Kirill Grouchnikov";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.LookAndFeel#getID()
+ */
+ @Override
+ public String getID() {
+ return this.name;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.LookAndFeel#getName()
+ */
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.LookAndFeel#isNativeLookAndFeel()
+ */
+ @Override
+ public boolean isNativeLookAndFeel() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.LookAndFeel#isSupportedLookAndFeel()
+ */
+ @Override
+ public boolean isSupportedLookAndFeel() {
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicLookAndFeel#initClassDefaults(javax.swing
+ * .UIDefaults)
+ */
+ @Override
+ protected void initClassDefaults(UIDefaults table) {
+ super.initClassDefaults(table);
+
+ String UI_CLASSNAME_PREFIX = "org.pushingpixels.substance.internal.ui.Substance";
+ Object[] uiDefaults = {
+
+ "ButtonUI", UI_CLASSNAME_PREFIX + "ButtonUI",
+
+ "CheckBoxUI", UI_CLASSNAME_PREFIX + "CheckBoxUI",
+
+ "ComboBoxUI", UI_CLASSNAME_PREFIX + "ComboBoxUI",
+
+ "CheckBoxMenuItemUI", UI_CLASSNAME_PREFIX + "CheckBoxMenuItemUI",
+
+ "DesktopIconUI", UI_CLASSNAME_PREFIX + "DesktopIconUI",
+
+ "DesktopPaneUI", UI_CLASSNAME_PREFIX + "DesktopPaneUI",
+
+ "EditorPaneUI",
+ UI_CLASSNAME_PREFIX + "EditorPaneUI",
+
+ "FileChooserUI",
+ UI_CLASSNAME_PREFIX + "FileChooserUI",
+
+ // "FileChooserUI", "javax.swing.plaf.metal.MetalFileChooserUI",
+
+ "FormattedTextFieldUI",
+ UI_CLASSNAME_PREFIX + "FormattedTextFieldUI",
+
+ "InternalFrameUI", UI_CLASSNAME_PREFIX + "InternalFrameUI",
+
+ "LabelUI", UI_CLASSNAME_PREFIX + "LabelUI",
+
+ "ListUI", UI_CLASSNAME_PREFIX + "ListUI",
+
+ "MenuUI", UI_CLASSNAME_PREFIX + "MenuUI",
+
+ "MenuBarUI", UI_CLASSNAME_PREFIX + "MenuBarUI",
+
+ "MenuItemUI", UI_CLASSNAME_PREFIX + "MenuItemUI",
+
+ "OptionPaneUI", UI_CLASSNAME_PREFIX + "OptionPaneUI",
+
+ "PanelUI", UI_CLASSNAME_PREFIX + "PanelUI",
+
+ "PasswordFieldUI", UI_CLASSNAME_PREFIX + "PasswordFieldUI",
+
+ "PopupMenuUI", UI_CLASSNAME_PREFIX + "PopupMenuUI",
+
+ "PopupMenuSeparatorUI",
+ UI_CLASSNAME_PREFIX + "PopupMenuSeparatorUI",
+
+ "ProgressBarUI", UI_CLASSNAME_PREFIX + "ProgressBarUI",
+
+ "RadioButtonUI", UI_CLASSNAME_PREFIX + "RadioButtonUI",
+
+ "RadioButtonMenuItemUI",
+ UI_CLASSNAME_PREFIX + "RadioButtonMenuItemUI",
+
+ "RootPaneUI", UI_CLASSNAME_PREFIX + "RootPaneUI",
+
+ "ScrollBarUI", UI_CLASSNAME_PREFIX + "ScrollBarUI",
+
+ "ScrollPaneUI", UI_CLASSNAME_PREFIX + "ScrollPaneUI",
+
+ "SeparatorUI", UI_CLASSNAME_PREFIX + "SeparatorUI",
+
+ "SliderUI", UI_CLASSNAME_PREFIX + "SliderUI",
+
+ "SpinnerUI", UI_CLASSNAME_PREFIX + "SpinnerUI",
+
+ "SplitPaneUI", UI_CLASSNAME_PREFIX + "SplitPaneUI",
+
+ "TabbedPaneUI", UI_CLASSNAME_PREFIX + "TabbedPaneUI",
+
+ "TableUI", UI_CLASSNAME_PREFIX + "TableUI",
+
+ "TableHeaderUI", UI_CLASSNAME_PREFIX + "TableHeaderUI",
+
+ "TextAreaUI", UI_CLASSNAME_PREFIX + "TextAreaUI",
+
+ "TextFieldUI", UI_CLASSNAME_PREFIX + "TextFieldUI",
+
+ "TextPaneUI", UI_CLASSNAME_PREFIX + "TextPaneUI",
+
+ "ToggleButtonUI", UI_CLASSNAME_PREFIX + "ToggleButtonUI",
+
+ "ToolBarUI", UI_CLASSNAME_PREFIX + "ToolBarUI",
+
+ "ToolBarSeparatorUI",
+ UI_CLASSNAME_PREFIX + "ToolBarSeparatorUI",
+
+ "ToolTipUI", UI_CLASSNAME_PREFIX + "ToolTipUI",
+
+ "TreeUI", UI_CLASSNAME_PREFIX + "TreeUI",
+
+ "ViewportUI", UI_CLASSNAME_PREFIX + "ViewportUI",
+
+ };
+ table.putDefaults(uiDefaults);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicLookAndFeel#initComponentDefaults(javax.swing
+ * .UIDefaults)
+ */
+ @Override
+ protected void initComponentDefaults(UIDefaults table) {
+ super.initComponentDefaults(table);
+
+ initFontDefaults(table);
+ this.skin.addCustomEntriesToTable(table);
+ }
+
+ /**
+ * Sets the {@link FontPolicy} to be used with Substance family. If the
+ * specified policy is <code>null</code>, the default will be reset. This
+ * method does not require Substance to be the current look-and-feel, and
+ * will cause Substance to be set as the current application look-and-feel.
+ *
+ * @param fontPolicy
+ * The {@link FontPolicy} to be used with Substance family, or
+ * <code>null</code> to reset to the default
+ *
+ * @see #getFontPolicy()
+ * @see SubstanceLookAndFeel#SUBSTANCE_FONT_POLICY_KEY
+ */
+ public static void setFontPolicy(FontPolicy fontPolicy) {
+ UIManager.put(SUBSTANCE_FONT_POLICY_KEY, fontPolicy);
+ SubstanceSizeUtils.setControlFontSize(-1);
+ SubstanceSizeUtils.resetPointsToPixelsRatio(fontPolicy);
+ SubstanceLookAndFeel.setSkin(SubstanceLookAndFeel.getCurrentSkin());
+ }
+
+ /**
+ * Looks up and retrieves the {@link FontPolicy} used by the Substance
+ * family. If a {@link FontPolicy} has been set, it'll be returned.
+ * Otherwise, this method checks if a {@link FontPolicy} or {@link FontSet}
+ * is defined in the system properties or UIDefaults. If so, it is returned.
+ * If no {@link FontPolicy} has been set for this look, in the system
+ * properties or {@link UIDefaults}, the default Substance font policy will
+ * be returned.
+ *
+ * @return the {@link FontPolicy} set for this Look&feel - if any, the
+ * {@link FontPolicy} specified in the system properties or
+ * {@link UIDefaults} - if any, or the default Substance font
+ * policy.
+ *
+ * @see #setFontPolicy(org.pushingpixels.substance.api.fonts.FontPolicy)
+ * @see FontPolicies
+ * @see FontPolicies#customSettingsPolicy(FontPolicy)
+ */
+ public static FontPolicy getFontPolicy() {
+ FontPolicy policy = (FontPolicy) UIManager
+ .get(SUBSTANCE_FONT_POLICY_KEY);
+ if (policy != null)
+ return policy;
+
+ // return default policy
+ return SubstanceFontUtilities.getDefaultFontPolicy();
+ }
+
+ /**
+ * Sets the {@link InputMapSet} to be used with Substance family. If the
+ * specified set is <code>null</code>, the default will be reset. This
+ * method does not require Substance to be the current look-and-feel, and
+ * will cause Substance to be set as the current application look-and-feel.
+ *
+ * @param inputMapSet
+ * The {@link InputMapSet} to be used with Substance family, or
+ * <code>null</code> to reset to the default
+ *
+ * @see #getInputMapSet()
+ * @see SubstanceLookAndFeel#SUBSTANCE_INPUT_MAP_SET_KEY
+ */
+ public static void setInputMapSet(InputMapSet inputMapSet) {
+ UIManager.put(SUBSTANCE_INPUT_MAP_SET_KEY, inputMapSet);
+ SubstanceLookAndFeel.setSkin(SubstanceLookAndFeel.getCurrentSkin());
+ }
+
+ /**
+ * Looks up and retrieves the {@link InputMapSet} used by the Substance
+ * family. If a {@link InputMapSet} has been set, it'll be returned. If no
+ * {@link InputMapSet} has been set for this look, the default Substance
+ * input map set will be returned.
+ *
+ * @return the {@link InputMapSet} set for this Look&feel - if any, or
+ * the default Substance input map set.
+ *
+ * @see #setInputMapSet(InputMapSet)
+ */
+ public static InputMapSet getInputMapSet() {
+ InputMapSet inputMapSet = (InputMapSet) UIManager
+ .get(SUBSTANCE_INPUT_MAP_SET_KEY);
+ if (inputMapSet != null)
+ return inputMapSet;
+
+ // return system input map set
+ return SubstanceInputMapUtilities.getSystemInputMapSet();
+ }
+
+ /**
+ * Looks up the correct control font and sets it for all controls.
+ *
+ * @param table
+ * The UI defaults table.
+ */
+ private void initFontDefaults(UIDefaults table) {
+ FontSet substanceFontSet = getFontPolicy()
+ .getFontSet("Substance", null);
+ initFontDefaults(table, substanceFontSet);
+ }
+
+ /**
+ * Sets Fonts in the given FontSet as defaults for all known component types
+ * in the given UIDefaults table.
+ *
+ * @param table
+ * the UIDefaults table used to set fonts
+ * @param fontSet
+ * describes the set of Fonts to be installed
+ */
+ private static void initFontDefaults(UIDefaults table, FontSet fontSet) {
+ Font controlFont = fontSet.getControlFont();
+ Font menuFont = fontSet.getMenuFont();
+ Font messageFont = fontSet.getMessageFont();
+ Font toolTipFont = fontSet.getSmallFont();
+ Font titleFont = fontSet.getTitleFont();
+ Font windowFont = fontSet.getWindowTitleFont();
+
+ // System.out.println("Control: " + fontSet.getControlFont());
+ // System.out.println("Menu: " + fontSet.getMenuFont());
+ // System.out.println("Message: " + fontSet.getMessageFont());
+ // System.out.println("Small: " + fontSet.getSmallFont());
+ // System.out.println("Title: " + fontSet.getTitleFont());
+ // System.out.println("Window title: " + fontSet.getWindowTitleFont());
+
+ Object[] defaults = {
+
+ "Button.font", controlFont,
+
+ "CheckBox.font", controlFont,
+
+ "ColorChooser.font", controlFont,
+
+ "ComboBox.font", controlFont,
+
+ "EditorPane.font", controlFont,
+
+ "FormattedTextField.font", controlFont,
+
+ "Label.font", controlFont,
+
+ "List.font", controlFont,
+
+ "Panel.font", controlFont,
+
+ "PasswordField.font", controlFont,
+
+ "ProgressBar.font", controlFont,
+
+ "RadioButton.font", controlFont,
+
+ "ScrollPane.font", controlFont,
+
+ "Spinner.font", controlFont,
+
+ "TabbedPane.font", controlFont,
+
+ "Table.font", controlFont,
+
+ "TableHeader.font", controlFont,
+
+ "TextArea.font", controlFont,
+
+ "TextField.font", controlFont,
+
+ "TextPane.font", controlFont,
+
+ "ToolBar.font", controlFont,
+
+ "ToggleButton.font", controlFont,
+
+ "Tree.font", controlFont,
+
+ "Viewport.font", controlFont,
+
+ "InternalFrame.titleFont", windowFont,
+
+ "DesktopIcon.titleFont", windowFont,
+
+ "OptionPane.font", messageFont,
+
+ "OptionPane.messageFont", messageFont,
+
+ "OptionPane.buttonFont", messageFont,
+
+ "TitledBorder.font", titleFont,
+
+ "ToolTip.font", toolTipFont,
+
+ "CheckBoxMenuItem.font", menuFont,
+
+ "CheckBoxMenuItem.acceleratorFont", menuFont,
+
+ "Menu.font", menuFont,
+
+ "Menu.acceleratorFont", menuFont,
+
+ "MenuBar.font", menuFont,
+
+ "MenuItem.font", menuFont,
+
+ "MenuItem.acceleratorFont", menuFont,
+
+ "PopupMenu.font", menuFont,
+
+ "RadioButtonMenuItem.font", menuFont,
+
+ "RadioButtonMenuItem.acceleratorFont", menuFont,
+ // ?
+ };
+ table.putDefaults(defaults);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicLookAndFeel#getDefaults()
+ */
+ @Override
+ public UIDefaults getDefaults() {
+ UIDefaults table = super.getDefaults();
+
+ SubstanceLookAndFeel.componentPlugins.processAllDefaultsEntries(table,
+ this.skin);
+ return table;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicLookAndFeel#initialize()
+ */
+ @Override
+ public void initialize() {
+ super.initialize();
+ ShadowPopupFactory.install();
+
+ setSkin(this.skin, false);
+
+ // tracer for memory analysis
+ String paramTraceFile = SubstanceCoreUtilities
+ .getVmParameter(SubstanceLookAndFeel.TRACE_FILE);
+ if (paramTraceFile != null) {
+ MemoryAnalyzer.commence(1000, paramTraceFile);
+ for (Object plugin : SubstanceLookAndFeel.componentPlugins
+ .getAvailablePlugins(true))
+ MemoryAnalyzer.enqueueUsage("Has plugin '"
+ + plugin.getClass().getName() + "'");
+ }
+
+ // to show heap status panel in title pane?
+ String heapStatusPanelParam = SubstanceCoreUtilities
+ .getVmParameter(SubstanceLookAndFeel.HEAP_STATUS_TRACE_FILE);
+ SubstanceTitlePane.setHeapStatusLogfileName(heapStatusPanelParam);
+
+ // initialize component plugins
+ SubstanceLookAndFeel.componentPlugins.initializeAll();
+
+ // initialize widget support
+ LafWidgetRepository.getRepository().setLafSupport(
+ new SubstanceWidgetSupport());
+
+ // fix for defect 208 - tracking changes to focus owner
+ // and repainting the default button
+ this.focusOwnerChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("focusOwner".equals(evt.getPropertyName())) {
+ Component newFocusOwner = (Component) evt.getNewValue();
+ if (newFocusOwner != null) {
+ JRootPane rootPane = SwingUtilities
+ .getRootPane(newFocusOwner);
+ if (rootPane == null)
+ return;
+ JButton defaultButton = rootPane.getDefaultButton();
+ if (defaultButton == null)
+ return;
+ defaultButton.repaint();
+ }
+ }
+ if ("managingFocus".equals(evt.getPropertyName())) {
+ if (Boolean.FALSE.equals(evt.getNewValue())) {
+ // new keyboard focus manager has been installed
+ currentKeyboardFocusManager
+ .removePropertyChangeListener(focusOwnerChangeListener);
+ currentKeyboardFocusManager = KeyboardFocusManager
+ .getCurrentKeyboardFocusManager();
+ currentKeyboardFocusManager
+ .addPropertyChangeListener(focusOwnerChangeListener);
+ }
+ }
+ }
+ };
+ this.currentKeyboardFocusManager = KeyboardFocusManager
+ .getCurrentKeyboardFocusManager();
+ this.currentKeyboardFocusManager
+ .addPropertyChangeListener(this.focusOwnerChangeListener);
+ if (!LookUtils.IS_OS_WINDOWS
+ || System.getProperty("java.version").compareTo("1.6.0_10") < 0) {
+ UIManager.put(WINDOW_ROUNDED_CORNERS, Boolean.FALSE);
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicLookAndFeel#uninitialize()
+ */
+ @Override
+ public void uninitialize() {
+ super.uninitialize();
+
+ SubstanceLookAndFeel.currentSkin = null;
+
+ ShadowPopupFactory.uninstall();
+
+ SubstanceCoreUtilities.stopThreads();
+
+ // fix for defect 109 - memory leak on watermarks
+ if (this.skin.getWatermark() != null)
+ this.skin.getWatermark().dispose();
+
+ // uninitialize component plugins
+ SubstanceLookAndFeel.componentPlugins.uninitializeAll();
+
+ // reset widget support
+ LafWidgetRepository.getRepository().unsetLafSupport();
+
+ // clear caches
+ LazyResettableHashMap.reset();
+
+ this.currentKeyboardFocusManager
+ .removePropertyChangeListener(this.focusOwnerChangeListener);
+ this.focusOwnerChangeListener = null;
+ this.currentKeyboardFocusManager = null;
+ }
+
+ /**
+ * Registers a new listener on skin change.
+ *
+ * @param skinChangeListener
+ * New listener on skin change.
+ * @see #setSkin(String)
+ * @see #setSkin(SubstanceSkin)
+ * @see #unregisterSkinChangeListener(SkinChangeListener)
+ */
+ public static void registerSkinChangeListener(
+ SkinChangeListener skinChangeListener) {
+ SubstanceLookAndFeel.skinChangeListeners.add(skinChangeListener);
+ }
+
+ /**
+ * Unregisters a listener on skin change.
+ *
+ * @param skinChangeListener
+ * The listener to unregister.
+ * @see #setSkin(String)
+ * @see #setSkin(SubstanceSkin)
+ * @see #registerSkinChangeListener(SkinChangeListener)
+ */
+ public static void unregisterSkinChangeListener(
+ SkinChangeListener skinChangeListener) {
+ SubstanceLookAndFeel.skinChangeListeners.remove(skinChangeListener);
+ }
+
+ /**
+ * Registers the specified listener on tab-close events on <b>all</b> tabbed
+ * panes.
+ *
+ * @param tabCloseListener
+ * Listener to register.
+ * @see #registerTabCloseChangeListener(JTabbedPane, BaseTabCloseListener)
+ * @see #unregisterTabCloseChangeListener(BaseTabCloseListener)
+ * @see #unregisterTabCloseChangeListener(JTabbedPane, BaseTabCloseListener)
+ */
+ public static void registerTabCloseChangeListener(
+ BaseTabCloseListener tabCloseListener) {
+ TabCloseListenerManager.getInstance()
+ .registerListener(tabCloseListener);
+ }
+
+ /**
+ * Registers the specified listener on tab-close events on <b>the
+ * specified</b> tabbed pane.
+ *
+ * @param tabbedPane
+ * Tabbed pane. If <code>null</code>, the tab close listener is
+ * registered globally (for all tabbed panes).
+ * @param tabCloseListener
+ * Listener to register.
+ * @see #registerTabCloseChangeListener(BaseTabCloseListener)
+ * @see #unregisterTabCloseChangeListener(BaseTabCloseListener)
+ * @see #unregisterTabCloseChangeListener(JTabbedPane, BaseTabCloseListener)
+ */
+ public static void registerTabCloseChangeListener(JTabbedPane tabbedPane,
+ BaseTabCloseListener tabCloseListener) {
+ TabCloseListenerManager.getInstance().registerListener(tabbedPane,
+ tabCloseListener);
+ }
+
+ /**
+ * Unregisters the specified listener on tab-close events on <b>all</b>
+ * tabbed panes.
+ *
+ * @param tabCloseListener
+ * Listener to unregister.
+ * @see #registerTabCloseChangeListener(BaseTabCloseListener)
+ * @see #registerTabCloseChangeListener(JTabbedPane, BaseTabCloseListener)
+ * @see #unregisterTabCloseChangeListener(JTabbedPane, BaseTabCloseListener)
+ */
+ public static void unregisterTabCloseChangeListener(
+ BaseTabCloseListener tabCloseListener) {
+ TabCloseListenerManager.getInstance().unregisterListener(
+ tabCloseListener);
+ }
+
+ /**
+ * Unregisters the specified listener on tab-close events on <b>the
+ * specified</b> tabbed pane.
+ *
+ * @param tabbedPane
+ * Tabbed pane. If <code>null</code>, the tab close listener is
+ * unregistered globally (for all tabbed panes).
+ * @param tabCloseListener
+ * Listener to unregister.
+ * @see #registerTabCloseChangeListener(BaseTabCloseListener)
+ * @see #registerTabCloseChangeListener(JTabbedPane, BaseTabCloseListener)
+ * @see #unregisterTabCloseChangeListener(BaseTabCloseListener)
+ */
+ public static void unregisterTabCloseChangeListener(JTabbedPane tabbedPane,
+ BaseTabCloseListener tabCloseListener) {
+ TabCloseListenerManager.getInstance().unregisterListener(tabbedPane,
+ tabCloseListener);
+ }
+
+ /**
+ * Returns the set of all listeners registered on tab-close events on
+ * <b>all</b> tabbed panes.
+ *
+ * @return Set of all listeners registered on tab-close events on <b>all</b>
+ * tabbed panes.
+ */
+ public static Set<BaseTabCloseListener> getAllTabCloseListeners() {
+ return TabCloseListenerManager.getInstance().getListeners();
+ }
+
+ /**
+ * Returns all listeners registered on tab closing of the specified tabbed
+ * pane.
+ *
+ * @param tabbedPane
+ * A tabbed pane. If <code>null</code>, all globally registered
+ * tab close listeners are returned.
+ * @return All listeners registered on tab closing of the specified tabbed
+ * pane.
+ */
+ public static Set<BaseTabCloseListener> getAllTabCloseListeners(
+ JTabbedPane tabbedPane) {
+ return TabCloseListenerManager.getInstance().getListeners(tabbedPane);
+ }
+
+ /**
+ * Registers a new listener on locale change.
+ *
+ * @param localeListener
+ * New listener on locale change.
+ */
+ public static void registerLocaleChangeListener(
+ LocaleChangeListener localeListener) {
+ SubstanceLookAndFeel.localeChangeListeners.add(localeListener);
+ }
+
+ /**
+ * Unregisters a listener on locale change.
+ *
+ * @param localeListener
+ * The listener to unregister.
+ */
+ public static void unregisterLocaleChangeListener(
+ LocaleChangeListener localeListener) {
+ SubstanceLookAndFeel.localeChangeListeners.remove(localeListener);
+ }
+
+ /**
+ * Returns all listeners registered on locale change.
+ *
+ * @return All listeners registered on locale change.
+ */
+ public static Set<LocaleChangeListener> getLocaleListeners() {
+ return Collections
+ .unmodifiableSet(SubstanceLookAndFeel.localeChangeListeners);
+ }
+
+ /**
+ * Sets the visibility of the specified widget kind(s). If the first
+ * <code>rootPane</code> parameter is <code>null</code>, this call applies
+ * to all root panes. This method should not be called from inside the
+ * initialization sequence of your window. If the specific widget needs to
+ * be visible when the window is shown, wrap the call with
+ * {@link SwingUtilities#invokeLater(Runnable)}.
+ *
+ * @param rootPane
+ * Root pane. May be <code>null</code>.
+ * @param visible
+ * Visibility indication.
+ * @param substanceWidgets
+ * Widget types.
+ * @since version 5.0
+ */
+ public static void setWidgetVisible(JRootPane rootPane, boolean visible,
+ SubstanceWidgetType... substanceWidgets) {
+ SubstanceWidgetManager.getInstance().register(rootPane, visible,
+ substanceWidgets);
+ if (rootPane != null) {
+ SwingUtilities.updateComponentTreeUI(rootPane);
+ } else {
+ for (Window window : Window.getWindows()) {
+ JRootPane root = SwingUtilities.getRootPane(window);
+ SwingUtilities.updateComponentTreeUI(root);
+ }
+ }
+ }
+
+ /**
+ * Checks whether the <code>JOptionPane</code>s created with predefined
+ * message types should use constant color schemes for the icons.
+ *
+ * @return <code>true</code> if the <code>JOptionPane</code>s created with
+ * predefined message types should use constant color schemes for
+ * the icons, <code>false</code> otherwise.
+ * @see #setToUseConstantThemesOnDialogs(boolean)
+ */
+ public static boolean isToUseConstantThemesOnDialogs() {
+ return SubstanceLookAndFeel.toUseConstantThemesOnDialogs;
+ }
+
+ /**
+ * Sets the new setting for the icons of the <code>JOptionPane</code>s
+ * created with predefined message types.
+ *
+ * @param toUseConstantThemesOnDialogs
+ * if <code>true</code>, the <code>JOptionPane</code>s created
+ * with predefined message types should use constant color
+ * schemes for the icons.
+ * @see #isToUseConstantThemesOnDialogs()
+ */
+ public static void setToUseConstantThemesOnDialogs(
+ boolean toUseConstantThemesOnDialogs) {
+ SubstanceLookAndFeel.toUseConstantThemesOnDialogs = toUseConstantThemesOnDialogs;
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ for (Window window : Window.getWindows()) {
+ SwingUtilities.updateComponentTreeUI(window);
+ }
+ }
+ });
+ }
+
+ /**
+ * The current Substance skin.
+ */
+ private static SubstanceSkin currentSkin = null;
+
+ /**
+ * Sets the specified skin. If the current look-and-feel is not Substance,
+ * this method will create a new Substance look-and-feel based on the
+ * specified skin and set it on {@link UIManager}. This method does not
+ * require Substance to be the current look-and-feel.
+ *
+ * @param newSkin
+ * Skin to set.
+ * @param toUpdateWindows
+ * if <code>true</code>, the
+ * {@link SwingUtilities#updateComponentTreeUI(Component)} is
+ * called on all windows returned by {@link Window#getWindows()}
+ * API.
+ * @return <code>true</code> if the specified skin has been set
+ * successfully, <code>false</code> otherwise.
+ * @see #setSkin(SubstanceSkin)
+ */
+ private static boolean setSkin(final SubstanceSkin newSkin,
+ final boolean toUpdateWindows) {
+ if (!SwingUtilities.isEventDispatchThread()) {
+ final boolean[] returnValue = new boolean[1];
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ returnValue[0] = setSkin(newSkin, toUpdateWindows);
+ }
+ });
+ } catch (InvocationTargetException ite) {
+ if (ite.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) ite.getCause();
+ } else if (ite.getCause() instanceof Error) {
+ throw (Error) ite.getCause();
+ }
+
+ } catch (InterruptedException ignore) {}
+ return returnValue[0];
+ }
+
+ if (!newSkin.isValid())
+ return false;
+
+ boolean isSubstance = (UIManager.getLookAndFeel() instanceof SubstanceLookAndFeel);
+ if (!isSubstance) {
+ class SkinDerivedLookAndFeel extends SubstanceLookAndFeel {
+ public SkinDerivedLookAndFeel(SubstanceSkin newSkin) {
+ super(newSkin);
+ }
+ }
+
+ LookAndFeel derived = new SkinDerivedLookAndFeel(newSkin);
+ try {
+ UIManager.setLookAndFeel(derived);
+ } catch (UnsupportedLookAndFeelException ulafe) {
+ return false;
+ }
+ if (!(UIManager.getLookAndFeel() instanceof SubstanceLookAndFeel)) {
+ return false;
+ }
+ for (Window window : Window.getWindows()) {
+ SwingUtilities.updateComponentTreeUI(window);
+ }
+ return true;
+ }
+
+ try {
+ // Required skin settings must be non-null
+ if (!newSkin.isValid()) {
+ return false;
+ }
+
+ // fix for defect 109 - memory leak on watermark switch
+ if ((currentSkin != null) && (currentSkin.getWatermark() != null)) {
+ currentSkin.getWatermark().dispose();
+ }
+ if (newSkin.getWatermark() != null) {
+ if (!newSkin.getWatermark().updateWatermarkImage(newSkin)) {
+ return false;
+ }
+ }
+
+ UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
+ // The table will be null when the skin is set using a custom
+ // LAF
+ if (lafDefaults != null) {
+ initFontDefaults(lafDefaults, SubstanceLookAndFeel
+ .getFontPolicy().getFontSet("Substance", null));
+ newSkin.addCustomEntriesToTable(lafDefaults);
+ SubstanceLookAndFeel.componentPlugins
+ .processAllDefaultsEntries(lafDefaults, newSkin);
+ }
+
+ // file chooser strings go to the main UIManager table
+ for (ResourceBundle bundle : new ResourceBundle[] {
+ ResourceBundle.getBundle("com.sun.swing.internal.plaf.metal.resources.metal"),
+ SubstanceLookAndFeel.getLabelBundle()
+ }) {
+ Enumeration<String> keyEn = bundle.getKeys();
+ while (keyEn.hasMoreElements()) {
+ String key = keyEn.nextElement();
+ if (key.contains("FileChooser")) {
+ String value = bundle.getString(key);
+ UIManager.put(key, value);
+ }
+ }
+ }
+
+ if (isSubstance)
+ LazyResettableHashMap.reset();
+
+ currentSkin = newSkin;
+
+ if (toUpdateWindows) {
+ for (Window window : Window.getWindows()) {
+ SwingUtilities.updateComponentTreeUI(window);
+ }
+ }
+
+ // SwingUtilities.invokeLater(new Runnable() {
+ // public void run() {
+ for (SkinChangeListener skinChangeListener : SubstanceLookAndFeel.skinChangeListeners)
+ skinChangeListener.skinChanged();
+ // }
+ // });
+ return true;
+ } catch (NoClassDefFoundError ncdfe) {
+ // this may happen when a skin references some class
+ // that can't be found in the classpath.
+ return false;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Sets the specified skin. If the current look-and-feel is not Substance,
+ * this method will create a new Substance look-and-feel based on the
+ * specified skin and set it on {@link UIManager}. This method does not
+ * require Substance to be the current look-and-feel. Calling this method
+ * will call {@link SwingUtilities#updateComponentTreeUI(Component)} on all
+ * open top-level windows.
+ *
+ * @param newSkin
+ * Skin to set.
+ * @return <code>true</code> if the specified skin has been set
+ * successfully, <code>false</code> otherwise.
+ * @throws IllegalStateException
+ * When called outside the Event Dispatch Thread.
+ * @see #registerSkinChangeListener(SkinChangeListener)
+ * @see #unregisterSkinChangeListener(SkinChangeListener)
+ * @see SubstanceSkin#isValid()
+ */
+ public static boolean setSkin(SubstanceSkin newSkin) {
+ return setSkin(newSkin, true);
+ }
+
+ /**
+ * Sets the specified skin. If the current look-and-feel is not Substance,
+ * this method will create a new Substance look-and-feel based on the
+ * specified skin and set it on {@link UIManager}. This method does not
+ * require Substance to be the current look-and-feel. Calling this method
+ * will call {@link SwingUtilities#updateComponentTreeUI(Component)} on all
+ * open top-level windows.
+ *
+ * @param skinClassName
+ * Skin to set.
+ * @return <code>true</code> if the specified skin has been set
+ * successfully, <code>false</code> otherwise.
+ * @throws IllegalStateException
+ * When called outside the Event Dispatch Thread.
+ * @since version 3.1
+ * @see #setSkin(SubstanceSkin)
+ * @see #registerSkinChangeListener(SkinChangeListener)
+ * @see #unregisterSkinChangeListener(SkinChangeListener)
+ * @see SubstanceSkin#isValid()
+ */
+ public static boolean setSkin(String skinClassName) {
+ try {
+ Class<?> skinClass = Class.forName(skinClassName);
+ if (skinClass == null) {
+ return false;
+ }
+ Object obj = skinClass.newInstance();
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof SubstanceSkin)) {
+ return false;
+ }
+ return SubstanceLookAndFeel.setSkin((SubstanceSkin) obj);
+ } catch (Exception exc) {
+ exc.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * Returns all available skins.
+ *
+ * @return All available skins. Key - skin display name, value - skin
+ * information.
+ */
+ public static Map<String, SkinInfo> getAllSkins() {
+ initPluginsIfNecessary();
+ Map<String, SkinInfo> result = new TreeMap<String, SkinInfo>();
+ for (Object skinPlugin : SubstanceLookAndFeel.skinPlugins
+ .getAvailablePlugins(true)) {
+ for (SkinInfo skinInfo : ((SubstanceSkinPlugin) skinPlugin)
+ .getSkins()) {
+ result.put(skinInfo.getDisplayName(), skinInfo);
+ }
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.LookAndFeel#getSupportsWindowDecorations()
+ */
+ @Override
+ public boolean getSupportsWindowDecorations() {
+ return true;
+ }
+
+ /**
+ * Sets the class loader for {@link #LABEL_BUNDLE}.
+ *
+ * @param labelBundleClassLoader
+ * Class loader for {@link #LABEL_BUNDLE}.
+ * @since version 3.1
+ */
+ public static void setLabelBundleClassLoader(
+ ClassLoader labelBundleClassLoader) {
+ SubstanceLookAndFeel.labelBundleClassLoader = labelBundleClassLoader;
+ LafWidgetRepository.setLabelBundleClassLoader(labelBundleClassLoader);
+ }
+
+ /**
+ * Returns the title pane of the specified top-level window.
+ *
+ * @param window
+ * Top-level window.
+ * @return If the parameter is either {@link JFrame} or {@link JDialog} and
+ * has custom decorations, the result is the title pane,
+ * <code>null</code> otherwise.
+ * @since version 3.1
+ */
+ public static JComponent getTitlePaneComponent(Window window) {
+ JRootPane rootPane = null;
+ if (window instanceof JFrame) {
+ JFrame f = (JFrame) window;
+ rootPane = f.getRootPane();
+ }
+ if (window instanceof JDialog) {
+ JDialog d = (JDialog) window;
+ rootPane = d.getRootPane();
+ }
+ if (rootPane != null) {
+ SubstanceRootPaneUI ui = (SubstanceRootPaneUI) rootPane.getUI();
+ return ui.getTitlePane();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the decoration type of the specified component and all its children.
+ *
+ * @param comp
+ * Component.
+ * @param type
+ * Decoration type of the component and all its children.
+ */
+ public static void setDecorationType(JComponent comp,
+ DecorationAreaType type) {
+ DecorationPainterUtils.setDecorationType(comp, type);
+ }
+
+ /**
+ * Returns the decoration area type of the specified component. The
+ * component and its ancestor hierarchy are scanned for the registered
+ * decoration area type. If
+ * {@link #setDecorationType(JComponent, DecorationAreaType)} has been
+ * called on the specified component, the matching decoration type is
+ * returned. Otherwise, the component hierarchy is scanned to find the
+ * closest ancestor that was passed to
+ * {@link #setDecorationType(JComponent, DecorationAreaType)} - and its
+ * decoration type is returned. If neither the component, nor any one of its
+ * parent components has been passed to the setter method,
+ * {@link DecorationAreaType#NONE} is returned.
+ *
+ * @param comp
+ * Component.
+ * @return Decoration area type of the component.
+ */
+ public static DecorationAreaType getDecorationType(Component comp) {
+ return DecorationPainterUtils.getDecorationType(comp);
+ }
+
+ /**
+ * Returns the immediate decoration area type of the specified component.
+ * The component is checked for the registered decoration area type. If
+ * {@link #setDecorationType(javax.swing.JComponent, DecorationAreaType)} was
+ * not called on this component, this method returns <code>null</code>.
+ *
+ * @param comp
+ * Component.
+ * @return Immediate decoration area type of the component.
+ */
+ public static DecorationAreaType getImmediateDecorationType(Component comp) {
+ return DecorationPainterUtils.getImmediateDecorationType(comp);
+ }
+
+ /**
+ * Checks whether Substance is the current look-and-feel. This method is for
+ * internal use only.
+ *
+ * @return <code>true</code> if Substance is the current look-and-feel,
+ * <code>false</code> otherwise.
+ */
+ public static boolean isCurrentLookAndFeel() {
+ return ((UIManager.getLookAndFeel() instanceof SubstanceLookAndFeel) && (currentSkin != null));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.LookAndFeel#getDisabledIcon(javax.swing.JComponent,
+ * javax.swing.Icon)
+ */
+ @Override
+ public Icon getDisabledIcon(JComponent component, Icon icon) {
+ if (icon == null)
+ return null;
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(component, ComponentState.DISABLED_UNSELECTED);
+ BufferedImage result = SubstanceImageCreator.getColorSchemeImage(
+ component, icon, colorScheme, 0.5f);
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(component,
+ ComponentState.DISABLED_UNSELECTED);
+ if (alpha < 1.0f) {
+ BufferedImage intermediate = SubstanceCoreUtilities.getBlankImage(
+ result.getWidth(), result.getHeight());
+ Graphics2D g2d = intermediate.createGraphics();
+ g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
+ g2d.drawImage(result, 0, 0, null);
+ g2d.dispose();
+ result = intermediate;
+ }
+
+ return new IconUIResource(new ImageIcon(result));
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/SubstanceSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/SubstanceSkin.java
new file mode 100644
index 0000000..4b4684f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/SubstanceSkin.java
@@ -0,0 +1,1137 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+import java.awt.Component;
+import java.awt.Graphics2D;
+import java.net.URL;
+import java.util.*;
+
+import javax.swing.JTabbedPane;
+import javax.swing.UIDefaults;
+
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.SubstanceDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.SubstanceHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.SubstanceOverlayPainter;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.api.trait.SubstanceTrait;
+import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Base abstract class for Substance skins.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class SubstanceSkin implements SubstanceTrait {
+ /**
+ * Maps decoration area type to the color scheme bundles. Must contain an
+ * entry for {@link DecorationAreaType#NONE}.
+ */
+ protected Map<DecorationAreaType, SubstanceColorSchemeBundle> colorSchemeBundleMap;
+
+ /**
+ * Maps decoration area type to the background color schemes.
+ */
+ protected Map<DecorationAreaType, SubstanceColorScheme> backgroundColorSchemeMap;
+
+ /**
+ * Maps decoration area type to the registered overlay painters. Each
+ * decoration area type can have more than one overlay painter.
+ */
+ protected Map<DecorationAreaType, List<SubstanceOverlayPainter>> overlayPaintersMap;
+
+ /**
+ * The watermark of <code>this</code> skin. May be <code>null</code> if
+ * <code>this</code> skin doesn't define a custom watermark.
+ */
+ protected SubstanceWatermark watermark;
+
+ /**
+ * The button shaper of <code>this</code> skin. Must be non-
+ * <code>null</code>.
+ */
+ protected SubstanceButtonShaper buttonShaper;
+
+ /**
+ * The fill painter of <code>this</code> skin. Must be non-
+ * <code>null</code>.
+ */
+ protected SubstanceFillPainter fillPainter;
+
+ /**
+ * The border painter of <code>this</code> skin. Must be non-
+ * <code>null</code>.
+ */
+ protected SubstanceBorderPainter borderPainter;
+
+ /**
+ * The highlight border painter of <code>this</code> skin. Can be
+ * <code>null</code>.
+ */
+ protected SubstanceBorderPainter highlightBorderPainter;
+
+ /**
+ * The highlight painter of <code>this</code> skin. Must be non-
+ * <code>null</code>.
+ */
+ protected SubstanceHighlightPainter highlightPainter;
+
+ /**
+ * The decoration painter of <code>this</code> skin. Must be non-
+ * <code>null</code>.
+ */
+ protected SubstanceDecorationPainter decorationPainter;
+
+ /**
+ * Set of all decoration area types that are not explicitly registered in
+ * {@link #colorSchemeBundleMap} but still are considered as decoration
+ * areas in this skin. Controls lying in such areas will have their
+ * background painted by
+ * {@link SubstanceDecorationPainter#paintDecorationArea(Graphics2D, Component, DecorationAreaType, int, int, SubstanceSkin)}
+ * instead of a simple background fill.
+ */
+ protected Set<DecorationAreaType> decoratedAreaSet;
+
+ /**
+ * The start of fade effect on the selected tabs in {@link JTabbedPane}s.
+ *
+ * @see #selectedTabFadeEnd
+ */
+ protected double selectedTabFadeStart;
+
+ /**
+ * The end of fade effect on the selected tabs in {@link JTabbedPane}s.
+ *
+ * @see #selectedTabFadeStart
+ */
+ protected double selectedTabFadeEnd;
+
+ /**
+ * Contains the types of decoration areas that show a drop shadow on the few
+ * top pixels.
+ */
+ // protected Set<DecorationAreaType> dropShadowsSet;
+
+ /**
+ * Color scheme for watermarks.
+ */
+ protected SubstanceColorScheme watermarkScheme;
+
+ /**
+ * All component states that have associated non-trivial alpha values.
+ */
+ Set<ComponentState> statesWithAlpha;
+
+ /**
+ * Constructs the basic data structures for a skin.
+ */
+ protected SubstanceSkin() {
+ this.colorSchemeBundleMap = new HashMap<DecorationAreaType, SubstanceColorSchemeBundle>();
+ this.backgroundColorSchemeMap = new HashMap<DecorationAreaType, SubstanceColorScheme>();
+ this.overlayPaintersMap = new HashMap<DecorationAreaType, List<SubstanceOverlayPainter>>();
+
+ this.decoratedAreaSet = new HashSet<DecorationAreaType>();
+ this.decoratedAreaSet.add(DecorationAreaType.PRIMARY_TITLE_PANE);
+ this.decoratedAreaSet.add(DecorationAreaType.SECONDARY_TITLE_PANE);
+
+ // this.dropShadowsSet = EnumSet.noneOf(DecorationAreaType.class);
+
+ this.selectedTabFadeStart = 0.1;
+ this.selectedTabFadeEnd = 0.3;
+
+ this.statesWithAlpha = new HashSet<ComponentState>();// EnumSet.noneOf(ComponentState.class);
+ }
+
+ // /**
+ // * Returns the main default color scheme for this skin. The result is the
+ // * default color scheme for controls that do not lie in any decoration
+ // area.
+ // * Custom painting code that needs to consult the colors of the specific
+ // * component should use {@link #getColorScheme(Component, ComponentState)}
+ // * method and various {@link SubstanceColorScheme} methods.
+ // *
+ // * @return The main default color scheme for this skin.
+ // * @see #getColorScheme(Component, ComponentState)
+ // * @see #getMainDefaultColorScheme(DecorationAreaType)
+ // */
+ // public final SubstanceColorScheme getMainDefaultColorScheme() {
+ // return this.colorSchemeBundleMap.get(DecorationAreaType.NONE)
+ // .getDefaultColorScheme();
+ // }
+ //
+ // /**
+ // * Returns the main disabled color scheme for this skin. The result is the
+ // * disabled color scheme for controls that do not lie in any decoration
+ // * area. Custom painting code that needs to consult the colors of the
+ // * specific component should use
+ // * {@link #getColorScheme(Component, ComponentState)} method and various
+ // * {@link SubstanceColorScheme} methods.
+ // *
+ // * @return The main disabled color scheme for this skin.
+ // * @see #getColorScheme(Component, ComponentState)
+ // * @see #getMainDisabledColorScheme(DecorationAreaType)
+ // */
+ // public final SubstanceColorScheme getMainDisabledColorScheme() {
+ // return this.colorSchemeBundleMap.get(DecorationAreaType.NONE)
+ // .getDisabledColorScheme();
+ // }
+
+ /**
+ * Returns the watermark of this skin.
+ *
+ * @return The watermark of this skin. May be <code>null</code>.
+ */
+ public final SubstanceWatermark getWatermark() {
+ return this.watermark;
+ }
+
+ /**
+ * Returns the border painter of this skin.
+ *
+ * @return The border painter of this skin. A valid skin cannot have a
+ * <code>null</code> value returned from this method. Call
+ * {@link #isValid()
+ * } to verify that the skin is valid.
+ * @see #isValid()
+ */
+ public final SubstanceBorderPainter getBorderPainter() {
+ return this.borderPainter;
+ }
+
+ /**
+ * Returns the highlight border painter of this skin.
+ *
+ * @return The highlight border painter of this skin. The return value of
+ * this method may be <code>null</code>. In this case, call
+ * {@link #getBorderPainter()}.
+ */
+ public final SubstanceBorderPainter getHighlightBorderPainter() {
+ return this.highlightBorderPainter;
+ }
+
+ /**
+ * Returns the button shaper of this skin.
+ *
+ * @return The button shaper of this skin. A valid skin cannot have a
+ * <code>null</code> value returned from this method. Call
+ * {@link #isValid()
+ * } to verify that the skin is valid.
+ * @see #isValid()
+ */
+ public final SubstanceButtonShaper getButtonShaper() {
+ return this.buttonShaper;
+ }
+
+ /**
+ * Returns the fill painter of this skin.
+ *
+ * @return The fill painter of this skin. A valid skin cannot have a
+ * <code>null</code> value returned from this method. Call
+ * {@link #isValid()
+ * } to verify that the skin is valid.
+ * @see #isValid()
+ */
+ public final SubstanceFillPainter getFillPainter() {
+ return this.fillPainter;
+ }
+
+ /**
+ * Returns the highlight painter of this skin.
+ *
+ * @return The highlight painter of this skin. A valid skin cannot have a
+ * <code>null</code> value returned from this method. Call
+ * {@link #isValid()
+ * } to verify that the skin is valid.
+ * @see #isValid()
+ */
+ public final SubstanceHighlightPainter getHighlightPainter() {
+ return this.highlightPainter;
+ }
+
+ /**
+ * Returns the decoration painter of this skin.
+ *
+ * @return The decoration painter of this skin. A valid skin cannot have a
+ * <code>null</code> value returned from this method. Call
+ * {@link #isValid()
+ * } to verify that the skin is valid.
+ * @see #isValid()
+ */
+ public final SubstanceDecorationPainter getDecorationPainter() {
+ return this.decorationPainter;
+ }
+
+ /**
+ * Adds skin-specific entries to the UI defaults table.
+ *
+ * @param table
+ * UI defaults table.
+ */
+ public void addCustomEntriesToTable(UIDefaults table) {
+ // Apparently this function is called with null table
+ // when the application is run with -Dswing.defaultlaf
+ // setting. In this case, this function will be called
+ // second time with correct table.
+ if (table == null) {
+ return;
+ }
+
+ SkinUtilities.addCustomEntriesToTable(table, this);
+ }
+
+ /**
+ * Returns the color scheme of the specified component in the specified
+ * component state.
+ *
+ * @param comp
+ * Component.
+ * @param componentState
+ * Component state.
+ * @return The color scheme of the component in the specified component
+ * state.
+ */
+ public final SubstanceColorScheme getColorScheme(Component comp,
+ ComponentState componentState) {
+ // small optimization - lookup the decoration area only if there
+ // are decoration-specific scheme bundles.
+ if (this.colorSchemeBundleMap.size() > 1) {
+ DecorationAreaType decorationAreaType = SubstanceLookAndFeel
+ .getDecorationType(comp);
+ // if ((decorationAreaType == DecorationAreaType.NONE)
+ // && (componentState == ComponentState.DEFAULT)) {
+ // return this.defaultColorScheme;
+ // }
+ if (this.colorSchemeBundleMap.containsKey(decorationAreaType)) {
+ SubstanceColorScheme registered = this.colorSchemeBundleMap
+ .get(decorationAreaType).getColorScheme(componentState);
+ if (registered == null) {
+ throw new IllegalStateException(
+ "Color scheme shouldn't be null here. Please report this issue");
+ }
+ return registered;
+ }
+ }
+
+ // if (componentState == ComponentState.DEFAULT)
+ // return this.defaultColorScheme;
+
+ SubstanceColorScheme registered = this.colorSchemeBundleMap.get(
+ DecorationAreaType.NONE).getColorScheme(componentState);
+ if (registered == null) {
+ throw new IllegalStateException(
+ "Color scheme shouldn't be null here. Please report this issue");
+ }
+ return registered;
+ }
+
+ /**
+ * Returns the alpha channel of the highlight color scheme of the component.
+ *
+ * @param comp
+ * Component.
+ * @param componentState
+ * Component state.
+ * @return Highlight color scheme alpha channel.
+ */
+ public final float getHighlightAlpha(Component comp,
+ ComponentState componentState) {
+ // small optimization - lookup the decoration area only if there
+ // are decoration-specific scheme bundles.
+ if (this.colorSchemeBundleMap.size() > 1) {
+ DecorationAreaType decorationAreaType = SubstanceLookAndFeel
+ .getDecorationType(comp);
+ if (this.colorSchemeBundleMap.containsKey(decorationAreaType)) {
+ Float registered = this.colorSchemeBundleMap.get(
+ decorationAreaType).getHighlightAlpha(comp,
+ componentState);
+ if (registered >= 0.0)
+ return registered;
+ }
+ }
+
+ Float registered = this.colorSchemeBundleMap.get(
+ DecorationAreaType.NONE)
+ .getHighlightAlpha(comp, componentState);
+ if (registered >= 0.0)
+ return registered;
+
+ boolean isRollover = componentState
+ .isFacetActive(ComponentStateFacet.ROLLOVER);
+ boolean isSelected = componentState
+ .isFacetActive(ComponentStateFacet.SELECTION);
+ boolean isArmed = componentState.isFacetActive(ComponentStateFacet.ARM);
+
+ if (isRollover && isSelected)
+ return 0.9f;
+ if (isRollover && isArmed)
+ return 0.8f;
+ if (isSelected)
+ return 0.7f;
+ if (isArmed)
+ return 0.6f;
+ if (isRollover)
+ return 0.4f;
+ return 0.0f;
+ }
+
+ /**
+ * Returns the alpha channel of the color scheme of the component.
+ *
+ * @param comp
+ * Component.
+ * @param componentState
+ * Component state.
+ * @return Color scheme alpha channel.
+ */
+ public final float getAlpha(Component comp, ComponentState componentState) {
+ // optimization - if the state is not registered in any
+ // scheme bundle with custom alpha, return 1.0
+ if (!this.statesWithAlpha.contains(componentState))
+ return 1.0f;
+
+ // small optimization - lookup the decoration area only if there
+ // are decoration-specific scheme bundles.
+ if (this.colorSchemeBundleMap.size() > 1) {
+ DecorationAreaType decorationAreaType = SubstanceLookAndFeel
+ .getDecorationType(comp);
+ if (this.colorSchemeBundleMap.containsKey(decorationAreaType)) {
+ Float registered = this.colorSchemeBundleMap.get(
+ decorationAreaType).getAlpha(comp, componentState);
+ if (registered >= 0.0)
+ return registered;
+ }
+ }
+
+ Float registered = this.colorSchemeBundleMap.get(
+ DecorationAreaType.NONE).getAlpha(comp, componentState);
+ if (registered >= 0.0)
+ return registered;
+
+ return 1.0f;
+ }
+
+ /**
+ * Registers the specified color scheme bundle and background color scheme
+ * to be used on controls in decoration areas.
+ *
+ * @param bundle
+ * The color scheme bundle to use on controls in decoration
+ * areas.
+ * @param backgroundColorScheme
+ * The color scheme to use for background of controls in
+ * decoration areas.
+ * @param areaTypes
+ * Enumerates the area types that are affected by the parameters.
+ */
+ public void registerDecorationAreaSchemeBundle(
+ SubstanceColorSchemeBundle bundle,
+ SubstanceColorScheme backgroundColorScheme,
+ DecorationAreaType... areaTypes) {
+ if (bundle == null)
+ return;
+
+ if (backgroundColorScheme == null) {
+ throw new IllegalArgumentException(
+ "Cannot pass null background color scheme");
+ }
+
+ for (DecorationAreaType areaType : areaTypes) {
+ this.decoratedAreaSet.add(areaType);
+ this.colorSchemeBundleMap.put(areaType, bundle);
+ this.backgroundColorSchemeMap.put(areaType, backgroundColorScheme);
+
+ // if (areaType == DecorationAreaType.NONE) {
+ // this.defaultColorScheme = bundle.getDefaultColorScheme();
+ // }
+ }
+ this.statesWithAlpha.addAll(bundle.getStatesWithAlpha());
+ }
+
+ /**
+ * Registers the specified color scheme bundle to be used on controls in
+ * decoration areas.
+ *
+ * @param bundle
+ * The color scheme bundle to use on controls in decoration
+ * areas.
+ * @param areaTypes
+ * Enumerates the area types that are affected by the parameters.
+ */
+ public void registerDecorationAreaSchemeBundle(
+ SubstanceColorSchemeBundle bundle, DecorationAreaType... areaTypes) {
+ this.registerDecorationAreaSchemeBundle(bundle, bundle
+ .getEnabledColorScheme(), areaTypes);
+ }
+
+ /**
+ * Registers the specified background color scheme to be used on controls in
+ * decoration areas.
+ *
+ * @param backgroundColorScheme
+ * The color scheme to use for background of controls in
+ * decoration areas.
+ * @param areaTypes
+ * Enumerates the area types that are affected by the parameters.
+ * Each decoration area type will be painted by
+ * {@link SubstanceDecorationPainter#paintDecorationArea(Graphics2D, Component, DecorationAreaType, int, int, SubstanceSkin)}
+ * .
+ */
+ public void registerAsDecorationArea(
+ SubstanceColorScheme backgroundColorScheme,
+ DecorationAreaType... areaTypes) {
+ if (backgroundColorScheme == null) {
+ throw new IllegalArgumentException(
+ "Cannot pass null background color scheme");
+ }
+ for (DecorationAreaType areaType : areaTypes) {
+ this.decoratedAreaSet.add(areaType);
+ this.backgroundColorSchemeMap.put(areaType, backgroundColorScheme);
+ }
+ }
+
+ /**
+ * Returns indication whether the specified decoration area type should have
+ * their background painted by
+ * {@link SubstanceDecorationPainter#paintDecorationArea(Graphics2D, Component, DecorationAreaType, int, int, SubstanceSkin)}
+ * instead of a simple background fill.
+ *
+ * @param decorationType
+ * Decoration area type.
+ * @return <code>true</code> if specified decoration area type should have
+ * their background painted by
+ * {@link SubstanceDecorationPainter#paintDecorationArea(Graphics2D, Component, DecorationAreaType, int, int, SubstanceSkin)}
+ * , <code>false</code> otherwise.
+ */
+ public boolean isRegisteredAsDecorationArea(
+ DecorationAreaType decorationType) {
+ return this.decoratedAreaSet.contains(decorationType);
+ }
+
+ /**
+ * Returns the color scheme to be used for painting the watermark. If no
+ * custom watermark color scheme is specified ({@link #watermarkScheme} is
+ * <code>null</code>), the main default color scheme of this skin is used.
+ *
+ * @return The color scheme to be used for painting the watermark.
+ */
+ public SubstanceColorScheme getWatermarkColorScheme() {
+ if (this.watermarkScheme != null) {
+ return this.watermarkScheme;
+ }
+
+ return this.colorSchemeBundleMap.get(DecorationAreaType.NONE)
+ .getEnabledColorScheme();
+ }
+
+ /**
+ * Returns the main active color scheme for the specific decoration area
+ * type. Custom painting code that needs to consult the colors of the
+ * specific component should use
+ * {@link #getColorScheme(Component, ComponentState)} method and various
+ * {@link SubstanceColorScheme} methods.
+ *
+ * @param decorationAreaType
+ * Decoration area type.
+ * @return The main active color scheme for this skin.
+ * @see #getColorScheme(Component, ComponentState)
+ */
+ public final SubstanceColorScheme getActiveColorScheme(
+ DecorationAreaType decorationAreaType) {
+ if (this.colorSchemeBundleMap.containsKey(decorationAreaType))
+ return this.colorSchemeBundleMap.get(decorationAreaType)
+ .getActiveColorScheme();
+ return this.colorSchemeBundleMap.get(DecorationAreaType.NONE)
+ .getActiveColorScheme();
+ }
+
+ /**
+ * Returns the main enabled color scheme for the specific decoration area
+ * type. Custom painting code that needs to consult the colors of the
+ * specific component should use
+ * {@link #getColorScheme(Component, ComponentState)} method and various
+ * {@link SubstanceColorScheme} methods.
+ *
+ * @param decorationAreaType
+ * Decoration area type.
+ * @return The main enabled color scheme for this skin.
+ * @see #getColorScheme(Component, ComponentState)
+ */
+ public final SubstanceColorScheme getEnabledColorScheme(
+ DecorationAreaType decorationAreaType) {
+ if (this.colorSchemeBundleMap.containsKey(decorationAreaType))
+ return this.colorSchemeBundleMap.get(decorationAreaType)
+ .getEnabledColorScheme();
+ return this.colorSchemeBundleMap.get(DecorationAreaType.NONE)
+ .getEnabledColorScheme();
+ }
+
+ /**
+ * Returns the main disabled color scheme for the specific decoration area
+ * type. Custom painting code that needs to consult the colors of the
+ * specific component should use
+ * {@link #getColorScheme(Component, ComponentState)} method and various
+ * {@link SubstanceColorScheme} methods.
+ *
+ * @param decorationAreaType
+ * Decoration area type.
+ * @return The main disabled color scheme for this skin.
+ * @see #getColorScheme(Component, ComponentState)
+ */
+ public final SubstanceColorScheme getDisabledColorScheme(
+ DecorationAreaType decorationAreaType) {
+ if (this.colorSchemeBundleMap.containsKey(decorationAreaType))
+ return this.colorSchemeBundleMap.get(decorationAreaType)
+ .getDisabledColorScheme();
+ return this.colorSchemeBundleMap.get(DecorationAreaType.NONE)
+ .getDisabledColorScheme();
+ }
+
+ /**
+ * Returns the start of fade effect on the selected tabs in
+ * {@link JTabbedPane}s. This value can be used to create XP-like "headers"
+ * on the selected tabs.
+ *
+ * @return The start of fade effect on the selected tabs in
+ * {@link JTabbedPane}s.
+ * @see #getSelectedTabFadeEnd()
+ */
+ public final double getSelectedTabFadeStart() {
+ return this.selectedTabFadeStart;
+ }
+
+ /**
+ * Returns the end of fade effect on the selected tabs in
+ * {@link JTabbedPane
+ * }s. This value can be used to create XP-like "headers"
+ * on the selected tabs.
+ *
+ * @return The end of fade effect on the selected tabs in
+ * {@link JTabbedPane
+ * }s.
+ * @see #getSelectedTabFadeStart()
+ */
+ public final double getSelectedTabFadeEnd() {
+ return this.selectedTabFadeEnd;
+ }
+
+ /**
+ * Sets the end of fade effect on the selected tabs in {@link JTabbedPane}s.
+ * The value should be in 0.0-1.0 range.
+ *
+ * @param selectedTabFadeEnd
+ * The end of fade effect on the selected tabs in
+ * {@link JTabbedPane}s. Should be in 0.0-1.0 range.
+ */
+ public void setSelectedTabFadeEnd(double selectedTabFadeEnd) {
+ if ((selectedTabFadeEnd < 0.0) || (selectedTabFadeEnd > 1.0)) {
+ throw new IllegalArgumentException(
+ "Value for selected tab fade end should be in 0.0-1.0 range");
+ }
+ this.selectedTabFadeEnd = selectedTabFadeEnd;
+ }
+
+ /**
+ * Sets the start of fade effect on the selected tabs in {@link JTabbedPane}
+ * s. The value should be in 0.0-1.0 range.
+ *
+ * @param selectedTabFadeStart
+ * The start of fade effect on the selected tabs in
+ * {@link JTabbedPane} s. Should be in 0.0-1.0 range.
+ */
+ public void setSelectedTabFadeStart(double selectedTabFadeStart) {
+ if ((selectedTabFadeStart < 0.0) || (selectedTabFadeStart > 1.0)) {
+ throw new IllegalArgumentException(
+ "Value for selected tab fade start should be in 0.0-1.0 range");
+ }
+ this.selectedTabFadeStart = selectedTabFadeStart;
+ }
+
+ /**
+ * Adds the specified overlay painter to the end of the list of overlay
+ * painters associated with the specified decoration area types.
+ *
+ * @param overlayPainter
+ * Overlay painter to add to the end of the list of overlay
+ * painters associated with the specified decoration area types.
+ * @param areaTypes
+ * Decoration area types.
+ */
+ public void addOverlayPainter(SubstanceOverlayPainter overlayPainter,
+ DecorationAreaType... areaTypes) {
+ for (DecorationAreaType areaType : areaTypes) {
+ if (!this.overlayPaintersMap.containsKey(areaType))
+ this.overlayPaintersMap.put(areaType,
+ new ArrayList<SubstanceOverlayPainter>());
+ this.overlayPaintersMap.get(areaType).add(overlayPainter);
+ }
+ }
+
+ /**
+ * Removes the specified overlay painter from the list of overlay painters
+ * associated with the specified decoration area types.
+ *
+ * @param overlayPainter
+ * Overlay painter to remove from the list of overlay painters
+ * associated with the specified decoration area types.
+ * @param areaTypes
+ * Decoration area types.
+ */
+ public void removeOverlayPainter(SubstanceOverlayPainter overlayPainter,
+ DecorationAreaType... areaTypes) {
+ for (DecorationAreaType areaType : areaTypes) {
+ if (!this.overlayPaintersMap.containsKey(areaType))
+ return;
+ this.overlayPaintersMap.get(areaType).remove(overlayPainter);
+ if (this.overlayPaintersMap.get(areaType).size() == 0)
+ this.overlayPaintersMap.remove(areaType);
+ }
+ }
+
+ /**
+ * Returns a non-null, non-modifiable list of overlay painters associated
+ * with the specified decoration area type.
+ *
+ * @param decorationAreaType
+ * Decoration area type.
+ * @return A non-null, non-modifiable list of overlay painters associated
+ * with the specified decoration area type.
+ */
+ public List<SubstanceOverlayPainter> getOverlayPainters(
+ DecorationAreaType decorationAreaType) {
+ if (!this.overlayPaintersMap.containsKey(decorationAreaType))
+ return Collections.emptyList();
+ return Collections.unmodifiableList(this.overlayPaintersMap
+ .get(decorationAreaType));
+ }
+
+ /**
+ * Returns the color scheme to be used for painting the specified visual
+ * area of components in the specified decoration area.
+ *
+ * @param decorationAreaType
+ * Decoration area type.
+ * @param associationKind
+ * Color scheme association kind.
+ * @param componentState
+ * Component state.
+ * @return Color scheme to be used for painting the specified visual area of
+ * components in the specified decoration area.
+ * @since version 5.3
+ */
+ public final SubstanceColorScheme getColorScheme(
+ DecorationAreaType decorationAreaType,
+ ColorSchemeAssociationKind associationKind,
+ ComponentState componentState) {
+ if (this.colorSchemeBundleMap.size() > 1) {
+ if (this.colorSchemeBundleMap.containsKey(decorationAreaType)) {
+ return this.colorSchemeBundleMap.get(decorationAreaType)
+ .getColorScheme(associationKind, componentState);
+ }
+ }
+ return this.colorSchemeBundleMap.get(DecorationAreaType.NONE)
+ .getColorScheme(associationKind, componentState);
+ }
+
+ /**
+ * Returns the color scheme to be used for painting the specified visual
+ * area of the component under the specified component state.
+ *
+ * @param comp
+ * Component.
+ * @param associationKind
+ * Color scheme association kind.
+ * @param componentState
+ * Component state.
+ * @return Color scheme to be used for painting the specified visual area of
+ * the component under the specified component state.
+ * @since version 5.1
+ */
+ public final SubstanceColorScheme getColorScheme(Component comp,
+ ColorSchemeAssociationKind associationKind,
+ ComponentState componentState) {
+ // small optimization - lookup the decoration area only if there
+ // are decoration-specific scheme bundles.
+ if (this.colorSchemeBundleMap.size() > 1) {
+ DecorationAreaType decorationAreaType = SubstanceLookAndFeel
+ .getDecorationType(comp);
+ if (this.colorSchemeBundleMap.containsKey(decorationAreaType)) {
+ return this.colorSchemeBundleMap.get(decorationAreaType)
+ .getColorScheme(associationKind, componentState);
+ }
+ }
+ return this.colorSchemeBundleMap.get(DecorationAreaType.NONE)
+ .getColorScheme(associationKind, componentState);
+ }
+
+ /**
+ * Creates a new skin that has the same settings as this skin with the
+ * addition of applying the specified color scheme transformation on all the
+ * relevant color schemes.
+ *
+ * @param transform
+ * Color scheme transformation.
+ * @param name
+ * The name of the new skin.
+ * @return The new skin.
+ */
+ public SubstanceSkin transform(ColorSchemeTransform transform,
+ final String name) {
+ SubstanceSkin result = new SubstanceSkin() {
+ @Override
+ public String getDisplayName() {
+ return name;
+ }
+ };
+ // same painters
+ result.borderPainter = this.borderPainter;
+ result.buttonShaper = this.buttonShaper;
+ result.decorationPainter = this.decorationPainter;
+ result.fillPainter = this.fillPainter;
+ result.highlightPainter = this.highlightPainter;
+ result.highlightBorderPainter = this.highlightBorderPainter;
+ // same watermark and transformed scheme
+ result.watermark = this.watermark;
+ if (this.watermarkScheme != null)
+ result.watermarkScheme = transform.transform(this.watermarkScheme);
+ // issue 428 - transform the default color scheme
+ // result.defaultColorScheme = transform
+ // .transform(this.defaultColorScheme);
+
+ // same misc settings
+ result.selectedTabFadeEnd = this.selectedTabFadeEnd;
+ result.selectedTabFadeStart = this.selectedTabFadeStart;
+
+ // transform the scheme bundles
+ if (this.colorSchemeBundleMap != null) {
+ result.colorSchemeBundleMap = new HashMap<DecorationAreaType, SubstanceColorSchemeBundle>();
+ for (Map.Entry<DecorationAreaType, SubstanceColorSchemeBundle> bundleEntry : this.colorSchemeBundleMap
+ .entrySet()) {
+ result.colorSchemeBundleMap.put(bundleEntry.getKey(),
+ bundleEntry.getValue().transform(transform));
+ }
+ }
+
+ // same set of decoration areas
+ if (this.decoratedAreaSet != null) {
+ result.decoratedAreaSet = new HashSet<DecorationAreaType>(
+ this.decoratedAreaSet);
+ }
+ // transform the background schemes
+ if (this.backgroundColorSchemeMap != null) {
+ result.backgroundColorSchemeMap = new HashMap<DecorationAreaType, SubstanceColorScheme>();
+ for (Map.Entry<DecorationAreaType, SubstanceColorScheme> entry : this.backgroundColorSchemeMap
+ .entrySet()) {
+ result.backgroundColorSchemeMap.put(entry.getKey(), transform
+ .transform(entry.getValue()));
+ }
+ }
+ // same map of overlay painters
+ result.overlayPaintersMap = new HashMap<DecorationAreaType, List<SubstanceOverlayPainter>>(
+ this.overlayPaintersMap);
+ return result;
+ }
+
+ /**
+ * Returns the background color scheme for the specified decoration area
+ * type. This method is mainly for the internal use of
+ * {@link SubstanceDecorationPainter#paintDecorationArea(Graphics2D, Component, DecorationAreaType, int, int, SubstanceSkin)}
+ * , but can be used in applications that wish to provide custom overlay
+ * background painting (such as watermarks, for example).
+ *
+ * @param decorationAreaType
+ * Decoration area type.
+ * @return The background color scheme for the specified decoration area
+ * type.
+ */
+ public final SubstanceColorScheme getBackgroundColorScheme(
+ DecorationAreaType decorationAreaType) {
+ // 1 - check the registered background scheme for this specific area
+ // type.
+ if (this.backgroundColorSchemeMap.containsKey(decorationAreaType))
+ return this.backgroundColorSchemeMap.get(decorationAreaType);
+ // 2 - check the registered scheme bundle for this specific area type.
+ if (this.colorSchemeBundleMap.containsKey(decorationAreaType)) {
+ SubstanceColorScheme registered = this.colorSchemeBundleMap.get(
+ decorationAreaType).getEnabledColorScheme();
+ if (registered != null)
+ return registered;
+ }
+ // 3 - return the background scheme for the default area type
+ return this.backgroundColorSchemeMap.get(DecorationAreaType.NONE);
+ }
+
+ /**
+ * Checks whether this skin is valid. A valid skin must have a color scheme
+ * bundle for {@link DecorationAreaType#NONE} and non-<code>null</code>
+ * button shaper, gradient painter, border painter, highlight painter and
+ * decoration painter. If call to
+ * {@link SubstanceLookAndFeel#setSkin(String)} or
+ * {@link SubstanceLookAndFeel#setSkin(SubstanceSkin)} does not seem to have
+ * any visible effect (returning <code>false</code>), call this method to
+ * verify that your skin is valid.
+ *
+ * @return <code>true</code> if this skin is valid, <code>false</code>
+ * otherwise.
+ */
+ public boolean isValid() {
+ if (!this.colorSchemeBundleMap.containsKey(DecorationAreaType.NONE))
+ return false;
+ if (this.getButtonShaper() == null) {
+ return false;
+ }
+ if (this.getFillPainter() == null) {
+ return false;
+ }
+ if (this.getBorderPainter() == null) {
+ return false;
+ }
+ if (this.getHighlightPainter() == null) {
+ return false;
+ }
+ if (this.getDecorationPainter() == null) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Contains information on color schemes loaded by the
+ * {@link SubstanceSkin#getColorSchemes(URL)} and
+ * {@link SubstanceSkin#getColorSchemes(String)} APIs. Note that the custom
+ * skins should only use the {@link #get(String)} API. The rest of the API
+ * is currently internal and is used in the <strong>Jitterbug</strong>
+ * visual editor.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class ColorSchemes {
+ /**
+ * List of color schemes of this object.
+ */
+ private List<SubstanceColorScheme> schemes;
+
+ /**
+ * Creates an object with empty list of color schemes. This method is
+ * for internal use only and should not be used in custom application
+ * skins.
+ */
+ public ColorSchemes() {
+ this.schemes = new ArrayList<SubstanceColorScheme>();
+ }
+
+ /**
+ * Creates an object based on the specified list of color schemes. This
+ * method is for internal use only and should not be used in custom
+ * application skins.
+ *
+ * @param schemes
+ * List of color schemes.
+ */
+ public ColorSchemes(List<SubstanceColorScheme> schemes) {
+ this();
+ this.schemes.addAll(schemes);
+ }
+
+ /**
+ * Returns the number of color schemes in this object. This method is
+ * for internal use only and should not be used in custom application
+ * skins.
+ *
+ * @return The number of color schemes in this object.
+ */
+ public int size() {
+ return this.schemes.size();
+ }
+
+ /**
+ * Returns the color scheme at the specified index. This method is for
+ * internal use only and should not be used in custom application skins.
+ *
+ * @param index
+ * Index.
+ * @return Color scheme at the specified index.
+ */
+ public SubstanceColorScheme get(int index) {
+ return this.schemes.get(index);
+ }
+
+ /**
+ * Returns the color scheme based on its display name. This method is
+ * the only API that is published for use in custom application skins.
+ *
+ * @param displayName
+ * Display name of a color scheme.
+ * @return The color scheme with the matching display name.
+ */
+ public SubstanceColorScheme get(String displayName) {
+ for (SubstanceColorScheme scheme : this.schemes) {
+ if (scheme.getDisplayName().equals(displayName)) {
+ return scheme;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the index of the color scheme that has the specified display
+ * name. This method is for internal use only and should not be used in
+ * custom application skins.
+ *
+ * @param displayName
+ * Display name of a color scheme.
+ * @return The index of the color scheme that has the specified display
+ * name.
+ */
+ private int indexOf(String displayName) {
+ for (int i = 0; i < this.schemes.size(); i++) {
+ SubstanceColorScheme curr = this.schemes.get(i);
+ if (curr.getDisplayName().equals(displayName)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Finds the index of the color scheme that has the specified display
+ * name and replaces it with (possibly another) color scheme. This
+ * method is for internal use only and should not be used in custom
+ * application skins.
+ *
+ * @param displayName
+ * Display name of a color scheme.
+ * @param scheme
+ * Color scheme that will replace the existing color scheme
+ * (based on the display name) at the same index in the list.
+ */
+ public void replace(String displayName, SubstanceColorScheme scheme) {
+ int index = this.indexOf(displayName);
+
+ if (index >= 0) {
+ this.schemes.remove(index);
+ this.schemes.add(index, scheme);
+ }
+ }
+
+ /**
+ * Deletes the color scheme that has the specified display name. This
+ * method is for internal use only and should not be used in custom
+ * application skins.
+ *
+ * @param displayName
+ * Display name of the color scheme to delete from the list.
+ */
+ public void delete(String displayName) {
+ int index = this.indexOf(displayName);
+ if (index >= 0) {
+ this.schemes.remove(index);
+ }
+ }
+
+ /**
+ * Adds the specified color scheme to the end of the list. This method
+ * is for internal use only and should not be used in custom application
+ * skins.
+ *
+ * @param scheme
+ * Color scheme to add to the end of the list.
+ */
+ public void add(SubstanceColorScheme scheme) {
+ this.schemes.add(scheme);
+ }
+
+ /**
+ * Moves the color scheme with the specified display name one position
+ * towards the beginning of the list. This method is for internal use
+ * only and should not be used in custom application skins.
+ *
+ * @param displayName
+ * Display name of the color scheme to move one position
+ * towards the beginning of the list.
+ */
+ public void switchWithPrevious(String displayName) {
+ int index = this.indexOf(displayName);
+
+ if (index >= 0) {
+ SubstanceColorScheme scheme = this.schemes.remove(index);
+ this.schemes.add(index - 1, scheme);
+ }
+ }
+
+ /**
+ * Moves the color scheme with the specified display name one position
+ * towards the end of the list. This method is for internal use only and
+ * should not be used in custom application skins.
+ *
+ * @param displayName
+ * Display name of the color scheme to move one position
+ * towards the end of the list.
+ */
+ public void switchWithNext(String displayName) {
+ int index = this.indexOf(displayName);
+
+ if (index >= 0) {
+ SubstanceColorScheme scheme = this.schemes.remove(index);
+ this.schemes.add(index + 1, scheme);
+ }
+ }
+ }
+
+ /**
+ * Returns the collection of color schemes in the specified URL.
+ *
+ * @param url
+ * URL that points to a resource containing the description of
+ * Substance color schemes.
+ * @return The collection of color schemes in the specified URL.
+ * @since version 5.2
+ */
+ public static ColorSchemes getColorSchemes(URL url) {
+ return SubstanceColorSchemeUtilities.getColorSchemes(url);
+ }
+
+ /**
+ * Returns the collection of color schemes in the specified URL.
+ *
+ * @param resourceName
+ * Name of the resource containing the description of Substance
+ * color schemes.
+ * @return The collection of color schemes in the specified URL.
+ * @since version 6.0
+ */
+ public static ColorSchemes getColorSchemes(String resourceName) {
+ ClassLoader cl = SubstanceCoreUtilities.getClassLoaderForResources();
+ return SubstanceColorSchemeUtilities.getColorSchemes(cl
+ .getResource(resourceName));
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/UiThreadingViolationException.java b/substance/src/main/java/org/pushingpixels/substance/api/UiThreadingViolationException.java
new file mode 100644
index 0000000..5656928
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/UiThreadingViolationException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api;
+
+/**
+ * This exception is thrown by Substance when it detects violations of UI
+ * threading rules.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class UiThreadingViolationException extends
+ org.pushingpixels.lafwidget.UiThreadingViolationException {
+ /**
+ * Creates a new instance of this exception.
+ *
+ * @param message
+ * Message.
+ */
+ public UiThreadingViolationException(String message) {
+ super(message);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/AquaColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/AquaColorScheme.java
new file mode 100644
index 0000000..cccb763
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/AquaColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Aqua</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class AquaColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(194, 224, 237);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(164, 227, 243);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(112, 206, 239);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(32, 180, 226);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(44, 47, 140);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(30, 40, 100);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Aqua</code> color scheme.
+ */
+ public AquaColorScheme() {
+ super("Aqua");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return AquaColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return AquaColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return AquaColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return AquaColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return AquaColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return AquaColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return AquaColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BarbyPinkColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BarbyPinkColorScheme.java
new file mode 100644
index 0000000..e8afeca
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BarbyPinkColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Barby pink</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BarbyPinkColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(240, 159, 242);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(239, 153, 235);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(238, 139, 230);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(231, 95, 193);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(150, 30, 101);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(111, 29, 78);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Barby Pink</code> color scheme.
+ */
+ public BarbyPinkColorScheme() {
+ super("Barby Pink");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return BarbyPinkColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return BarbyPinkColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return BarbyPinkColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return BarbyPinkColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return BarbyPinkColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return BarbyPinkColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return BarbyPinkColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BaseColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BaseColorScheme.java
new file mode 100644
index 0000000..aafe441
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BaseColorScheme.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SchemeDerivedColorsResolver;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.colorscheme.HueShiftColorScheme;
+import org.pushingpixels.substance.internal.colorscheme.InvertedColorScheme;
+import org.pushingpixels.substance.internal.colorscheme.NegatedColorScheme;
+import org.pushingpixels.substance.internal.colorscheme.SaturatedColorScheme;
+import org.pushingpixels.substance.internal.colorscheme.ShadeColorScheme;
+import org.pushingpixels.substance.internal.colorscheme.ShiftColorScheme;
+import org.pushingpixels.substance.internal.colorscheme.TintColorScheme;
+import org.pushingpixels.substance.internal.colorscheme.ToneColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Base class for <b>Substance</b> color schemes.
+ *
+ * @author kirillg
+ */
+public abstract class BaseColorScheme implements SubstanceColorScheme {
+ /**
+ * Indicates whether this color scheme is dark.
+ */
+ protected boolean isDark;
+
+ /**
+ * Display name of this color scheme.
+ */
+ protected String displayName;
+
+ /**
+ * Resolver for the derived colors.
+ */
+ protected SchemeDerivedColorsResolver derivedColorsResolver;
+
+ /**
+ * Constructs the basic functionality of a color scheme.
+ *
+ * @param displayName
+ * Display name.
+ * @param isDark
+ * Indication whether the color scheme is dark.
+ */
+ protected BaseColorScheme(String displayName, boolean isDark) {
+ this(displayName, isDark, isDark ? DerivedColorsResolverDark.INSTANCE : DerivedColorsResolverLight.INSTANCE);
+ }
+
+ /**
+ * Constructs the basic functionality of a color scheme.
+ * <p>
+ * Subclasses should typically invoke this constructor.
+ *
+ * @param displayName
+ * Display name.
+ * @param derivedColorsResolver
+ * A resolver that determine how derived colors are derived
+ * @throws NullPointerException
+ * if {@code derivedColorsResolver} is {@code null}
+ */
+ protected BaseColorScheme(String displayName, SchemeDerivedColorsResolver derivedColorsResolver) {
+ this(displayName, derivedColorsResolver.isDark(), derivedColorsResolver);
+ }
+
+ private BaseColorScheme(String displayName, boolean isDark, SchemeDerivedColorsResolver derivedColorsResolver) {
+ if (derivedColorsResolver == null) {
+ throw new NullPointerException("derivedColorsResolver cannot be null");
+ }
+
+ this.displayName = displayName;
+ this.isDark = isDark;
+ this.derivedColorsResolver = derivedColorsResolver;
+ }
+
+ /**
+ * Allows subclasses to determine the best color resolver. This is typically used by color
+ * scheme that wrap other color scheme, such as color shifting or color inversion.
+ *
+ * @param colorScheme
+ * the color scheme to test
+ * @return a resolver for the supplied color scheme
+ */
+ protected static SchemeDerivedColorsResolver getResolver(SubstanceColorScheme colorScheme) {
+ if (colorScheme instanceof BaseColorScheme) {
+ return ((BaseColorScheme) colorScheme).derivedColorsResolver;
+ }
+
+ return colorScheme.isDark() ? DerivedColorsResolverDark.INSTANCE : DerivedColorsResolverLight.INSTANCE;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.trait.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public final String getDisplayName() {
+ return this.displayName;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.SubstanceColorScheme#isDark()
+ */
+ @Override
+ public final boolean isDark() {
+ return this.isDark;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SubstanceColorScheme#shift(java.awt.Color
+ * , double, java.awt.Color, double)
+ */
+ @Override
+ public final SubstanceColorScheme shift(Color backgroundShiftColor,
+ double backgroundShiftFactor, Color foregroundShiftColor,
+ double foregroundShiftFactor) {
+ return new ShiftColorScheme(this, backgroundShiftColor,
+ backgroundShiftFactor, foregroundShiftColor,
+ foregroundShiftFactor, true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SubstanceColorScheme#shiftBackground(
+ * java.awt .Color, double)
+ */
+ @Override
+ public final SubstanceColorScheme shiftBackground(
+ Color backgroundShiftColor, double backgroundShiftFactor) {
+ return this.shift(backgroundShiftColor, backgroundShiftFactor, null,
+ 0.0);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.SubstanceColorScheme#tint(double)
+ */
+ @Override
+ public SubstanceColorScheme tint(double tintFactor) {
+ return new TintColorScheme(this, tintFactor);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.SubstanceColorScheme#tone(double)
+ */
+ @Override
+ public SubstanceColorScheme tone(double toneFactor) {
+ return new ToneColorScheme(this, toneFactor);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.SubstanceColorScheme#shade(double)
+ */
+ @Override
+ public SubstanceColorScheme shade(double shadeFactor) {
+ return new ShadeColorScheme(this, shadeFactor);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SubstanceColorScheme#saturate(double)
+ */
+ @Override
+ public SubstanceColorScheme saturate(double saturateFactor) {
+ return new SaturatedColorScheme(this, saturateFactor);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.SubstanceColorScheme#invert()
+ */
+ @Override
+ public SubstanceColorScheme invert() {
+ return new InvertedColorScheme(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.SubstanceColorScheme#negate()
+ */
+ @Override
+ public SubstanceColorScheme negate() {
+ return new NegatedColorScheme(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SubstanceColorScheme#hueShift(double)
+ */
+ @Override
+ public SubstanceColorScheme hueShift(double hueShiftFactor) {
+ return new HueShiftColorScheme(this, hueShiftFactor);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SchemeDerivedColors#getBackgroundFillColor
+ * ()
+ */
+ @Override
+ public final Color getBackgroundFillColor() {
+ return derivedColorsResolver.getBackgroundFillColor(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SchemeDerivedColors#getFocusRingColor()
+ */
+ @Override
+ public final Color getFocusRingColor() {
+ return derivedColorsResolver.getFocusRingColor(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.SchemeDerivedColors#getLineColor()
+ */
+ @Override
+ public final Color getLineColor() {
+ return derivedColorsResolver.getLineColor(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.substance.api.SchemeDerivedColors#
+ * getSelectionForegroundColor()
+ */
+ @Override
+ public final Color getSelectionForegroundColor() {
+ return derivedColorsResolver.getSelectionForegroundColor(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.substance.api.SchemeDerivedColors#
+ * getSelectionBackgroundColor()
+ */
+ @Override
+ public final Color getSelectionBackgroundColor() {
+ return derivedColorsResolver.getSelectionBackgroundColor(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SchemeDerivedColors#getWatermarkDarkColor
+ * ()
+ */
+ @Override
+ public final Color getWatermarkDarkColor() {
+ return derivedColorsResolver.getWatermarkDarkColor(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SchemeDerivedColors#getWatermarkLightColor
+ * ()
+ */
+ @Override
+ public final Color getWatermarkLightColor() {
+ return derivedColorsResolver.getWatermarkLightColor(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SchemeDerivedColors#getWatermarkStampColor
+ * ()
+ */
+ @Override
+ public final Color getWatermarkStampColor() {
+ return derivedColorsResolver.getWatermarkStampColor(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.substance.api.SchemeDerivedColors#
+ * getTextBackgroundFillColor()
+ */
+ @Override
+ public final Color getTextBackgroundFillColor() {
+ return derivedColorsResolver.getTextBackgroundFillColor(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SubstanceColorScheme#named(java.lang.
+ * String)
+ */
+ @Override
+ public final SubstanceColorScheme named(String colorSchemeDisplayName) {
+ this.displayName = colorSchemeDisplayName;
+ return this;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return this.getDisplayName() + " {\n kind="
+ + (this.isDark() ? "Dark" : "Light") + "\n colorUltraLight="
+ + SubstanceColorUtilities.encode(this.getUltraLightColor())
+ + "\n colorExtraLight="
+ + SubstanceColorUtilities.encode(this.getExtraLightColor())
+ + "\n colorLight="
+ + SubstanceColorUtilities.encode(this.getLightColor())
+ + "\n colorMid="
+ + SubstanceColorUtilities.encode(this.getMidColor())
+ + "\n colorDark="
+ + SubstanceColorUtilities.encode(this.getDarkColor())
+ + "\n colorUltraDark="
+ + SubstanceColorUtilities.encode(this.getUltraDarkColor())
+ + "\n colorForeground="
+ + SubstanceColorUtilities.encode(this.getForegroundColor())
+ + "\n}";
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BaseDarkColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BaseDarkColorScheme.java
new file mode 100644
index 0000000..c184b8a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BaseDarkColorScheme.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+/**
+ * Base class for dark color schemes.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class BaseDarkColorScheme extends BaseColorScheme {
+ /**
+ * Creates a new dark color scheme.
+ *
+ * @param displayName
+ * Display name for the color scheme.
+ */
+ protected BaseDarkColorScheme(String displayName) {
+ super(displayName, true);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BaseLightColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BaseLightColorScheme.java
new file mode 100644
index 0000000..7097cad
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BaseLightColorScheme.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+/**
+ * Base class for light color schemes.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class BaseLightColorScheme extends BaseColorScheme {
+ /**
+ * Creates a new light color scheme.
+ *
+ * @param displayName
+ * Display name for the color scheme.
+ */
+ protected BaseLightColorScheme(String displayName) {
+ super(displayName, false);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BottleGreenColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BottleGreenColorScheme.java
new file mode 100644
index 0000000..2a8e0cd
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BottleGreenColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Bottle green</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BottleGreenColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(145, 209, 131);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(115, 197, 99);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(63, 181, 59);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(6, 139, 58);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(11, 75, 38);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(0, 14, 14);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Bottle Green</code> color scheme.
+ */
+ public BottleGreenColorScheme() {
+ super("Bottle Green");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return BottleGreenColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return BottleGreenColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return BottleGreenColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return BottleGreenColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return BottleGreenColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return BottleGreenColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return BottleGreenColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BrownColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BrownColorScheme.java
new file mode 100644
index 0000000..c126987
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/BrownColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Brown</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BrownColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(240, 230, 170);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(230, 219, 142);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(217, 179, 89);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(190, 137, 27);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(162, 90, 26);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(94, 71, 57);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Brown</code> color scheme.
+ */
+ public BrownColorScheme() {
+ super("Brown");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return BrownColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return BrownColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return BrownColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return BrownColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return BrownColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return BrownColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return BrownColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/CharcoalColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/CharcoalColorScheme.java
new file mode 100644
index 0000000..29dc556
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/CharcoalColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Charcoal</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class CharcoalColorScheme extends BaseDarkColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(110, 21, 27);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(94, 27, 36);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(61, 19, 29);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(50, 20, 22);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(35, 15, 10);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(13, 8, 4);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.white;
+
+ /**
+ * Creates a new <code>Charcoal</code> color scheme.
+ */
+ public CharcoalColorScheme() {
+ super("Charcoal");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return CharcoalColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return CharcoalColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return CharcoalColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return CharcoalColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return CharcoalColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return CharcoalColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return CharcoalColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/CremeColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/CremeColorScheme.java
new file mode 100644
index 0000000..a46827d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/CremeColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Brown</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class CremeColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(254, 254, 252);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(238, 243, 230);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(235, 234, 225);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(227, 228, 219);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(179, 182, 176);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(178, 168, 153);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Creme</code> color scheme.
+ */
+ public CremeColorScheme() {
+ super("Creme");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return CremeColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return CremeColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return CremeColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return CremeColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return CremeColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return CremeColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return CremeColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DarkGrayColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DarkGrayColorScheme.java
new file mode 100644
index 0000000..5506958
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DarkGrayColorScheme.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Dark gray</b> color scheme. The primary use of this color scheme is for
+ * disabled controls of dark skins.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DarkGrayColorScheme extends BaseDarkColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraDarkColor = new Color(5, 5, 5);
+
+ /**
+ * The main extra light color.
+ */
+ private static final Color mainDarkColor = new Color(15, 15, 15);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainMidColor = new Color(30, 30, 30);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainLightColor = new Color(45, 45, 45);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainExtraLightColor = new Color(75, 75, 75);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraLightColor = new Color(155, 155, 155);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.white;
+
+ /**
+ * Creates a new <code></code> color scheme.
+ */
+ public DarkGrayColorScheme() {
+ super("Dark Gray");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return DarkGrayColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return DarkGrayColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return DarkGrayColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return DarkGrayColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return DarkGrayColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return DarkGrayColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return DarkGrayColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DarkMetallicColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DarkMetallicColorScheme.java
new file mode 100644
index 0000000..f906e1b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DarkMetallicColorScheme.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Dark metallic</b> color scheme. The primary use of this color scheme is
+ * for default controls of dark skins.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DarkMetallicColorScheme extends BaseDarkColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraDarkColor = new Color(5, 3, 0);
+
+ /**
+ * The main extra light color.
+ */
+ private static final Color mainDarkColor = new Color(15, 10, 5);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainMidColor = new Color(55, 45, 35);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainLightColor = new Color(75, 70, 65);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainExtraLightColor = new Color(90, 85, 80);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraLightColor = new Color(100, 90, 85);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.white;
+
+ /**
+ * Creates a new <code>Dark Metallic</code> color scheme.
+ */
+ public DarkMetallicColorScheme() {
+ super("Dark Metallic");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return DarkMetallicColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return DarkMetallicColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return DarkMetallicColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return DarkMetallicColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return DarkMetallicColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return DarkMetallicColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return DarkMetallicColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DarkVioletColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DarkVioletColorScheme.java
new file mode 100644
index 0000000..53d286f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DarkVioletColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Dark violet</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DarkVioletColorScheme extends BaseDarkColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(107, 22, 124);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(89, 19, 113);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(83, 17, 104);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(53, 6, 31);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(33, 1, 38);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(15, 1, 23);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.white;// new
+
+ /**
+ * Creates a new <code>Dark Violet</code> color scheme.
+ */
+ public DarkVioletColorScheme() {
+ super("Dark Violet");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return DarkVioletColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return DarkVioletColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return DarkVioletColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return DarkVioletColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return DarkVioletColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return DarkVioletColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return DarkVioletColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DerivedColorsResolverDark.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DerivedColorsResolverDark.java
new file mode 100644
index 0000000..fdd8fd2
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DerivedColorsResolverDark.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SchemeDerivedColorsResolver;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Resolver of derived colors for dark color schemes. This class is not
+ * accessible outside the package and is for internal use only.
+ *
+ * @author Kirill Grouchnikov
+ * @author Karl Schaefer (immutable redesign)
+ */
+//TODO this class should be final
+class DerivedColorsResolverDark implements SchemeDerivedColorsResolver {
+ static final DerivedColorsResolverDark INSTANCE = new DerivedColorsResolverDark();
+
+ @Override
+ public boolean isDark() {
+ return true;
+ }
+
+ @Override
+ public SchemeDerivedColorsResolver invert() {
+ return DerivedColorsResolverLight.INSTANCE;
+ }
+
+ @Override
+ public Color getWatermarkStampColor(SubstanceColorScheme colorScheme) {
+ return SubstanceColorUtilities.getAlphaColor(colorScheme
+ .getUltraLightColor(), 30);
+ }
+
+ @Override
+ public Color getWatermarkDarkColor(SubstanceColorScheme colorScheme) {
+ return colorScheme.getLightColor();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.SchemeDerivedColors#getWatermarkLightColor()
+ */
+ @Override
+ public Color getWatermarkLightColor(SubstanceColorScheme colorScheme) {
+ return colorScheme.getUltraLightColor();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.SchemeDerivedColors#getLineColor()
+ */
+ @Override
+ public Color getLineColor(SubstanceColorScheme colorScheme) {
+ return colorScheme.getMidColor();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SchemeDerivedColors#getSelectionForegroundColor()
+ */
+ @Override
+ public Color getSelectionForegroundColor(SubstanceColorScheme colorScheme) {
+ return colorScheme.getUltraDarkColor().darker();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SchemeDerivedColors#getSelectionBackgroundColor()
+ */
+ @Override
+ public Color getSelectionBackgroundColor(SubstanceColorScheme colorScheme) {
+ return colorScheme.getUltraLightColor().brighter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.SchemeDerivedColors#getBackgroundFillColor()
+ */
+ @Override
+ public Color getBackgroundFillColor(SubstanceColorScheme colorScheme) {
+ return colorScheme.getDarkColor().brighter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.SchemeDerivedColors#getFocusRingColor()
+ */
+ @Override
+ public Color getFocusRingColor(SubstanceColorScheme colorScheme) {
+ return colorScheme.getUltraDarkColor();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.SchemeDerivedColors#getTextBackgroundFillColor()
+ */
+ @Override
+ public Color getTextBackgroundFillColor(SubstanceColorScheme colorScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(colorScheme
+ .getMidColor(), colorScheme.getLightColor(), 0.4);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DerivedColorsResolverLight.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DerivedColorsResolverLight.java
new file mode 100644
index 0000000..85e5691
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DerivedColorsResolverLight.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SchemeDerivedColorsResolver;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Resolver of derived colors for light color schemes. This class is not
+ * accessible outside the package and is for internal use only.
+ *
+ * @author Kirill Grouchnikov
+ * @author Karl Schaefer (immutable redesign)
+ */
+//TODO this class should be final
+public class DerivedColorsResolverLight implements SchemeDerivedColorsResolver {
+ static final DerivedColorsResolverLight INSTANCE = new DerivedColorsResolverLight();
+
+ @Override
+ public boolean isDark() {
+ return false;
+ }
+
+ @Override
+ public SchemeDerivedColorsResolver invert() {
+ return DerivedColorsResolverDark.INSTANCE;
+ }
+
+ @Override
+ public Color getWatermarkStampColor(SubstanceColorScheme colorScheme) {
+ return SubstanceColorUtilities.getAlphaColor(colorScheme.getMidColor(),
+ 50);
+ }
+
+ @Override
+ public Color getWatermarkLightColor(SubstanceColorScheme colorScheme) {
+ return colorScheme.getLightColor();
+ }
+
+ @Override
+ public Color getWatermarkDarkColor(SubstanceColorScheme colorScheme) {
+ return SubstanceColorUtilities.getAlphaColor(
+ colorScheme.getDarkColor(), 15);
+ }
+
+ @Override
+ public Color getLineColor(SubstanceColorScheme colorScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(colorScheme
+ .getMidColor(), colorScheme.getDarkColor(), 0.7);
+ }
+
+ @Override
+ public Color getSelectionForegroundColor(SubstanceColorScheme colorScheme) {
+ return colorScheme.getUltraDarkColor().darker().darker();
+ }
+
+ @Override
+ public Color getSelectionBackgroundColor(SubstanceColorScheme colorScheme) {
+ return colorScheme.getExtraLightColor();
+ }
+
+ @Override
+ public Color getBackgroundFillColor(SubstanceColorScheme colorScheme) {
+ return colorScheme.getExtraLightColor();
+ }
+
+ @Override
+ public Color getFocusRingColor(SubstanceColorScheme colorScheme) {
+ return colorScheme.getDarkColor();
+ }
+
+ @Override
+ public Color getTextBackgroundFillColor(SubstanceColorScheme colorScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(colorScheme
+ .getUltraLightColor(), colorScheme.getExtraLightColor(), 0.8);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DesertSandColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DesertSandColorScheme.java
new file mode 100644
index 0000000..40733a9
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/DesertSandColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Desert Sand</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DesertSandColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(204, 226, 135);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(187, 204, 170);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(182, 200, 119);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(147, 157, 105);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(113, 120, 81);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(80, 96, 48);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Desert Sand</code> color scheme.
+ */
+ public DesertSandColorScheme() {
+ super("Desert Sand");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return DesertSandColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return DesertSandColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return DesertSandColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return DesertSandColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return DesertSandColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return DesertSandColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return DesertSandColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/EbonyColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/EbonyColorScheme.java
new file mode 100644
index 0000000..55c736b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/EbonyColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Ebony</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class EbonyColorScheme extends BaseDarkColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(85, 85, 85);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(75, 75, 75);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(60, 60, 60);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(40, 40, 40);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(20, 20, 20);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(10, 10, 10);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.white;
+
+ /**
+ * Creates a new <code>Ebony</code> color scheme.
+ */
+ public EbonyColorScheme() {
+ super("Ebony");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return EbonyColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return EbonyColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return EbonyColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return EbonyColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return EbonyColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return EbonyColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return EbonyColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/JadeForestColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/JadeForestColorScheme.java
new file mode 100644
index 0000000..be7eeeb
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/JadeForestColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Jade Forest</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JadeForestColorScheme extends BaseDarkColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(40, 124, 22);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(45, 113, 19);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(39, 104, 17);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(6, 53, 27);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(7, 38, 1);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(10, 23, 1);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.white;
+
+ /**
+ * Creates a new <code>Jade Forest</code> color scheme.
+ */
+ public JadeForestColorScheme() {
+ super("Jade Forest");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return JadeForestColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return JadeForestColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return JadeForestColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return JadeForestColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return JadeForestColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return JadeForestColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return JadeForestColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/LightAquaColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/LightAquaColorScheme.java
new file mode 100644
index 0000000..5a69873
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/LightAquaColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Light aqua</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LightAquaColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(215, 238, 250);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(194, 224, 237);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(164, 227, 243);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(112, 206, 239);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(32, 180, 226);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(44, 47, 140);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Light Aqua</code> color scheme.
+ */
+ public LightAquaColorScheme() {
+ super("Light Aqua");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return LightAquaColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return LightAquaColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return LightAquaColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return LightAquaColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return LightAquaColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return LightAquaColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return LightAquaColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/LightGrayColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/LightGrayColorScheme.java
new file mode 100644
index 0000000..92a88e4
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/LightGrayColorScheme.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Light gray</b> color scheme. The primary use of this color scheme is for
+ * disabled controls of light skins.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LightGrayColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(250, 251, 252);
+
+ /**
+ * The main extra light color.
+ */
+ private static final Color mainExtraLightColor = new Color(240, 242, 244);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(225, 228, 231);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(210, 214, 218);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(180, 185, 190);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(100, 106, 112);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = new Color(120, 125, 130);
+
+ /**
+ * Creates a new <code>Light Gray</code> color scheme.
+ */
+ public LightGrayColorScheme() {
+ super("Light Gray");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return LightGrayColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return LightGrayColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return LightGrayColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return LightGrayColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return LightGrayColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return LightGrayColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return LightGrayColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/LimeGreenColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/LimeGreenColorScheme.java
new file mode 100644
index 0000000..89495df
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/LimeGreenColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Lime green</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LimeGreenColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(205, 255, 85);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(172, 255, 54);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(169, 248, 57);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(117, 232, 39);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(18, 86, 0);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(8, 62, 0);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Lime Green</code> color scheme.
+ */
+ public LimeGreenColorScheme() {
+ super("Lime Green");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return LimeGreenColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return LimeGreenColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return LimeGreenColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return LimeGreenColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return LimeGreenColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return LimeGreenColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return LimeGreenColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/MetallicColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/MetallicColorScheme.java
new file mode 100644
index 0000000..739d63c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/MetallicColorScheme.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Metallic</b> color scheme. The primary use of this color scheme is for
+ * default controls of light skins.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class MetallicColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(250, 252, 255);
+
+ /**
+ * The main extra light color.
+ */
+ private static final Color mainExtraLightColor = new Color(240, 245, 250);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(200, 210, 220);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(180, 185, 190);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(80, 85, 90);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(32, 37, 42);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = new Color(15, 20, 25);
+
+ /**
+ * Creates a new <code>Metallic</code> color scheme.
+ */
+ public MetallicColorScheme() {
+ super("Metallic");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return MetallicColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return MetallicColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return MetallicColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return MetallicColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return MetallicColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return MetallicColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return MetallicColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/OliveColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/OliveColorScheme.java
new file mode 100644
index 0000000..309913d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/OliveColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Olive</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class OliveColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(205, 212, 182);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(189, 192, 165);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(175, 183, 142);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(165, 174, 129);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(135, 142, 102);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(104, 111, 67);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Olive</code> color scheme.
+ */
+ public OliveColorScheme() {
+ super("Olive");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return OliveColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return OliveColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return OliveColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return OliveColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return OliveColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return OliveColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return OliveColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/OrangeColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/OrangeColorScheme.java
new file mode 100644
index 0000000..4c080c8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/OrangeColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Orange</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class OrangeColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ public static final Color mainUltraLightColor = new Color(255, 250, 235);
+
+ /**
+ * The main light color.
+ */
+ public static final Color mainExtraLightColor = new Color(255, 220, 180);
+
+ /**
+ * The main light color.
+ */
+ public static final Color mainLightColor = new Color(245, 200, 128);
+
+ /**
+ * The main medium color.
+ */
+ public static final Color mainMidColor = new Color(240, 170, 50);
+
+ /**
+ * The main dark color.
+ */
+ public static final Color mainDarkColor = new Color(229, 151, 0);
+
+ /**
+ * The main ultra-dark color.
+ */
+ public static final Color mainUltraDarkColor = new Color(180, 100, 0);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Orange</code> color scheme.
+ */
+ public OrangeColorScheme() {
+ super("Orange");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return OrangeColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return OrangeColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return OrangeColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return OrangeColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return OrangeColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return OrangeColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return OrangeColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/PurpleColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/PurpleColorScheme.java
new file mode 100644
index 0000000..d0a778a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/PurpleColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Purple</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class PurpleColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(240, 220, 245);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(218, 209, 233);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(203, 175, 237);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(201, 135, 226);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(140, 72, 170);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(94, 39, 114);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Purple</code> color scheme.
+ */
+ public PurpleColorScheme() {
+ super("Purple");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return PurpleColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return PurpleColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return PurpleColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return PurpleColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return PurpleColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return PurpleColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return PurpleColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/RaspberryColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/RaspberryColorScheme.java
new file mode 100644
index 0000000..a376584
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/RaspberryColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Raspberry</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RaspberryColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(254, 166, 189);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(255, 152, 177);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(251, 110, 144);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(225, 52, 98);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(84, 28, 41);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(40, 0, 9);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Raspberry</code> color scheme.
+ */
+ public RaspberryColorScheme() {
+ super("Raspberry");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return RaspberryColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return RaspberryColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return RaspberryColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return RaspberryColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return RaspberryColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return RaspberryColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return RaspberryColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SepiaColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SepiaColorScheme.java
new file mode 100644
index 0000000..42f4572
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SepiaColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Sepia</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SepiaColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(220, 182, 150);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(205, 168, 135);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(195, 153, 128);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(187, 151, 102);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(157, 102, 72);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(154, 106, 84);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Sepia</code> color scheme.
+ */
+ public SepiaColorScheme() {
+ super("Sepia");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return SepiaColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return SepiaColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return SepiaColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return SepiaColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return SepiaColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return SepiaColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return SepiaColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SteelBlueColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SteelBlueColorScheme.java
new file mode 100644
index 0000000..2138079
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SteelBlueColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Steel Blue</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SteelBlueColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(149, 193, 219);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(130, 181, 212);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(118, 165, 195);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(108, 149, 178);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(38, 79, 111);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(47, 75, 99);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Steel Blue</code> color scheme.
+ */
+ public SteelBlueColorScheme() {
+ super("Steel Blue");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return SteelBlueColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return SteelBlueColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return SteelBlueColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return SteelBlueColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return SteelBlueColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return SteelBlueColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return SteelBlueColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SunGlareColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SunGlareColorScheme.java
new file mode 100644
index 0000000..efb2a10
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SunGlareColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Sun Glare</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SunGlareColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(255, 255, 209);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(248, 249, 160);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(255, 255, 80);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(252, 226, 55);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(106, 29, 0);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(67, 18, 0);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Sun Glare</code> color scheme.
+ */
+ public SunGlareColorScheme() {
+ super("Sun Glare");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return SunGlareColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return SunGlareColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return SunGlareColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return SunGlareColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return SunGlareColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return SunGlareColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return SunGlareColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SunfireRedColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SunfireRedColorScheme.java
new file mode 100644
index 0000000..571b149
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SunfireRedColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Sunfire Red</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SunfireRedColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(225, 139, 166);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(218, 110, 130);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(215, 42, 23);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(224, 20, 10);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(170, 28, 23);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(129, 23, 15);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Sunfire Red</code> color scheme.
+ */
+ public SunfireRedColorScheme() {
+ super("Sunfire Red");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return SunfireRedColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return SunfireRedColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return SunfireRedColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return SunfireRedColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return SunfireRedColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return SunfireRedColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return SunfireRedColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SunsetColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SunsetColorScheme.java
new file mode 100644
index 0000000..34a6072
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/SunsetColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Sunset</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SunsetColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(255, 196, 56);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(255, 162, 45);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(255, 137, 41);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(254, 97, 30);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(197, 19, 55);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(115, 38, 80);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Sunset</code> color scheme.
+ */
+ public SunsetColorScheme() {
+ super("Sunset");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return SunsetColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return SunsetColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return SunsetColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return SunsetColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return SunsetColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return SunsetColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return SunsetColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/TerracottaColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/TerracottaColorScheme.java
new file mode 100644
index 0000000..a2b893c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/TerracottaColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Terracotta</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TerracottaColorScheme extends BaseLightColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(250, 203, 125);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(248, 191, 114);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(239, 176, 105);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(227, 147, 88);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(195, 113, 63);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(163, 87, 64);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.black;
+
+ /**
+ * Creates a new <code>Terracotta</code> color scheme.
+ */
+ public TerracottaColorScheme() {
+ super("Terracotta");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return TerracottaColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return TerracottaColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return TerracottaColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return TerracottaColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return TerracottaColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return TerracottaColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return TerracottaColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/UltramarineColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/UltramarineColorScheme.java
new file mode 100644
index 0000000..2f5a320
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/colorscheme/UltramarineColorScheme.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.colorscheme;
+
+import java.awt.Color;
+
+/**
+ * <b>Ultramarine</b> color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class UltramarineColorScheme extends BaseDarkColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private static final Color mainUltraLightColor = new Color(46, 22, 124);
+
+ /**
+ * The main extra-light color.
+ */
+ private static final Color mainExtraLightColor = new Color(33, 19, 113);
+
+ /**
+ * The main light color.
+ */
+ private static final Color mainLightColor = new Color(31, 17, 104);
+
+ /**
+ * The main medium color.
+ */
+ private static final Color mainMidColor = new Color(47, 6, 53);
+
+ /**
+ * The main dark color.
+ */
+ private static final Color mainDarkColor = new Color(11, 1, 38);
+
+ /**
+ * The main ultra-dark color.
+ */
+ private static final Color mainUltraDarkColor = new Color(2, 1, 23);
+
+ /**
+ * The foreground color.
+ */
+ private static final Color foregroundColor = Color.white;
+
+ /**
+ * Creates a new <code>Ultramarine</code> color scheme.
+ */
+ public UltramarineColorScheme() {
+ super("Ultramarine");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return UltramarineColorScheme.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return UltramarineColorScheme.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return UltramarineColorScheme.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return UltramarineColorScheme.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return UltramarineColorScheme.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return UltramarineColorScheme.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return UltramarineColorScheme.mainUltraDarkColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/combo/ComboPopupPrototypeCallback.java b/substance/src/main/java/org/pushingpixels/substance/api/combo/ComboPopupPrototypeCallback.java
new file mode 100644
index 0000000..aef893b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/combo/ComboPopupPrototypeCallback.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.combo;
+
+import javax.swing.JComboBox;
+
+/**
+ * Interface for specifying a prototype entry for a combo popup. This class is
+ * part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface ComboPopupPrototypeCallback {
+ /**
+ * Returns the prototype entry for combo popup of the specified combobox.
+ *
+ * @param jc
+ * Combo box.
+ * @return Prototype entry for combo popup of the specified combobox.
+ */
+ public Object getPopupPrototypeDisplayValue(JComboBox jc);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/combo/WidestComboPopupPrototype.java b/substance/src/main/java/org/pushingpixels/substance/api/combo/WidestComboPopupPrototype.java
new file mode 100644
index 0000000..d200f5f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/combo/WidestComboPopupPrototype.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.combo;
+
+import java.awt.Component;
+
+import javax.swing.JComboBox;
+import javax.swing.JList;
+
+import org.pushingpixels.substance.internal.ui.SubstanceComboBoxUI;
+
+/**
+ * Sample core implementation of {@link ComboPopupPrototypeCallback} interface
+ * that returns the widest combo entry. This class is part of officially
+ * supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class WidestComboPopupPrototype implements ComboPopupPrototypeCallback {
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.substance.combo.ComboPopupPrototypeCallback#
+ * getPopupPrototypeDisplayValue(javax.swing.JComboBox)
+ */
+ @Override
+ public Object getPopupPrototypeDisplayValue(JComboBox jc) {
+ int maxWidth = -1;
+ Object prototype = null;
+ JList list = ((SubstanceComboBoxUI) jc.getUI()).getPopup().getList();
+ for (int i = 0; i < jc.getModel().getSize(); i++) {
+ Object elem = jc.getModel().getElementAt(i);
+ Component renderer = jc.getRenderer().getListCellRendererComponent(
+ list, elem, i, false, false);
+ if (renderer != null) {
+ int rWidth = renderer.getPreferredSize().width;
+ if (rWidth > maxWidth) {
+ maxWidth = rWidth;
+ prototype = elem;
+ }
+ }
+ }
+
+ return prototype;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/fonts/FontPolicy.java b/substance/src/main/java/org/pushingpixels/substance/api/fonts/FontPolicy.java
new file mode 100644
index 0000000..5987728
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/fonts/FontPolicy.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2001-2006 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.substance.api.fonts;
+
+import javax.swing.UIDefaults;
+
+import org.pushingpixels.substance.internal.fonts.FontPolicies;
+
+/**
+ * Looks up and returns a FontSet.
+ *
+ * @author Karsten Lentzsch
+ *
+ * @see FontSet
+ * @see FontPolicies
+ *
+ * @since 2.0
+ */
+public interface FontPolicy {
+
+ /**
+ * Looks up and returns a set of fonts that will be used by a Look&Feel
+ * to set the default fonts for its components.
+ * <p>
+ *
+ * This method is invoked during the L&F component initialization. And
+ * the invoker hands over the UIDefaults object used to define the component
+ * settings. Hence, the UIDefaults object may be used to look up a font as
+ * initialized by a super Look&Feel. For example the JGoodies Windows
+ * L&F could use the defaults set by the super L&F, the Sun Windows
+ * L&F.
+ *
+ * @param lafName
+ * the name of the Look&Feel that requests the fonts
+ * @param table
+ * the UIDefaults table that can be used to look up fonts of a
+ * super L&F
+ *
+ * @return a set of fonts used as default for the component.
+ */
+ FontSet getFontSet(String lafName, UIDefaults table);
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/fonts/FontSet.java b/substance/src/main/java/org/pushingpixels/substance/api/fonts/FontSet.java
new file mode 100644
index 0000000..a204161
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/fonts/FontSet.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2001-2006 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.substance.api.fonts;
+
+import javax.swing.plaf.FontUIResource;
+
+/**
+ * Returns the Fonts used by a Look&Feel or theme. These Fonts must
+ * implement the UIResource marker interface.
+ *
+ * @author Karsten Lentzsch
+ *
+ * @since 2.0
+ */
+public interface FontSet {
+
+ /**
+ * Returns the font used for all dialog components.
+ *
+ * @return the font used for all dialog components.
+ */
+ FontUIResource getControlFont();
+
+ /**
+ * Returns the font used for the menu.
+ *
+ * @return the font used for the menu.
+ */
+ FontUIResource getMenuFont();
+
+ /**
+ * Returns the font used for the title label in TitledBorders. This font is
+ * also used by JGoodies Forms titles, and titled separators.
+ *
+ * @return the font used for TitledBorder titles.
+ */
+ FontUIResource getTitleFont();
+
+ /**
+ * Returns the font used for internal frame titles.
+ *
+ * @return the font used for internal frame titles.
+ */
+ FontUIResource getWindowTitleFont();
+
+ /**
+ * Returns the font used for tool tips.
+ *
+ * @return the tool tip font.
+ */
+ FontUIResource getSmallFont();
+
+ /**
+ * Returns the font used for message dialogs.
+ *
+ * @return the font used for message dialogs.
+ */
+ FontUIResource getMessageFont();
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/fonts/SubstanceFontUtilities.java b/substance/src/main/java/org/pushingpixels/substance/api/fonts/SubstanceFontUtilities.java
new file mode 100644
index 0000000..efd7360
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/fonts/SubstanceFontUtilities.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.fonts;
+
+import java.awt.Font;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.swing.UIDefaults;
+import javax.swing.plaf.FontUIResource;
+
+import org.pushingpixels.lafwidget.utils.LookUtils;
+import org.pushingpixels.substance.internal.fonts.*;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+/**
+ * Font-related utilities.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceFontUtilities {
+ /**
+ * Font set implementation for Substance. This is used to make the window
+ * title font bold.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class SubstanceFontSet implements FontSet {
+ /**
+ * The default system font set.
+ */
+ private FontSet systemFontSet;
+
+ /**
+ * Creates a new font set for Substance.
+ *
+ * @param systemFontSet
+ * The default system font set.
+ */
+ public SubstanceFontSet(FontSet systemFontSet) {
+ this.systemFontSet = systemFontSet;
+ }
+
+ /**
+ * Returns Substance-specific font resource.
+ *
+ * @param systemFont
+ * The default system font.
+ * @return Substance-specific font resource.
+ */
+ private FontUIResource getSubstanceFont(FontUIResource systemFont) {
+ return systemFont;
+ }
+
+ /**
+ * Returns Substance-specific font resource.
+ *
+ * @param systemFont
+ * The default system font.
+ * @param toBoldify
+ * If <code>true</code>, the original font (the first
+ * parameter) is boldified.
+ * @param extraFontSize
+ * Extra font size in pixels.
+ * @return Substance-specific font resource.
+ */
+ private FontUIResource getSubstanceFont(FontUIResource systemFont,
+ boolean toBoldify, int extraFontSize) {
+ boolean isOrigItalic = systemFont.isItalic();
+ int newStyle = systemFont.getStyle();
+ if (toBoldify) {
+ if (isOrigItalic)
+ newStyle = Font.ITALIC + Font.BOLD;
+ else
+ newStyle = Font.BOLD;
+ }
+ return new FontUIResource(systemFont.deriveFont(
+ (float) (systemFont.getSize() + extraFontSize)).deriveFont(
+ newStyle));
+ // return new FontUIResource(systemFont.getFontName(), newStyle,
+ // systemFont.getSize() + extraFontSize);
+ }
+
+ @Override
+ public FontUIResource getControlFont() {
+ return this.getSubstanceFont(this.systemFontSet.getControlFont());
+ }
+
+ @Override
+ public FontUIResource getMenuFont() {
+ return this.getSubstanceFont(this.systemFontSet.getMenuFont());
+ }
+
+ @Override
+ public FontUIResource getMessageFont() {
+ return this.getSubstanceFont(this.systemFontSet.getMessageFont());
+ }
+
+ @Override
+ public FontUIResource getSmallFont() {
+ return this.getSubstanceFont(this.systemFontSet.getSmallFont(),
+ false, 1);
+ }
+
+ @Override
+ public FontUIResource getTitleFont() {
+ return this.getSubstanceFont(this.systemFontSet.getTitleFont());
+ }
+
+ @Override
+ public FontUIResource getWindowTitleFont() {
+ return this.getSubstanceFont(this.systemFontSet
+ .getWindowTitleFont(), true, 1);
+ }
+ }
+
+ /**
+ * Returns the default platform-specific font policy.
+ *
+ * @return Default platform-specific font policy.
+ */
+ public static FontPolicy getDefaultFontPolicy() {
+ // boolean toWrapPolicy = !LookUtils.IS_OS_MAC;
+ FontPolicy defaultPolicy = FontPolicies.getDefaultPlasticPolicy();
+ boolean isKDE = false;
+ // boolean isGnome = false;
+ try {
+ isKDE = DefaultKDEFontPolicy.isKDERunning();
+ } catch (Throwable t) {
+ // security access - too bad for KDE desktops.
+ }
+ if (LookUtils.IS_OS_WINDOWS) {
+ defaultPolicy = FontPolicies.getDefaultWindowsPolicy();
+ } else {
+ if (LookUtils.IS_OS_MAC) {
+ defaultPolicy = new DefaultMacFontPolicy();
+ } else {
+ if (isKDE) {
+ // new in version 4.2
+ defaultPolicy = new DefaultKDEFontPolicy();
+ } else {
+ try {
+ String desktop = AccessController
+ .doPrivileged(new PrivilegedAction<String>() {
+ @Override
+ public String run() {
+ return System
+ .getProperty("sun.desktop");
+ }
+ });
+ if ("gnome".equals(desktop)) {
+ // new in version 4.1
+ defaultPolicy = new DefaultGnomeFontPolicy();
+ // isGnome = true;
+ }
+ } catch (Throwable t) {
+ // security access - too bad for Gnome desktops.
+ }
+ }
+ }
+ }
+
+ // System.out.println("System " + System.getProperty("os.name")
+ // + ", policy " + defaultPolicy.getClass().getName());
+ //
+ SubstanceSizeUtils.resetPointsToPixelsRatio(defaultPolicy);
+ final FontPolicy fontPolicy = FontPolicies
+ .customSettingsPolicy(defaultPolicy);
+ if (LookUtils.IS_OS_MAC || isKDE)// || isGnome)
+ return fontPolicy;
+ return new FontPolicy() {
+ @Override
+ public FontSet getFontSet(String lafName, UIDefaults table) {
+ FontSet baseResult = fontPolicy.getFontSet(lafName, table);
+ FontSet substanceFontSet = new SubstanceFontSet(baseResult);
+ return substanceFontSet;
+ }
+ };
+ }
+
+ /**
+ * Returns scaled platform-specific font policy.
+ *
+ * @param scaleFactor
+ * Scale factor. Should be positive.
+ * @return Scaled platform-specific font policy.
+ */
+ public static FontPolicy getScaledFontPolicy(final float scaleFactor) {
+ final FontSet substanceCoreFontSet = SubstanceFontUtilities
+ .getDefaultFontPolicy().getFontSet("Substance", null);
+ // Create the scaled font set
+ FontPolicy newFontPolicy = new FontPolicy() {
+ @Override
+ public FontSet getFontSet(String lafName, UIDefaults table) {
+ return new ScaledFontSet(substanceCoreFontSet, scaleFactor);
+ }
+ };
+ return newFontPolicy;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/inputmaps/InputMapSet.java b/substance/src/main/java/org/pushingpixels/substance/api/inputmaps/InputMapSet.java
new file mode 100644
index 0000000..2aba898
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/inputmaps/InputMapSet.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.inputmaps;
+
+public interface InputMapSet {
+ public SubstanceInputMap getButtonFocusInputMap();
+
+ public SubstanceInputMap getCheckBoxFocusInputMap();
+
+ public SubstanceInputMap getComboBoxAncestorInputMap();
+
+ public SubstanceInputMap getDesktopAncestorInputMap();
+
+ public SubstanceInputMap getEditorPaneFocusInputMap();
+
+ public SubstanceInputMap getFileChooserAncestorInputMap();
+
+ public SubstanceInputMap getFormattedTextFieldFocusInputMap();
+
+ public SubstanceInputMap getListFocusInputMap();
+
+ public SubstanceInputMap getPasswordFieldFocusInputMap();
+
+ public SubstanceInputMap getRadioButtonFocusInputMap();
+
+ public SubstanceInputMap getRootPaneAncestorInputMap();
+
+ public SubstanceInputMap getScrollBarAncestorInputMap();
+
+ public SubstanceInputMap getScrollPaneAncestorInputMap();
+
+ public SubstanceInputMap getSliderFocusInputMap();
+
+ public SubstanceInputMap getSpinnerAncestorInputMap();
+
+ public SubstanceInputMap getSplitPaneAncestorInputMap();
+
+ public SubstanceInputMap getTabbedPaneAncestorInputMap();
+
+ public SubstanceInputMap getTabbedPaneFocusInputMap();
+
+ public SubstanceInputMap getTableAncestorInputMap();
+
+ public SubstanceInputMap getTableHeaderAncestorInputMap();
+
+ public SubstanceInputMap getTextAreaFocusInputMap();
+
+ public SubstanceInputMap getTextFieldFocusInputMap();
+
+ public SubstanceInputMap getTextPaneFocusInputMap();
+
+ public SubstanceInputMap getToggleButtonFocusInputMap();
+
+ public SubstanceInputMap getToolBarAncestorInputMap();
+
+ public SubstanceInputMap getTreeAncestorInputMap();
+
+ public SubstanceInputMap getTreeFocusInputMap();
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/inputmaps/SubstanceInputMap.java b/substance/src/main/java/org/pushingpixels/substance/api/inputmaps/SubstanceInputMap.java
new file mode 100644
index 0000000..46550ed
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/inputmaps/SubstanceInputMap.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.inputmaps;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.swing.UIDefaults.LazyInputMap;
+
+public final class SubstanceInputMap {
+ private Map<String, String> mapping;
+
+ public SubstanceInputMap() {
+ this.mapping = new TreeMap<String, String>();
+ }
+
+ public void put(String keyStroke, String actionName) {
+ this.mapping.put(keyStroke, actionName);
+ }
+
+ public void remove(String keyStroke) {
+ this.mapping.remove(keyStroke);
+ }
+
+ public LazyInputMap getUiMap() {
+ Object[] flat = new Object[2 * this.mapping.size()];
+ int index = 0;
+ for (Map.Entry<String, String> entry : this.mapping.entrySet()) {
+ flat[index++] = entry.getKey();
+ flat[index++] = entry.getValue();
+ }
+ return new LazyInputMap(flat);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/inputmaps/SubstanceInputMapUtilities.java b/substance/src/main/java/org/pushingpixels/substance/api/inputmaps/SubstanceInputMapUtilities.java
new file mode 100644
index 0000000..aabce0f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/inputmaps/SubstanceInputMapUtilities.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.inputmaps;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.pushingpixels.lafwidget.utils.LookUtils;
+import org.pushingpixels.substance.internal.inputmaps.*;
+
+public class SubstanceInputMapUtilities {
+ public static InputMapSet getSystemInputMapSet() {
+ if (LookUtils.IS_OS_MAC) {
+ return new AquaInputMapSet();
+ }
+ if (LookUtils.IS_OS_WINDOWS) {
+ return new WindowsInputMapSet();
+ }
+ try {
+ String desktop = AccessController
+ .doPrivileged(new PrivilegedAction<String>() {
+ @Override
+ public String run() {
+ return System.getProperty("sun.desktop");
+ }
+ });
+ if ("gnome".equals(desktop)) {
+ return new GnomeInputMapSet();
+ }
+ } catch (Throwable t) {
+ // security access - too bad for Gnome desktops.
+ }
+
+ return getCrossPlatformInputMapSet();
+ }
+
+ public static InputMapSet getCrossPlatformInputMapSet() {
+ return new BaseInputMapSet();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/FractionBasedPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/FractionBasedPainter.java
new file mode 100644
index 0000000..490d689
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/FractionBasedPainter.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter;
+
+import org.pushingpixels.substance.api.ColorSchemeSingleColorQuery;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.trait.SubstanceTrait;
+
+/**
+ * Base painter with fraction-based stops and a color query associated with each
+ * stop. This class allows creating multi-stop gradients with exact control over
+ * which color is used at every gradient control point.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class FractionBasedPainter implements SubstanceTrait {
+ /**
+ * The display name of this painter.
+ */
+ private String displayName;
+
+ /**
+ * The fractions of this painter. If the constructor has not thrown an
+ * {@link IllegalArgumentException}, the entries in this array are strictly
+ * increasing, starting from 0.0 and ending at 1.0.
+ */
+ protected float[] fractions;
+
+ /**
+ * The color queries of this painter. Each entry in this array corresponds
+ * to the matching index in the {@link #fractions}, specifying which color
+ * will be used at the relevant gradient control point. If the constructor
+ * has not thrown an {@link IllegalArgumentException}, the size of this
+ * array is identical to the size of {@link #fractions}, and there are no
+ * <code>null</code> entries in this array. Note that the application code
+ * can still cause an exception at runtime by throwing it in the
+ * implementation of the
+ * {@link ColorSchemeSingleColorQuery#query(SubstanceColorScheme)} method.
+ */
+ protected ColorSchemeSingleColorQuery[] colorQueries;
+
+ /**
+ * Creates a new fraction-based border painter.
+ *
+ * @param displayName
+ * The display name of this painter.
+ * @param fractions
+ * The fractions of this painter. Must be strictly increasing,
+ * starting from 0.0 and ending at 1.0.
+ * @param colorQueries
+ * The color queries of this painter. Must have the same size as
+ * the fractions array, and all entries must be non-
+ * <code>null</code>.
+ */
+ public FractionBasedPainter(String displayName, float[] fractions,
+ ColorSchemeSingleColorQuery[] colorQueries) {
+ this.displayName = displayName;
+ if ((fractions == null) || (colorQueries == null)) {
+ throw new IllegalArgumentException("Cannot pass null arguments");
+ }
+ if (fractions.length != colorQueries.length) {
+ throw new IllegalArgumentException("Argument length does not match");
+ }
+ int length = fractions.length;
+ if ((fractions[0] != 0.0f) || (fractions[length - 1] != 1.0f)) {
+ throw new IllegalArgumentException(
+ "End fractions must be 0.0 and 1.0");
+ }
+ for (int i = 0; i < length - 1; i++) {
+ if (fractions[i + 1] <= fractions[i]) {
+ throw new IllegalArgumentException(
+ "Fractions must be strictly increasing");
+ }
+ }
+ for (int i = 0; i < length; i++) {
+ if (colorQueries[i] == null) {
+ throw new IllegalArgumentException("Cannot pass null query");
+ }
+ }
+ this.colorQueries = new ColorSchemeSingleColorQuery[length];
+ System.arraycopy(colorQueries, 0, this.colorQueries, 0, length);
+ this.fractions = new float[length];
+ System.arraycopy(fractions, 0, this.fractions, 0, length);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return this.displayName;
+ }
+
+ /**
+ * Returns the fractions of this painter.
+ *
+ * @return Fractions of this painter.
+ */
+ public float[] getFractions() {
+ float[] result = new float[this.fractions.length];
+ System.arraycopy(this.fractions, 0, result, 0, this.fractions.length);
+ return result;
+ }
+
+ /**
+ * Returns the color queries of this painter.
+ *
+ * @return Color queries of this painter.
+ */
+ public ColorSchemeSingleColorQuery[] getColorQueries() {
+ ColorSchemeSingleColorQuery[] result = new ColorSchemeSingleColorQuery[this.colorQueries.length];
+ System.arraycopy(this.colorQueries, 0, result, 0,
+ this.colorQueries.length);
+ return result;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/SubstancePainterUtils.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/SubstancePainterUtils.java
new file mode 100644
index 0000000..b151eaa
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/SubstancePainterUtils.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter;
+
+import java.awt.Component;
+import java.awt.Point;
+
+import javax.swing.*;
+
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+public class SubstancePainterUtils {
+ public static Point getOffsetInRootPaneCoords(Component comp) {
+ JRootPane rootPane = SwingUtilities.getRootPane(comp);
+ int dx = 0;
+ int dy = 0;
+ JComponent titlePane = null;
+
+ if (rootPane != null) {
+ titlePane = SubstanceCoreUtilities.getTitlePane(rootPane);
+
+ if (titlePane != null) {
+ if (comp.isShowing() && titlePane.isShowing()) {
+ dx += (comp.getLocationOnScreen().x - titlePane
+ .getLocationOnScreen().x);
+ dy += (comp.getLocationOnScreen().y - titlePane
+ .getLocationOnScreen().y);
+ } else {
+ // have to traverse the hierarchy
+ Component c = comp;
+ dx = 0;
+ dy = 0;
+ while (c != rootPane) {
+ dx += c.getX();
+ dy += c.getY();
+ c = c.getParent();
+ }
+ c = titlePane;
+ if ((c != null) && (c.getParent() != null)) {
+ while (c != rootPane) {
+ dx -= c.getX();
+ dy -= c.getY();
+ c = c.getParent();
+ }
+ }
+ }
+ }
+ }
+
+ return new Point(dx, dy);
+ }
+
+ public static Component getTopMostParentWithDecorationAreaType(
+ Component comp, DecorationAreaType type) {
+ Component c = comp;
+ Component topMostWithSameDecorationAreaType = c;
+ while (c != null) {
+ if (DecorationPainterUtils.getImmediateDecorationType(c) == type) {
+ topMostWithSameDecorationAreaType = c;
+ }
+ c = c.getParent();
+ }
+ return topMostWithSameDecorationAreaType;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/border/ClassicBorderPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/ClassicBorderPainter.java
new file mode 100644
index 0000000..05ccd89
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/ClassicBorderPainter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.border;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Border painter that returns images with classic appearance. This class is
+ * part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ClassicBorderPainter extends StandardBorderPainter {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.painter.border.StandardBorderPainter#
+ * getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return "Classic";
+ }
+
+ @Override
+ public Color getTopBorderColor(SubstanceColorScheme borderScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(super
+ .getTopBorderColor(borderScheme), super
+ .getMidBorderColor(borderScheme), 0.0);
+ }
+
+ @Override
+ public Color getMidBorderColor(SubstanceColorScheme borderScheme) {
+ return this.getTopBorderColor(borderScheme);
+ }
+
+ @Override
+ public Color getBottomBorderColor(SubstanceColorScheme borderScheme) {
+ return this.getTopBorderColor(borderScheme);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/border/CompositeBorderPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/CompositeBorderPainter.java
new file mode 100644
index 0000000..8c759dc
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/CompositeBorderPainter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.border;
+
+import java.awt.*;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+
+/**
+ * Composite border painter that delegates the painting of outer and inner
+ * contours.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.2
+ */
+public class CompositeBorderPainter implements SubstanceBorderPainter {
+ /**
+ * Display name of this border painter.
+ */
+ private String displayName;
+
+ /**
+ * Delegate painter for painting the inner contrours.
+ */
+ private SubstanceBorderPainter inner;
+
+ /**
+ * Delegate painter for painting the outer contours.
+ */
+ private SubstanceBorderPainter outer;
+
+ /**
+ * Creates a new composite border painter.
+ *
+ * @param displayName
+ * Display name.
+ * @param outer
+ * Delegate painter for painting the outer contours.
+ * @param inner
+ * Delegate painter for painting the inner contrours.
+ */
+ public CompositeBorderPainter(String displayName,
+ SubstanceBorderPainter outer, SubstanceBorderPainter inner) {
+ this.displayName = displayName;
+ this.outer = outer;
+ this.inner = inner;
+ }
+
+ @Override
+ public boolean isPaintingInnerContour() {
+ return true;
+ }
+
+ @Override
+ public void paintBorder(Graphics g, Component c, int width, int height,
+ Shape contour, Shape innerContour, SubstanceColorScheme borderScheme) {
+ if (innerContour != null) {
+ this.inner.paintBorder(g, c, width, height, innerContour, null,
+ borderScheme);
+ }
+ if (contour != null) {
+ this.outer.paintBorder(g, c, width, height, contour, null,
+ borderScheme);
+ }
+ }
+
+ @Override
+ public String getDisplayName() {
+ return this.displayName;
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/border/DelegateBorderPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/DelegateBorderPainter.java
new file mode 100644
index 0000000..f97a75c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/DelegateBorderPainter.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.border;
+
+import java.awt.*;
+
+import org.pushingpixels.substance.api.ColorSchemeTransform;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Delegate border painter that allows tweaking the visual appearance of
+ * borders.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DelegateBorderPainter extends StandardBorderPainter {
+ /**
+ * Display name of this border painter.
+ */
+ protected String displayName;
+
+ /**
+ * The delegate border painter.
+ */
+ protected StandardBorderPainter delegate;
+
+ /**
+ * 8-digit hexadecimal mask applied on the top color painted by
+ * {@link #delegate}. Can be used to apply custom translucency. For example,
+ * value 0x80FFFFFF will result in 50% translucency of the original top
+ * border color.
+ */
+ protected int topMask;
+
+ /**
+ * 8-digit hexadecimal mask applied on the middle color painted by
+ * {@link #delegate}. Can be used to apply custom translucency. For example,
+ * value 0x80FFFFFF will result in 50% translucency of the original middle
+ * border color.
+ */
+ protected int midMask;
+
+ /**
+ * 8-digit hexadecimal mask applied on the bottom color painted by
+ * {@link #delegate}. Can be used to apply custom translucency. For example,
+ * value 0x80FFFFFF will result in 50% translucency of the original bottom
+ * border color.
+ */
+ protected int bottomMask;
+
+ /**
+ * Transformation to be applied on the color schemes prior to compute the
+ * colors to be used for border painting.
+ */
+ protected ColorSchemeTransform transform;
+
+ /**
+ * Creates a new delegate border painter
+ *
+ * @param displayName
+ * Display name of this border painter.
+ * @param delegate
+ * The delegate border painter.
+ * @param transform
+ * Transformation to be applied on the color schemes prior to
+ * compute the colors to be used for border painting.
+ */
+ public DelegateBorderPainter(String displayName,
+ StandardBorderPainter delegate, ColorSchemeTransform transform) {
+ this(displayName, delegate, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+ transform);
+ }
+
+ /**
+ * Creates a new delegate border painter
+ *
+ * @param displayName
+ * Display name of this border painter.
+ * @param delegate
+ * The delegate border painter.
+ * @param topMask
+ * 8-digit hexadecimal mask applied on the top color painted by
+ * the <code>delegate</code>.
+ * @param midMask
+ * 8-digit hexadecimal mask applied on the middle color painted
+ * by the <code>delegate</code>.
+ * @param bottomMask
+ * 8-digit hexadecimal mask applied on the bottom color painted
+ * by the <code>delegate</code>.
+ * @param transform
+ * Transformation to be applied on the color schemes prior to
+ * compute the colors to be used for border painting.
+ */
+ public DelegateBorderPainter(String displayName,
+ StandardBorderPainter delegate, int topMask, int midMask,
+ int bottomMask, ColorSchemeTransform transform) {
+ this.displayName = displayName;
+ this.delegate = delegate;
+ this.topMask = topMask;
+ this.midMask = midMask;
+ this.bottomMask = bottomMask;
+ this.transform = transform;
+ }
+
+ /**
+ * Map of transformed color schemes (to speed up the subsequent lookups).
+ */
+ protected final static LazyResettableHashMap<SubstanceColorScheme> transformMap = new LazyResettableHashMap<SubstanceColorScheme>(
+ "DelegateBorderPainter");
+
+ @Override
+ public Color getTopBorderColor(SubstanceColorScheme borderScheme) {
+ return new Color(this.topMask
+ & this.delegate.getTopBorderColor(borderScheme).getRGB(), true);
+ }
+
+ @Override
+ public Color getMidBorderColor(SubstanceColorScheme borderScheme) {
+ return new Color(this.midMask
+ & this.delegate.getMidBorderColor(borderScheme).getRGB(), true);
+ }
+
+ @Override
+ public Color getBottomBorderColor(SubstanceColorScheme borderScheme) {
+ return new Color(this.bottomMask
+ & this.delegate.getBottomBorderColor(borderScheme).getRGB(),
+ true);
+ }
+
+ @Override
+ public void paintBorder(Graphics g, Component c, int width, int height,
+ Shape contour, Shape innerContour, SubstanceColorScheme borderScheme) {
+ super.paintBorder(g, c, width, height, contour, innerContour,
+ getShiftScheme(borderScheme));
+ }
+
+ @Override
+ public String getDisplayName() {
+ return this.displayName;
+ }
+
+ /**
+ * Retrieves a transformed color scheme.
+ *
+ * @param orig
+ * Original color scheme.
+ * @return Transformed color scheme.
+ */
+ private SubstanceColorScheme getShiftScheme(SubstanceColorScheme orig) {
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(orig
+ .getDisplayName(), this.getDisplayName(), this.transform);
+ SubstanceColorScheme result = transformMap.get(key);
+ if (result == null) {
+ result = this.transform.transform(orig);
+ transformMap.put(key, result);
+ }
+ return result;
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/border/DelegateFractionBasedBorderPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/DelegateFractionBasedBorderPainter.java
new file mode 100644
index 0000000..74a8c54
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/DelegateFractionBasedBorderPainter.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.border;
+
+import java.awt.*;
+import java.awt.MultipleGradientPaint.CycleMethod;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Delegate border painter that allows tweaking the visual appearance of
+ * borders.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DelegateFractionBasedBorderPainter implements
+ SubstanceBorderPainter {
+ /**
+ * Display name of this border painter.
+ */
+ protected String displayName;
+
+ /**
+ * The delegate border painter.
+ */
+ protected FractionBasedBorderPainter delegate;
+
+ /**
+ * 8-digit hexadecimal masks applied on the colors painted by
+ * {@link #delegate}. Can be used to apply custom translucency. For example,
+ * value 0x80FFFFFF will result in 50% translucency of the original border
+ * color.
+ */
+ protected int[] masks;
+
+ /**
+ * Transformation to be applied on the color schemes prior to compute the
+ * colors to be used for border painting.
+ */
+ protected ColorSchemeTransform transform;
+
+ /**
+ * Creates a new delegate border painter
+ *
+ * @param displayName
+ * Display name of this border painter.
+ * @param delegate
+ * The delegate border painter.
+ * @param masks
+ * Array of 8-digit hexadecimal masks applied on the relevant
+ * colors painted by the <code>delegate</code>.
+ * @param transform
+ * Transformation to be applied on the color schemes prior to
+ * compute the colors to be used for border painting.
+ */
+ public DelegateFractionBasedBorderPainter(String displayName,
+ FractionBasedBorderPainter delegate, int[] masks,
+ ColorSchemeTransform transform) {
+ this.displayName = displayName;
+ this.delegate = delegate;
+ this.masks = new int[masks.length];
+ System.arraycopy(masks, 0, this.masks, 0, masks.length);
+ this.transform = transform;
+ }
+
+ /**
+ * Map of transformed color schemes (to speed up the subsequent lookups).
+ */
+ protected final static LazyResettableHashMap<SubstanceColorScheme> transformMap = new LazyResettableHashMap<SubstanceColorScheme>(
+ "DelegateBorderPainter");
+
+ @Override
+ public boolean isPaintingInnerContour() {
+ return false;
+ }
+
+ @Override
+ public void paintBorder(Graphics g, Component c, int width, int height,
+ Shape contour, Shape innerContour, SubstanceColorScheme borderScheme) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_NORMALIZE);
+
+ // shift schemes
+ SubstanceColorScheme scheme = getShiftScheme(borderScheme);
+
+ float[] fractions = delegate.getFractions();
+ ColorSchemeSingleColorQuery[] colorQueries = delegate.getColorQueries();
+ Color[] fillColors = new Color[fractions.length];
+ for (int i = 0; i < fractions.length; i++) {
+ ColorSchemeSingleColorQuery colorQuery = colorQueries[i];
+ Color color = colorQuery.query(scheme);
+ // apply masks
+ color = new Color(this.masks[i] & color.getRGB(), true);
+ fillColors[i] = color;
+ }
+
+ float strokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ // issue 433 - the "c" can be null when painting
+ // the border of a tree icon used outside the
+ // JTree context.
+ boolean isSpecialButton = c instanceof SubstanceInternalArrowButton;
+ int joinKind = isSpecialButton ? BasicStroke.JOIN_MITER
+ : BasicStroke.JOIN_ROUND;
+ int capKind = isSpecialButton ? BasicStroke.CAP_SQUARE
+ : BasicStroke.CAP_BUTT;
+ graphics.setStroke(new BasicStroke(strokeWidth, capKind, joinKind));
+
+ MultipleGradientPaint gradient = new LinearGradientPaint(0, 0, 0,
+ height, fractions, fillColors, CycleMethod.REPEAT);
+ graphics.setPaint(gradient);
+ graphics.draw(contour);
+ graphics.dispose();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return this.displayName;
+ }
+
+ /**
+ * Retrieves a transformed color scheme.
+ *
+ * @param orig
+ * Original color scheme.
+ * @return Transformed color scheme.
+ */
+ private SubstanceColorScheme getShiftScheme(SubstanceColorScheme orig) {
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(orig
+ .getDisplayName(), this.getDisplayName(), this.transform);
+ SubstanceColorScheme result = transformMap.get(key);
+ if (result == null) {
+ result = this.transform.transform(orig);
+ transformMap.put(key, result);
+ }
+ return result;
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/border/FlatBorderPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/FlatBorderPainter.java
new file mode 100644
index 0000000..6ec62e2
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/FlatBorderPainter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.border;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+
+/**
+ * Border painter that returns images with flat appearance. This class is part
+ * of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class FlatBorderPainter extends StandardBorderPainter {
+ @Override
+ public String getDisplayName() {
+ return "Flat";
+ }
+
+ @Override
+ public Color getMidBorderColor(SubstanceColorScheme borderScheme) {
+ return super.getTopBorderColor(borderScheme);
+ }
+
+ @Override
+ public Color getBottomBorderColor(SubstanceColorScheme borderScheme) {
+ return super.getTopBorderColor(borderScheme);
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/border/FractionBasedBorderPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/FractionBasedBorderPainter.java
new file mode 100644
index 0000000..6c9d007
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/FractionBasedBorderPainter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.border;
+
+import java.awt.*;
+import java.awt.MultipleGradientPaint.CycleMethod;
+
+import org.pushingpixels.substance.api.ColorSchemeSingleColorQuery;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.painter.FractionBasedPainter;
+import org.pushingpixels.substance.internal.utils.SubstanceInternalArrowButton;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+/**
+ * Border painter with fraction-based stops and a color query associated with
+ * each stop. This class allows creating multi-gradient borders with exact
+ * control over which color is used at every gradient control point.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class FractionBasedBorderPainter extends FractionBasedPainter implements
+ SubstanceBorderPainter {
+ /**
+ * Creates a new fraction-based border painter.
+ *
+ * @param displayName
+ * The display name of this painter.
+ * @param fractions
+ * The fractions of this painter. Must be strictly increasing,
+ * starting from 0.0 and ending at 1.0.
+ * @param colorQueries
+ * The color queries of this painter. Must have the same size as
+ * the fractions array, and all entries must be non-
+ * <code>null</code>.
+ */
+ public FractionBasedBorderPainter(String displayName, float[] fractions,
+ ColorSchemeSingleColorQuery[] colorQueries) {
+ super(displayName, fractions, colorQueries);
+ }
+
+ @Override
+ public void paintBorder(Graphics g, Component c, int width, int height,
+ Shape contour, Shape innerContour, SubstanceColorScheme borderScheme) {
+ if (contour == null)
+ return;
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_NORMALIZE);
+
+ Color[] drawColors = new Color[this.fractions.length];
+ for (int i = 0; i < this.fractions.length; i++) {
+ ColorSchemeSingleColorQuery colorQuery = this.colorQueries[i];
+ drawColors[i] = colorQuery.query(borderScheme);
+ }
+
+ // System.out.println("\t" + interpolationScheme1.getDisplayName()
+ // + " -> [" + cyclePos + "] "
+ // + interpolationScheme2.getDisplayName());
+
+ float strokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ // issue 433 - the "c" can be null when painting
+ // the border of a tree icon used outside the
+ // JTree context.
+ boolean isSpecialButton = c instanceof SubstanceInternalArrowButton;
+ int joinKind = isSpecialButton ? BasicStroke.JOIN_MITER
+ : BasicStroke.JOIN_ROUND;
+ int capKind = isSpecialButton ? BasicStroke.CAP_SQUARE
+ : BasicStroke.CAP_BUTT;
+ graphics.setStroke(new BasicStroke(strokeWidth, capKind, joinKind));
+
+ MultipleGradientPaint gradient = new LinearGradientPaint(0, 0, 0,
+ height, this.fractions, drawColors, CycleMethod.REPEAT);
+ graphics.setPaint(gradient);
+ graphics.draw(contour);
+ graphics.dispose();
+ }
+
+ @Override
+ public boolean isPaintingInnerContour() {
+ return false;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/border/GlassBorderPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/GlassBorderPainter.java
new file mode 100644
index 0000000..e0c9eda
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/GlassBorderPainter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.border;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Border painter that returns images with classic appearance. This class is
+ * part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class GlassBorderPainter extends StandardBorderPainter {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.painter.border.StandardBorderPainter#
+ * getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return "Glass";
+ }
+
+ @Override
+ public Color getTopBorderColor(SubstanceColorScheme borderScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(super
+ .getTopBorderColor(borderScheme), super
+ .getMidBorderColor(borderScheme), 0.0);
+ }
+
+ @Override
+ public Color getMidBorderColor(SubstanceColorScheme borderScheme) {
+ return this.getTopBorderColor(borderScheme);
+ }
+
+ @Override
+ public Color getBottomBorderColor(SubstanceColorScheme borderScheme) {
+ return this.getTopBorderColor(borderScheme);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/border/StandardBorderPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/StandardBorderPainter.java
new file mode 100644
index 0000000..f6457ee
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/StandardBorderPainter.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.border;
+
+import java.awt.*;
+import java.awt.MultipleGradientPaint.CycleMethod;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * The default border painter. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class StandardBorderPainter implements SubstanceBorderPainter {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.trait.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return "Standard";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter
+ * #isPaintingInnerContour()
+ */
+ @Override
+ public boolean isPaintingInnerContour() {
+ return false;
+ }
+
+ @Override
+ public void paintBorder(Graphics g, Component c, int width, int height,
+ Shape contour, Shape innerContour, SubstanceColorScheme borderScheme) {
+ if (contour == null)
+ return;
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_NORMALIZE);
+
+ Color topBorderColor = getTopBorderColor(borderScheme);
+ Color midBorderColor = getMidBorderColor(borderScheme);
+ Color bottomBorderColor = getBottomBorderColor(borderScheme);
+
+ if ((topBorderColor != null) && (midBorderColor != null)
+ && (bottomBorderColor != null)) {
+ float strokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ // issue 433 - the "c" can be null when painting
+ // the border of a tree icon used outside the
+ // JTree context.
+ boolean isSpecialButton = c instanceof SubstanceInternalArrowButton;
+ int joinKind = isSpecialButton ? BasicStroke.JOIN_MITER
+ : BasicStroke.JOIN_ROUND;
+ int capKind = isSpecialButton ? BasicStroke.CAP_SQUARE
+ : BasicStroke.CAP_BUTT;
+ graphics.setStroke(new BasicStroke(strokeWidth, capKind, joinKind));
+
+ MultipleGradientPaint gradient = new LinearGradientPaint(0, 0, 0,
+ height, new float[] { 0.0f, 0.5f, 1.0f },
+ new Color[] { topBorderColor, midBorderColor,
+ bottomBorderColor }, CycleMethod.REPEAT);
+ graphics.setPaint(gradient);
+ graphics.draw(contour);
+ }
+
+ graphics.dispose();
+ }
+
+ /**
+ * Computes the color of the top portion of the border. Override to provide
+ * different visual.
+ *
+ * @param borderScheme
+ * The border color scheme.
+ * @return The color of the top portion of the border.
+ */
+ public Color getTopBorderColor(SubstanceColorScheme borderScheme) {
+ return SubstanceColorUtilities.getTopBorderColor(borderScheme);
+ }
+
+ /**
+ * Computes the color of the middle portion of the border. Override to
+ * provide different visual.
+ *
+ * @param borderScheme
+ * The border color scheme.
+ * @return The color of the middle portion of the border.
+ */
+ public Color getMidBorderColor(SubstanceColorScheme borderScheme) {
+ return SubstanceColorUtilities.getMidBorderColor(borderScheme);
+ }
+
+ /**
+ * Computes the color of the bottom portion of the border. Override to
+ * provide different visual.
+ *
+ * @param borderScheme
+ * The border color scheme.
+ * @return The color of the bottom portion of the border.
+ */
+ public Color getBottomBorderColor(SubstanceColorScheme borderScheme) {
+ return SubstanceColorUtilities.getBottomBorderColor(borderScheme);
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/border/SubstanceBorderPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/SubstanceBorderPainter.java
new file mode 100644
index 0000000..739d39e
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/border/SubstanceBorderPainter.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.border;
+
+import java.awt.*;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.trait.SubstanceTrait;
+
+/**
+ * Border painter interface for <b>Substance</b> look and feel. This class is
+ * part of officially supported API.<br>
+ * <br>
+ *
+ * Starting from version 4.0, the borders of some controls (buttons, check
+ * boxes, tabs, scroll bars etc) are painted by border painters. Up until
+ * version 4.0 this has been done by gradient painters (
+ * {@link SubstanceFillPainter}) instead. Note that a custom gradient painter
+ * may continue painting the borders, but these will be overriden by the current
+ * border painter.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public interface SubstanceBorderPainter extends SubstanceTrait {
+ /**
+ * Paints the control border.
+ *
+ * @param g
+ * Graphics.
+ * @param c
+ * Component.
+ * @param width
+ * Width of a UI component.
+ * @param height
+ * Height of a UI component.
+ * @param contour
+ * Contour of a UI component.
+ * @param innerContour
+ * Inner contour of a UI component. May be ignored if the
+ * specific implementation paints only the outside border.
+ * @param borderScheme
+ * The border color scheme.
+ */
+ public void paintBorder(Graphics g, Component c, int width, int height,
+ Shape contour, Shape innerContour, SubstanceColorScheme borderScheme);
+
+ /**
+ * Returns boolean indication whether this border painter is painting the
+ * inner contours.
+ *
+ * @return <code>true</code> if this border painter is painting the inner
+ * contours, <code>false</code> otherwise.
+ */
+ public boolean isPaintingInnerContour();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/ArcDecorationPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/ArcDecorationPainter.java
new file mode 100644
index 0000000..00a47ea
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/ArcDecorationPainter.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.decoration;
+
+import java.awt.*;
+import java.awt.MultipleGradientPaint.CycleMethod;
+import java.awt.geom.GeneralPath;
+import java.awt.image.BufferedImage;
+
+import javax.swing.*;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.SubstancePainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Implementation of {@link SubstanceDecorationPainter} that uses "arc" painting
+ * on title panes and lighter gradient near the center of the application frame.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ArcDecorationPainter implements SubstanceDecorationPainter {
+ /**
+ * The display name for the decoration painters of this class.
+ */
+ public static final String DISPLAY_NAME = "Arc";
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return DISPLAY_NAME;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ *
+ *
+ *
+ *
+ * @seeorg.pushingpixels.substance.painter.decoration.SubstanceDecorationPainter
+ * # paintDecorationArea(java.awt.Graphics2D, java.awt.Component,
+ * org.pushingpixels.substance.painter.decoration.DecorationAreaType, int,
+ * int, org.pushingpixels.substance.skin.SubstanceSkin)
+ */
+ @Override
+ public void paintDecorationArea(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+ if ((decorationAreaType == DecorationAreaType.PRIMARY_TITLE_PANE)
+ || (decorationAreaType == DecorationAreaType.SECONDARY_TITLE_PANE)) {
+ this.paintTitleBackground(graphics, comp, width, height, skin
+ .getBackgroundColorScheme(decorationAreaType));
+ } else {
+ this.paintExtraBackground(graphics, SubstanceCoreUtilities
+ .getHeaderParent(comp), comp, width, height, skin
+ .getBackgroundColorScheme(decorationAreaType));
+ }
+ }
+
+ /**
+ * Paints the title background.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param width
+ * Width.
+ * @param height
+ * Height.
+ * @param scheme
+ * Color scheme for painting the title background.
+ */
+ private void paintTitleBackground(Graphics2D graphics, Component comp,
+ int width, int height, SubstanceColorScheme scheme) {
+ // System.out.println(scheme.getDisplayName());
+ // create rectangular background and later draw it on
+ // result image with contour clip.
+ BufferedImage rectangular = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D rgraphics = (Graphics2D) rectangular.getGraphics();
+
+ // Fill background
+ GeneralPath clipTop = new GeneralPath();
+ clipTop.moveTo(0, 0);
+ clipTop.lineTo(width, 0);
+ clipTop.lineTo(width, height / 2);
+ clipTop.quadTo(width / 2, height / 4, 0, height / 2);
+ clipTop.lineTo(0, 0);
+
+ rgraphics.setClip(clipTop);
+ LinearGradientPaint gradientTop = new LinearGradientPaint(0, 0, width,
+ 0, new float[] { 0.0f, 0.5f, 1.0f }, new Color[] {
+ scheme.getLightColor(), scheme.getUltraLightColor(),
+ scheme.getLightColor() }, CycleMethod.REPEAT);
+ rgraphics.setPaint(gradientTop);
+ rgraphics.fillRect(0, 0, width, height);
+
+ GeneralPath clipBottom = new GeneralPath();
+ clipBottom.moveTo(0, height);
+ clipBottom.lineTo(width, height);
+ clipBottom.lineTo(width, height / 2);
+ clipBottom.quadTo(width / 2, height / 4, 0, height / 2);
+ clipBottom.lineTo(0, height);
+
+ rgraphics.setClip(clipBottom);
+ LinearGradientPaint gradientBottom = new LinearGradientPaint(0, 0,
+ width, 0, new float[] { 0.0f, 0.5f, 1.0f }, new Color[] {
+ scheme.getMidColor(), scheme.getLightColor(),
+ scheme.getMidColor() }, CycleMethod.REPEAT);
+ rgraphics.setPaint(gradientBottom);
+ rgraphics.fillRect(0, 0, width, height);
+
+ GeneralPath mid = new GeneralPath();
+ mid.moveTo(width, height / 2);
+ mid.quadTo(width / 2, height / 4, 0, height / 2);
+ // rgraphics.setClip(new Rectangle(0, 0, width / 2, height));
+ // rgraphics
+ // .setClip(new Rectangle(width / 2, 0, width - width / 2, height));
+ rgraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ rgraphics.setClip(new Rectangle(0, 0, width, height));
+ rgraphics.draw(mid);
+
+ graphics.drawImage(rectangular, 0, 0, null);
+ }
+
+ /**
+ * Paints the background of non-title decoration areas.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param parent
+ * Component ancestor for computing the correct offset of the
+ * background painting.
+ * @param comp
+ * Component.
+ * @param width
+ * Width.
+ * @param height
+ * Height.
+ * @param scheme
+ * Color scheme for painting the title background.
+ */
+ private void paintExtraBackground(Graphics2D graphics, Container parent,
+ Component comp, int width, int height, SubstanceColorScheme scheme) {
+ Point offset = SubstancePainterUtils.getOffsetInRootPaneCoords(comp);
+ JRootPane rootPane = SwingUtilities.getRootPane(parent);
+ // fix for bug 234 - Window doesn't have a root pane.
+ JComponent titlePane = null;
+ if (rootPane != null) {
+ titlePane = SubstanceCoreUtilities.getTitlePane(rootPane);
+ }
+
+ int pWidth = (titlePane == null) ? parent.getWidth() : titlePane
+ .getWidth();
+
+ if (pWidth != 0) {
+ LinearGradientPaint gradientBottom = new LinearGradientPaint(
+ -offset.x, 0, -offset.x + pWidth, 0, new float[] { 0.0f,
+ 0.5f, 1.0f }, new Color[] { scheme.getMidColor(),
+ scheme.getLightColor(), scheme.getMidColor() },
+ CycleMethod.REPEAT);
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.setPaint(gradientBottom);
+ g2d.fillRect(-offset.x, 0, pWidth, height);
+ g2d.dispose();
+ }
+
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/BrushedMetalDecorationPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/BrushedMetalDecorationPainter.java
new file mode 100644
index 0000000..7ae3048
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/BrushedMetalDecorationPainter.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.decoration;
+
+import java.net.URL;
+
+import javax.imageio.ImageIO;
+
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Implementation of {@link SubstanceDecorationPainter} that uses brushed metal
+ * painting on decoration areas.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public class BrushedMetalDecorationPainter extends
+ ImageWrapperDecorationPainter {
+ /**
+ * The display name for the decoration painters of this class.
+ */
+ public static final String DISPLAY_NAME = "Brushed Metal";
+
+ /**
+ * Creates a new brushed metal decoration painter.
+ */
+ public BrushedMetalDecorationPainter() {
+ super();
+ try {
+ // the following is fix by Dag Joar and Christian Schlichtherle
+ // for application running with -Xbootclasspath VM flag. In this
+ // case, the using MyClass.class.getClassLoader() would return
+ // null, but the context class loader will function properly
+ // that classes will be properly loaded regardless of whether
+ // the lib is added to the system class path, the extension class
+ // path and regardless of the class loader architecture set up by
+ // some frameworks.
+ ClassLoader cl = SubstanceCoreUtilities
+ .getClassLoaderForResources();
+ URL metalUrl = cl.getResource("resource/brushed.gif");
+ this.originalTile = ImageIO.read(metalUrl);
+ } catch (Exception exc) {
+ // ignore - probably specified incorrect file
+ // or file is not image
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return DISPLAY_NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/ClassicDecorationPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/ClassicDecorationPainter.java
new file mode 100644
index 0000000..cb81caf
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/ClassicDecorationPainter.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.decoration;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.fill.ClassicFillPainter;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Decoration painter that paints a classic gradient. This class is part of
+ * officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.3
+ */
+public class ClassicDecorationPainter implements SubstanceDecorationPainter {
+ /**
+ * The display name for the decoraion painters of this class.
+ */
+ public static final String DISPLAY_NAME = "Classic";
+
+ /**
+ * Cache for small objects.
+ */
+ protected final static LazyResettableHashMap<BufferedImage> smallImageCache = new LazyResettableHashMap<BufferedImage>(
+ "ClassicDecorationPainter");
+
+ /**
+ * Single gradient painter instance.
+ */
+ protected ClassicFillPainter painter;
+
+ /**
+ * Creates new classic title painter.
+ */
+ public ClassicDecorationPainter() {
+ this.painter = new ClassicFillPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return DISPLAY_NAME;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ *
+ *
+ * @seeorg.pushingpixels.substance.painter.decoration.SubstanceDecorationPainter
+ * # paintDecorationArea(java.awt.Graphics2D, java.awt.Component,
+ * org.pushingpixels.substance.painter.decoration.DecorationAreaType, int,
+ * int, org.pushingpixels.substance.skin.SubstanceSkin)
+ */
+ @Override
+ public void paintDecorationArea(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+ SubstanceColorScheme scheme = skin
+ .getBackgroundColorScheme(decorationAreaType);
+ if (width * height < 100000) {
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
+ scheme.getDisplayName());
+ BufferedImage result = smallImageCache.get(key);
+ if (result == null) {
+ result = SubstanceCoreUtilities.getBlankImage(width, height);
+ this.internalPaint((Graphics2D) result.getGraphics(), comp,
+ width, height, scheme);
+ smallImageCache.put(key, result);
+ }
+ graphics.drawImage(result, 0, 0, null);
+ return;
+ }
+
+ this.internalPaint(graphics, comp, width, height, scheme);
+ }
+
+ /**
+ * Paints the specified area.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param width
+ * Width.
+ * @param height
+ * Height.
+ * @param scheme
+ * Substance color scheme for painting the area.
+ */
+ protected void internalPaint(Graphics2D graphics, Component comp,
+ int width, int height, SubstanceColorScheme scheme) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.translate(-3, -3);
+ this.painter.paintContourBackground(g2d, comp, width + 6, height + 6,
+ new Rectangle(width + 6, height + 6), false, scheme, false);
+ g2d.dispose();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/FlatDecorationPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/FlatDecorationPainter.java
new file mode 100644
index 0000000..a7832f5
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/FlatDecorationPainter.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.decoration;
+
+import java.awt.Component;
+import java.awt.Graphics2D;
+
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceSkin;
+
+/**
+ * Decoration painter that paints a flat appearance. This class is part of
+ * officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.3
+ */
+public class FlatDecorationPainter implements SubstanceDecorationPainter {
+ /**
+ * The display name for the decoration painters of this class.
+ */
+ public static final String DISPLAY_NAME = "Flat";
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return DISPLAY_NAME;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.substance.painter.decoration.SubstanceDecorationPainter#
+ * paintDecorationArea(java.awt.Graphics2D, java.awt.Component,
+ * org.pushingpixels.substance.painter.decoration.DecorationAreaType, int, int,
+ * org.pushingpixels.substance.skin.SubstanceSkin)
+ */
+ @Override
+ public void paintDecorationArea(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+ graphics
+ .setColor(skin.getBackgroundColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE)
+ .getBackgroundFillColor());
+ graphics.fillRect(0, 0, width, height);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/FractionBasedDecorationPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/FractionBasedDecorationPainter.java
new file mode 100644
index 0000000..abf4d70
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/FractionBasedDecorationPainter.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.decoration;
+
+import java.awt.*;
+import java.awt.MultipleGradientPaint.CycleMethod;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.FractionBasedPainter;
+import org.pushingpixels.substance.api.painter.SubstancePainterUtils;
+
+/**
+ * Decoration painter with fraction-based stops and a color query associated
+ * with each stop. This class allows creating multi-gradient decorations with
+ * exact control over which color is used at every gradient control point.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class FractionBasedDecorationPainter extends FractionBasedPainter
+ implements SubstanceDecorationPainter {
+ private Set<DecorationAreaType> decoratedAreas;
+
+ /**
+ * Creates a new fraction-based decoration painter.
+ *
+ * @param displayName
+ * The display name of this painter.
+ * @param fractions
+ * The fractions of this painter. Must be strictly increasing,
+ * starting from 0.0 and ending at 1.0.
+ * @param colorQueries
+ * The color queries of this painter. Must have the same size as
+ * the fractions array, and all entries must be non-
+ * <code>null</code>.
+ */
+ public FractionBasedDecorationPainter(String displayName,
+ float[] fractions, ColorSchemeSingleColorQuery[] colorQueries) {
+ this(displayName, fractions, colorQueries,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE);
+ }
+
+ /**
+ * Creates a new fraction-based decoration painter.
+ *
+ * @param displayName
+ * The display name of this painter.
+ * @param fractions
+ * The fractions of this painter. Must be strictly increasing,
+ * starting from 0.0 and ending at 1.0.
+ * @param colorQueries
+ * The color queries of this painter. Must have the same size as
+ * the fractions array, and all entries must be non-
+ * <code>null</code>.
+ * @param decorationAreas
+ * Decoration areas that should be painted based on the color
+ * queries. All the rest will be filled with a solid color from
+ * the background color scheme of the matching decoration area.
+ */
+ public FractionBasedDecorationPainter(String displayName,
+ float[] fractions, ColorSchemeSingleColorQuery[] colorQueries,
+ DecorationAreaType... decorationAreas) {
+ super(displayName, fractions, colorQueries);
+ this.decoratedAreas = new HashSet<DecorationAreaType>();
+ if (decorationAreas != null) {
+ for (DecorationAreaType decorationArea : decorationAreas) {
+ this.decoratedAreas.add(decorationArea);
+ }
+ }
+ }
+
+ @Override
+ public void paintDecorationArea(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+ if (this.decoratedAreas.contains(decorationAreaType)) {
+ this.paintDecoratedBackground(graphics, comp, decorationAreaType,
+ width, height, skin
+ .getBackgroundColorScheme(decorationAreaType));
+ } else {
+ this.paintSolidBackground(graphics, comp, width, height, skin
+ .getBackgroundColorScheme(decorationAreaType));
+ }
+ }
+
+ private void paintDecoratedBackground(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceColorScheme scheme) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ Color[] fillColors = new Color[this.fractions.length];
+ for (int i = 0; i < this.fractions.length; i++) {
+ ColorSchemeSingleColorQuery colorQuery = this.colorQueries[i];
+ fillColors[i] = colorQuery.query(scheme);
+ }
+
+ Component topMostWithSameDecorationAreaType = SubstancePainterUtils
+ .getTopMostParentWithDecorationAreaType(comp,
+ decorationAreaType);
+ Point inTopMost = SwingUtilities.convertPoint(comp, new Point(0, 0),
+ topMostWithSameDecorationAreaType);
+ int dy = inTopMost.y;
+
+ MultipleGradientPaint gradient = new LinearGradientPaint(0, 0, 0,
+ topMostWithSameDecorationAreaType.getHeight(), this.fractions,
+ fillColors, CycleMethod.REPEAT);
+ g2d.setPaint(gradient);
+ g2d.translate(0, -dy);
+ g2d
+ .fillRect(0, 0, width, topMostWithSameDecorationAreaType
+ .getHeight());
+
+ g2d.dispose();
+ }
+
+ private void paintSolidBackground(Graphics2D graphics, Component comp,
+ int width, int height, SubstanceColorScheme scheme) {
+ graphics.setColor(scheme.getMidColor());
+ graphics.fillRect(0, 0, width, height);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/Glass3DDecorationPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/Glass3DDecorationPainter.java
new file mode 100644
index 0000000..b1b0333
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/Glass3DDecorationPainter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.decoration;
+
+import java.awt.Component;
+import java.awt.Graphics2D;
+
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+
+/**
+ * Decoration painter that paints a 3D glass gradient. This class is part of
+ * officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.3
+ */
+public class Glass3DDecorationPainter implements SubstanceDecorationPainter {
+ /**
+ * The display name for the decoration painters of this class.
+ */
+ public static final String DISPLAY_NAME = "Glass 3D";
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return DISPLAY_NAME;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.substance.painter.decoration.SubstanceDecorationPainter#
+ * paintDecorationArea(java.awt.Graphics2D, java.awt.Component,
+ * org.pushingpixels.substance.painter.decoration.DecorationAreaType, int, int,
+ * org.pushingpixels.substance.skin.SubstanceSkin)
+ */
+ @Override
+ public void paintDecorationArea(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+ SubstanceImageCreator.paintRectangularBackground(comp, graphics, 0, 0, width,
+ height, skin.getBackgroundColorScheme(decorationAreaType),
+ 0.0f, false);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/ImageWrapperDecorationPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/ImageWrapperDecorationPainter.java
new file mode 100644
index 0000000..85cf26d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/ImageWrapperDecorationPainter.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.decoration;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.SubstancePainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+
+/**
+ * Implementation of {@link SubstanceDecorationPainter} that uses brushed metal
+ * painting on decoration areas.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.3
+ */
+public abstract class ImageWrapperDecorationPainter implements
+ SubstanceDecorationPainter {
+ /**
+ * Contains the original (not colorized) image of this painter.
+ */
+ protected Image originalTile = null;
+
+ /**
+ * The base decoration painter - the colorized image tiles are painted over
+ * the painting of this painter. Can be <code>null</code>.
+ */
+ protected SubstanceDecorationPainter baseDecorationPainter;
+
+ /**
+ * Map of colorized tiles.
+ */
+ protected LinkedHashMap<String, Image> colorizedTileMap;
+
+ /**
+ * Alpha channel for the texture image (colorized tiles applied on top of
+ * the {@link #baseDecorationPainter} painting).
+ */
+ protected float textureAlpha;
+
+ /**
+ * Creates a new image wrapper decoration painter.
+ */
+ public ImageWrapperDecorationPainter() {
+ this.textureAlpha = 0.3f;
+
+ this.colorizedTileMap = new LinkedHashMap<String, Image>() {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<String, Image> eldest) {
+ return this.size() > 10;
+ }
+ };
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @seeorg.pushingpixels.substance.painter.decoration.SubstanceDecorationPainter
+ * # paintDecorationArea(java.awt.Graphics2D, java.awt.Component,
+ * org.pushingpixels.substance.painter.decoration.DecorationAreaType, int,
+ * int, org.pushingpixels.substance.api.SubstanceSkin)
+ */
+ @Override
+ public void paintDecorationArea(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+ if ((decorationAreaType == DecorationAreaType.PRIMARY_TITLE_PANE)
+ || (decorationAreaType == DecorationAreaType.SECONDARY_TITLE_PANE)) {
+ this.paintTitleBackground(graphics, comp, decorationAreaType,
+ width, height, skin);
+ } else {
+ this.paintExtraBackground(graphics, comp, decorationAreaType,
+ width, height, skin);
+ }
+ }
+
+ /**
+ * Paints the title background.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param decorationAreaType
+ * Decoration area type. Must not be <code>null</code>.
+ * @param width
+ * Width.
+ * @param height
+ * Height.
+ * @param skin
+ * Skin for painting the title background.
+ */
+ private void paintTitleBackground(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+
+ SubstanceColorScheme tileScheme = skin
+ .getBackgroundColorScheme(decorationAreaType);
+ if (this.baseDecorationPainter == null) {
+ graphics.setColor(tileScheme.getMidColor());
+ graphics.fillRect(0, 0, width, height);
+ } else {
+ this.baseDecorationPainter.paintDecorationArea(graphics, comp,
+ decorationAreaType, width, height, skin);
+ }
+
+ Graphics2D temp = (Graphics2D) graphics.create();
+ this.tileArea(temp, comp, tileScheme, 0, 0, 0, 0, width, height);
+ temp.dispose();
+ }
+
+ /**
+ * Paints the background of non-title decoration areas.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param decorationAreaType
+ * Decoration area type. Must not be <code>null</code>.
+ * @param width
+ * Width.
+ * @param height
+ * Height.
+ * @param skin
+ * Skin for painting the background of non-title decoration
+ * areas.
+ */
+ private void paintExtraBackground(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+
+ Point offset = SubstancePainterUtils.getOffsetInRootPaneCoords(comp);
+
+ SubstanceColorScheme tileScheme = skin
+ .getBackgroundColorScheme(decorationAreaType);
+ if (this.baseDecorationPainter != null) {
+ this.baseDecorationPainter.paintDecorationArea(graphics, comp,
+ decorationAreaType, width, height, skin);
+ } else {
+ graphics.setColor(tileScheme.getMidColor());
+ graphics.fillRect(0, 0, width, height);
+ }
+ Graphics2D temp = (Graphics2D) graphics.create();
+ this.tileArea(temp, comp, tileScheme, offset.x, offset.y, 0, 0, width,
+ height);
+ temp.dispose();
+ }
+
+ /**
+ * Tiles the specified area with colorized version of the image tile. This
+ * is called after the {@link #baseDecorationPainter} has painted the area.
+ * This method should respect the current {@link #textureAlpha} value.
+ *
+ * @param g
+ * Graphic context.
+ * @param comp
+ * Component.
+ * @param tileScheme
+ * Scheme for the tile colorization.
+ * @param offsetTextureX
+ * X offset for the tiling.
+ * @param offsetTextureY
+ * Y offset for the tiling.
+ * @param x
+ * X coordinate of the tiling region.
+ * @param y
+ * Y coordinate of the tiling region.
+ * @param width
+ * Width of the tiling region.
+ * @param height
+ * Height of the tiling region.
+ */
+ protected void tileArea(Graphics2D g, Component comp,
+ SubstanceColorScheme tileScheme, int offsetTextureX,
+ int offsetTextureY, int x, int y, int width, int height) {
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(comp,
+ this.textureAlpha, g));
+
+ Image colorizedTile = this.getColorizedTile(tileScheme);
+ int tileWidth = colorizedTile.getWidth(null);
+ int tileHeight = colorizedTile.getHeight(null);
+
+ offsetTextureX = offsetTextureX % tileWidth;
+ offsetTextureY = offsetTextureY % tileHeight;
+ int currTileTop = -offsetTextureY;
+ do {
+ int currTileLeft = -offsetTextureX;
+ do {
+ graphics.drawImage(colorizedTile, currTileLeft, currTileTop,
+ null);
+ currTileLeft += tileWidth;
+ } while (currTileLeft < width);
+ currTileTop += tileHeight;
+ } while (currTileTop < height);
+
+ graphics.dispose();
+ }
+
+ /**
+ * Sets the base decoration painter.
+ *
+ * @param baseDecorationPainter
+ * Base decoration painter.
+ */
+ public void setBaseDecorationPainter(
+ SubstanceDecorationPainter baseDecorationPainter) {
+ this.baseDecorationPainter = baseDecorationPainter;
+ }
+
+ /**
+ * Sets the alpha channel for the image texture.
+ *
+ * @param textureAlpha
+ * Alpha channel for the image texture.
+ */
+ public void setTextureAlpha(float textureAlpha) {
+ this.textureAlpha = textureAlpha;
+ }
+
+ /**
+ * Returns a colorized image tile.
+ *
+ * @param scheme
+ * Color scheme for the colorization.
+ * @return Colorized tile.
+ */
+ protected Image getColorizedTile(SubstanceColorScheme scheme) {
+ Image result = this.colorizedTileMap.get(scheme.getDisplayName());
+ if (result == null) {
+ BufferedImage tileBi = new BufferedImage(this.originalTile
+ .getWidth(null), this.originalTile.getHeight(null),
+ BufferedImage.TYPE_INT_ARGB);
+ tileBi.getGraphics().drawImage(this.originalTile, 0, 0, null);
+ result = SubstanceImageCreator.getColorSchemeImage(tileBi, scheme,
+ 0.0f);
+ this.colorizedTileMap.put(scheme.getDisplayName(), result);
+ }
+ return result;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/MarbleNoiseDecorationPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/MarbleNoiseDecorationPainter.java
new file mode 100644
index 0000000..1e47a8d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/MarbleNoiseDecorationPainter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.decoration;
+
+import org.pushingpixels.substance.internal.utils.NoiseFactory;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+
+/**
+ * Implementation of {@link SubstanceDecorationPainter} that uses marble noise
+ * painting on decoration areas.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.3
+ */
+public class MarbleNoiseDecorationPainter extends ImageWrapperDecorationPainter {
+ /**
+ * The display name for the decoration painters of this class.
+ */
+ public static final String DISPLAY_NAME = "Marble Noise";
+
+ /**
+ * Creates a new marble noise decoration painter.
+ */
+ public MarbleNoiseDecorationPainter() {
+ super();
+ this.originalTile = NoiseFactory.getNoiseImage(
+ SubstanceColorSchemeUtilities.METALLIC_SKIN, 400, 400, 0.8,
+ 0.8, false, true, true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return DISPLAY_NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/MatteDecorationPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/MatteDecorationPainter.java
new file mode 100644
index 0000000..0120e9b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/MatteDecorationPainter.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.decoration;
+
+import java.awt.*;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.SubstancePainterUtils;
+
+/**
+ * Implementation of {@link SubstanceDecorationPainter} that uses matte painting
+ * on decoration areas.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.3
+ */
+public class MatteDecorationPainter implements SubstanceDecorationPainter {
+ /**
+ * The display name for the decoration painters of this class.
+ */
+ public static final String DISPLAY_NAME = "Matte";
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return DISPLAY_NAME;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ *
+ *
+ *
+ *
+ * @seeorg.pushingpixels.substance.painter.decoration.SubstanceDecorationPainter
+ * # paintDecorationArea(java.awt.Graphics2D, java.awt.Component,
+ * org.pushingpixels.substance.painter.decoration.DecorationAreaType, int,
+ * int, org.pushingpixels.substance.api.SubstanceSkin)
+ */
+ @Override
+ public void paintDecorationArea(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+ if ((decorationAreaType == DecorationAreaType.PRIMARY_TITLE_PANE)
+ || (decorationAreaType == DecorationAreaType.SECONDARY_TITLE_PANE)) {
+ this.paintTitleBackground(graphics, comp, width, height, skin
+ .getBackgroundColorScheme(decorationAreaType));
+ } else {
+ this.paintExtraBackground(graphics, comp, width, height, skin
+ .getBackgroundColorScheme(decorationAreaType));
+ }
+ }
+
+ /**
+ * Paints the title background.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param width
+ * Width.
+ * @param height
+ * Height.
+ * @param scheme
+ * Color scheme for painting the title background.
+ */
+ private void paintTitleBackground(Graphics2D graphics, Component comp,
+ int width, int height, SubstanceColorScheme scheme) {
+ Graphics2D temp = (Graphics2D) graphics.create();
+ this.fill(temp, comp, scheme, 0, 0, 0, width, height);
+ temp.dispose();
+ }
+
+ /**
+ * Paints the background of non-title decoration areas.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param width
+ * Width.
+ * @param height
+ * Height.
+ * @param scheme
+ * Color scheme for painting the title background.
+ */
+ private void paintExtraBackground(Graphics2D graphics, Component comp,
+ int width, int height, SubstanceColorScheme scheme) {
+
+ Point offset = SubstancePainterUtils.getOffsetInRootPaneCoords(comp);
+ Graphics2D temp = (Graphics2D) graphics.create();
+ this.fill(temp, comp, scheme, offset.y, 0, 0, width, height);
+ temp.dispose();
+ }
+
+ /**
+ * Fills the relevant part with the gradient fill.
+ *
+ * @param graphics
+ * Graphics.
+ * @param comp
+ * Component.
+ * @param scheme
+ * Color scheme to use.
+ * @param offsetY
+ * Vertical offset.
+ * @param x
+ * X coordinate of the fill area.
+ * @param y
+ * Y coordinate of the fill area.
+ * @param width
+ * Fill area width.
+ * @param height
+ * Fill area height.
+ */
+ protected void fill(Graphics2D graphics, Component comp,
+ SubstanceColorScheme scheme, int offsetY, int x, int y, int width,
+ int height) {
+ // System.out.println(comp.getClass().getName() + ":"
+ // + scheme.getDisplayName());
+
+ // 0 - 50 : light -> medium
+ // 50 - : medium fill
+ int flexPoint = 50;
+
+ int startY = y + offsetY;
+ if (startY < 0)
+ startY = 0;
+ int endY = startY + height;
+
+ int currStart = 0;
+ if (flexPoint >= startY) {
+ graphics.setPaint(new GradientPaint(x, currStart - offsetY, scheme
+ .getLightColor(), x, flexPoint - offsetY, scheme
+ .getMidColor()));
+ graphics.fillRect(x, currStart - offsetY, width, flexPoint);
+ }
+ currStart += flexPoint;
+
+ if (currStart > endY)
+ return;
+
+ graphics.setColor(scheme.getMidColor());
+ graphics.fillRect(x, currStart - offsetY, width, endY - currStart
+ + offsetY);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/SubstanceDecorationPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/SubstanceDecorationPainter.java
new file mode 100644
index 0000000..9560081
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/decoration/SubstanceDecorationPainter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.decoration;
+
+import java.awt.Component;
+import java.awt.Graphics2D;
+
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.trait.SubstanceTrait;
+
+/**
+ * Decoration painter interface for <b>Substance</b> look and feel. This class
+ * is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.3
+ */
+public interface SubstanceDecorationPainter extends SubstanceTrait {
+ /**
+ * Paints the decoration area.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param decorationAreaType
+ * Decoration area type. Must not be <code>null</code>.
+ * @param width
+ * Width.
+ * @param height
+ * Height.
+ * @param skin
+ * Skin for painting the decoration area.
+ */
+ public void paintDecorationArea(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/ClassicFillPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/ClassicFillPainter.java
new file mode 100644
index 0000000..a944f24
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/ClassicFillPainter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.fill;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Fill painter that returns images with classic appearance. This class is part
+ * of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ClassicFillPainter extends StandardFillPainter {
+ /**
+ * Reusable instance of this painter.
+ */
+ public static final ClassicFillPainter INSTANCE = new ClassicFillPainter();
+
+ /**
+ * Creates a new classic gradient painter.
+ */
+ public ClassicFillPainter() {
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Classic";
+ }
+
+ @Override
+ public Color getTopFillColor(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(super
+ .getBottomFillColor(fillScheme), super
+ .getMidFillColorTop(fillScheme), 0.5);
+ }
+
+ @Override
+ public Color getMidFillColorTop(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(super
+ .getMidFillColorTop(fillScheme), super
+ .getBottomFillColor(fillScheme), 0.7);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/FractionBasedFillPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/FractionBasedFillPainter.java
new file mode 100644
index 0000000..7a3e0b8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/FractionBasedFillPainter.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.fill;
+
+import java.awt.*;
+import java.awt.MultipleGradientPaint.CycleMethod;
+
+import org.pushingpixels.substance.api.ColorSchemeSingleColorQuery;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.painter.FractionBasedPainter;
+
+/**
+ * Fill painter with fraction-based stops and a color query associated with each
+ * stop. This class allows creating multi-gradient fills with exact control over
+ * which color is used at every gradient control point.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class FractionBasedFillPainter extends FractionBasedPainter implements
+ SubstanceFillPainter {
+ /**
+ * Creates a new fraction-based fill painter.
+ *
+ * @param displayName
+ * The display name of this painter.
+ * @param fractions
+ * The fractions of this painter. Must be strictly increasing,
+ * starting from 0.0 and ending at 1.0.
+ * @param colorQueries
+ * The color queries of this painter. Must have the same size as
+ * the fractions array, and all entries must be non-
+ * <code>null</code>.
+ */
+ public FractionBasedFillPainter(String displayName, float[] fractions,
+ ColorSchemeSingleColorQuery[] colorQueries) {
+ super(displayName, fractions, colorQueries);
+ }
+
+ @Override
+ public void paintContourBackground(Graphics g, Component comp, int width,
+ int height, Shape contour, boolean isFocused,
+ SubstanceColorScheme fillScheme, boolean hasShine) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ Color[] fillColors = new Color[this.fractions.length];
+ for (int i = 0; i < this.fractions.length; i++) {
+ ColorSchemeSingleColorQuery colorQuery = this.colorQueries[i];
+ fillColors[i] = colorQuery.query(fillScheme);
+ }
+
+ MultipleGradientPaint gradient = new LinearGradientPaint(0, 0, 0,
+ height, this.fractions, fillColors, CycleMethod.REPEAT);
+ graphics.setPaint(gradient);
+ graphics.fill(contour);
+ graphics.dispose();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/GlassFillPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/GlassFillPainter.java
new file mode 100644
index 0000000..9c977e0
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/GlassFillPainter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.fill;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Fill painter that returns images with classic appearance. This class is part
+ * of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class GlassFillPainter extends StandardFillPainter {
+ @Override
+ public String getDisplayName() {
+ return "Glass";
+ }
+
+ @Override
+ public Color getTopFillColor(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(super
+ .getBottomFillColor(fillScheme), super
+ .getMidFillColorTop(fillScheme), 0.6);
+ }
+
+ @Override
+ public Color getMidFillColorTop(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(this
+ .getTopFillColor(fillScheme), super
+ .getMidFillColorTop(fillScheme), 0.8);
+ }
+
+ @Override
+ public Color getMidFillColorBottom(SubstanceColorScheme fillScheme) {
+ return super.getMidFillColorTop(fillScheme);
+ }
+
+ @Override
+ public Color getBottomFillColor(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(this
+ .getMidFillColorBottom(fillScheme), super
+ .getBottomFillColor(fillScheme), 0.7);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/MatteFillPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/MatteFillPainter.java
new file mode 100644
index 0000000..4db4860
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/MatteFillPainter.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.fill;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Fill painter that returns images with matte appearance. This class is part of
+ * officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public class MatteFillPainter extends ClassicFillPainter {
+ /**
+ * Reusable instance of this painter.
+ */
+ public static final MatteFillPainter INSTANCE = new MatteFillPainter();
+
+ /**
+ * Creates a new matte fill painter.
+ */
+ public MatteFillPainter() {
+ super();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Matte";
+ }
+
+ @Override
+ public Color getTopFillColor(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(super
+ .getBottomFillColor(fillScheme), super
+ .getMidFillColorTop(fillScheme), 0.5);
+ }
+
+ @Override
+ public Color getMidFillColorTop(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(super
+ .getMidFillColorTop(fillScheme), super
+ .getBottomFillColor(fillScheme), 0.7);
+ }
+
+ @Override
+ public Color getBottomFillColor(SubstanceColorScheme fillScheme) {
+ return super.getMidFillColorTop(fillScheme);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/StandardFillPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/StandardFillPainter.java
new file mode 100644
index 0000000..c828c8b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/StandardFillPainter.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.fill;
+
+import java.awt.*;
+import java.awt.MultipleGradientPaint.CycleMethod;
+import java.awt.image.BufferedImage;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Gradient painter that returns images with subtle 3D gradient appearance. This
+ * class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class StandardFillPainter implements SubstanceFillPainter {
+ @Override
+ public String getDisplayName() {
+ return "Standard";
+ }
+
+ @Override
+ public void paintContourBackground(Graphics g, Component comp, int width,
+ int height, Shape contour, boolean isFocused,
+ SubstanceColorScheme fillScheme, boolean hasShine) {
+
+ // long millis = System.nanoTime();
+
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ Color topFillColor = this.getTopFillColor(fillScheme);
+ Color midFillColorTop = this.getMidFillColorTop(fillScheme);
+ Color midFillColorBottom = this.getMidFillColorBottom(fillScheme);
+ Color bottomFillColor = this.getBottomFillColor(fillScheme);
+ Color topShineColor = this.getTopShineColor(fillScheme);
+ Color bottomShineColor = this.getBottomShineColor(fillScheme);
+
+ // Fill background
+ // long millis000 = System.nanoTime();
+
+ // graphics.clip(contour);
+ MultipleGradientPaint gradient = new LinearGradientPaint(0, 0, 0,
+ height, new float[] { 0.0f, 0.4999999f, 0.5f, 1.0f },
+ new Color[] { topFillColor, midFillColorTop,
+ midFillColorBottom, bottomFillColor },
+ CycleMethod.REPEAT);
+ graphics.setPaint(gradient);
+ graphics.fill(contour);
+
+ // long millis003 = 0, millis004 = 0, millis005 = 0;
+ if (hasShine && (topShineColor != null) && (bottomShineColor != null)) {
+ graphics.clip(contour);
+ int shineHeight = (int) (height / 1.8);
+ int kernelSize = (int) Math.min(12, Math.pow(Math
+ .min(width, height), 0.8) / 4);
+ if (kernelSize < 3)
+ kernelSize = 3;
+
+ BufferedImage blurredGhostContour = SubstanceCoreUtilities
+ .getBlankImage(width + 2 * kernelSize, height + 2
+ * kernelSize);
+ Graphics2D blurredGhostGraphics = (Graphics2D) blurredGhostContour
+ .getGraphics().create();
+ blurredGhostGraphics.setRenderingHint(
+ RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ blurredGhostGraphics.setColor(Color.black);
+ blurredGhostGraphics.translate(kernelSize, kernelSize);
+ int step = kernelSize > 5 ? 2 : 1;
+ for (int strokeSize = 2 * kernelSize - 1; strokeSize > 0; strokeSize -= step) {
+ float transp = 1.0f - strokeSize / (2.0f * kernelSize);
+ blurredGhostGraphics.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC, transp));
+ blurredGhostGraphics.setStroke(new BasicStroke(strokeSize));
+ blurredGhostGraphics.draw(contour);
+ }
+ blurredGhostGraphics.dispose();
+
+ // millis003 = System.nanoTime();
+
+ BufferedImage reverseGhostContour = SubstanceCoreUtilities
+ .getBlankImage(width + 2 * kernelSize, height + 2
+ * kernelSize);
+ Graphics2D reverseGraphics = (Graphics2D) reverseGhostContour
+ .getGraphics();
+ Color bottomShineColorTransp = new Color(bottomShineColor.getRed(),
+ bottomShineColor.getGreen(), bottomShineColor.getBlue(), 64);
+ GradientPaint gradientShine = new GradientPaint(0, kernelSize,
+ topShineColor, 0, kernelSize + shineHeight,
+ bottomShineColorTransp, true);
+ reverseGraphics.setPaint(gradientShine);
+ reverseGraphics.fillRect(0, kernelSize, width + 2 * kernelSize,
+ kernelSize + shineHeight);
+ reverseGraphics.setComposite(AlphaComposite.DstOut);
+ reverseGraphics.drawImage(blurredGhostContour, 0, 0, null);
+ // millis004 = System.nanoTime();
+
+ graphics.drawImage(reverseGhostContour, 0, 0, width - 1,
+ shineHeight, kernelSize, kernelSize,
+ kernelSize + width - 1, kernelSize + shineHeight, null);
+
+ BufferedImage overGhostContour = SubstanceCoreUtilities
+ .getBlankImage(width + 2 * kernelSize, height + 2
+ * kernelSize);
+ Graphics2D overGraphics = (Graphics2D) overGhostContour
+ .getGraphics();
+ overGraphics.setPaint(new GradientPaint(0, kernelSize,
+ topFillColor, 0, kernelSize + height / 2, midFillColorTop,
+ true));
+ overGraphics.fillRect(kernelSize, kernelSize, kernelSize + width,
+ kernelSize + shineHeight);
+ overGraphics.setComposite(AlphaComposite.DstIn);
+ overGraphics.drawImage(blurredGhostContour, 0, 0, null);
+ // millis005 = System.nanoTime();
+
+ graphics.drawImage(overGhostContour, 0, 0, width - 1, shineHeight,
+ kernelSize, kernelSize, kernelSize + width - 1, kernelSize
+ + shineHeight, null);
+ }
+
+ graphics.dispose();
+ // long millis006 = System.nanoTime();
+
+ // long millis2 = System.nanoTime();
+ // if (width * height > 5000) {
+ // System.out.println("new - " + width + "*" + height + " = "
+ // + format(millis2 - millis));
+ // System.out.println("\tfill : " + format(millis001 - millis000));
+ // System.out.println("\tcontour : " + format(millis003 - millis001));
+ // System.out.println("\trevert : " + format(millis004 - millis003));
+ // System.out.println("\toverlay : " + format(millis005 - millis004));
+ // System.out.println("\tborder : " + format(millis006 - millis005));
+ // }
+ }
+
+ /**
+ * Computes the color of the top portion of the fill. Override to provide
+ * different visual.
+ *
+ * @param fillScheme
+ * The fill scheme.
+ * @return The color of the top portion of the fill.
+ */
+ public Color getTopFillColor(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getTopFillColor(fillScheme);
+ }
+
+ /**
+ * Computes the color of the middle portion of the fill from the top.
+ * Override to provide different visual.
+ *
+ * @param fillScheme
+ * The fill scheme.
+ * @return The color of the middle portion of the fill from the top.
+ */
+ public Color getMidFillColorTop(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getMidFillColor(fillScheme);
+ }
+
+ /**
+ * Computes the color of the middle portion of the fill from the bottom.
+ * Override to provide different visual.
+ *
+ * @param fillScheme
+ * The fill scheme.
+ * @return The color of the middle portion of the fill from the bottom.
+ */
+ public Color getMidFillColorBottom(SubstanceColorScheme fillScheme) {
+
+ return this.getMidFillColorTop(fillScheme);
+ }
+
+ /**
+ * Computes the color of the bottom portion of the fill. Override to provide
+ * different visual.
+ *
+ * @param fillScheme
+ * The fill scheme.
+ * @return The color of the bottom portion of the fill.
+ */
+ public Color getBottomFillColor(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getBottomFillColor(fillScheme);
+ }
+
+ /**
+ * Computes the color of the top portion of the shine. Override to provide
+ * different visual.
+ *
+ * @param fillScheme
+ * The fill scheme.
+ * @return The color of the top portion of the shine.
+ */
+ public Color getTopShineColor(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getTopShineColor(fillScheme);
+ }
+
+ /**
+ * Computes the color of the bottom portion of the shine. Override to
+ * provide different visual.
+ *
+ * @param fillScheme
+ * The fill scheme.
+ * @return The color of the bottom portion of the shine.
+ */
+ public Color getBottomShineColor(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getBottomShineColor(fillScheme);
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/SubduedFillPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/SubduedFillPainter.java
new file mode 100644
index 0000000..f5cf218
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/SubduedFillPainter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.fill;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Fill painter that returns images with subdued 3D gradient appearance. This
+ * class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubduedFillPainter extends StandardFillPainter {
+ @Override
+ public String getDisplayName() {
+ return "Subdued";
+ }
+
+ @Override
+ public Color getTopFillColor(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(super
+ .getTopFillColor(fillScheme), this
+ .getMidFillColorTop(fillScheme), 0.3);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/SubstanceFillPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/SubstanceFillPainter.java
new file mode 100644
index 0000000..36453d5
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/fill/SubstanceFillPainter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.fill;
+
+import java.awt.*;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.trait.SubstanceTrait;
+
+/**
+ * Fill painter interface for <b>Substance</b> look and feel. This class is part
+ * of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SubstanceFillPainter extends SubstanceTrait {
+ /**
+ * Fills the contour that matches the specified parameters.
+ *
+ * @param g
+ * Graphics context.
+ * @param comp
+ * Component to paint.
+ * @param width
+ * Width of a UI component.
+ * @param height
+ * Height of a UI component.
+ * @param contour
+ * Contour of a UI component.
+ * @param isFocused
+ * Indication whether component owns the focus.
+ * @param fillScheme
+ * The fill color scheme.
+ * @param hasShine
+ * Indication whether the returned image should have a 3D shine
+ * spot in its top half.
+ */
+ public void paintContourBackground(Graphics g, Component comp, int width,
+ int height, Shape contour, boolean isFocused,
+ SubstanceColorScheme fillScheme, boolean hasShine);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/highlight/ClassicHighlightPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/highlight/ClassicHighlightPainter.java
new file mode 100644
index 0000000..05c5c17
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/highlight/ClassicHighlightPainter.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.highlight;
+
+import java.awt.*;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.painter.fill.ClassicFillPainter;
+
+/**
+ * Highlight painter that paints a classic gradient. This class is part of
+ * officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.3
+ */
+public class ClassicHighlightPainter implements SubstanceHighlightPainter {
+ /**
+ * The display name for the highlight painters of this class.
+ */
+ public static final String DISPLAY_NAME = "Classic";
+
+ /**
+ * Single gradient painter instance.
+ */
+ protected ClassicFillPainter painter;
+
+ /**
+ * Creates new classic title painter.
+ */
+ public ClassicHighlightPainter() {
+ this.painter = new ClassicFillPainter();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void paintHighlight(Graphics2D graphics, Component comp, int width,
+ int height, SubstanceColorScheme colorScheme) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.translate(-3, -3);
+ this.painter
+ .paintContourBackground(g2d, comp, width + 6, height + 6,
+ new Rectangle(width + 6, height + 6), false,
+ colorScheme, false);
+ g2d.dispose();
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/highlight/FractionBasedHighlightPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/highlight/FractionBasedHighlightPainter.java
new file mode 100644
index 0000000..e6fc853
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/highlight/FractionBasedHighlightPainter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.highlight;
+
+import java.awt.*;
+import java.awt.MultipleGradientPaint.CycleMethod;
+
+import org.pushingpixels.substance.api.ColorSchemeSingleColorQuery;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.painter.FractionBasedPainter;
+
+/**
+ * Highlight painter with fraction-based stops and a color query associated with
+ * each stop. This class allows creating multi-gradient highlights with exact
+ * control over which color is used at every gradient control point.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class FractionBasedHighlightPainter extends FractionBasedPainter
+ implements SubstanceHighlightPainter {
+ /**
+ * Creates a new fraction-based highlight painter.
+ *
+ * @param displayName
+ * The display name of this painter.
+ * @param fractions
+ * The fractions of this painter. Must be strictly increasing,
+ * starting from 0.0 and ending at 1.0.
+ * @param colorQueries
+ * The color queries of this painter. Must have the same size as
+ * the fractions array, and all entries must be non-
+ * <code>null</code>.
+ */
+ public FractionBasedHighlightPainter(String displayName, float[] fractions,
+ ColorSchemeSingleColorQuery[] colorQueries) {
+ super(displayName, fractions, colorQueries);
+ }
+
+ @Override
+ public void paintHighlight(Graphics2D graphics, Component comp, int width,
+ int height, SubstanceColorScheme colorScheme) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+
+ Color[] fillColors = new Color[this.fractions.length];
+ for (int i = 0; i < this.fractions.length; i++) {
+ fillColors[i] = this.colorQueries[i].query(colorScheme);
+ }
+
+ MultipleGradientPaint gradient = new LinearGradientPaint(0, 0, 0,
+ height, this.fractions, fillColors, CycleMethod.REPEAT);
+ g2d.setPaint(gradient);
+ g2d.fillRect(0, 0, width, height);
+ g2d.dispose();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/highlight/GlassHighlightPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/highlight/GlassHighlightPainter.java
new file mode 100644
index 0000000..43da701
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/highlight/GlassHighlightPainter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.highlight;
+
+import java.awt.*;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.painter.fill.GlassFillPainter;
+
+/**
+ * Highlight painter that paints a glass gradient. This class is part of
+ * officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.3
+ */
+public class GlassHighlightPainter implements SubstanceHighlightPainter {
+ /**
+ * The display name for the highlight painters of this class.
+ */
+ public static final String DISPLAY_NAME = "Glass";
+
+ /**
+ * Single gradient painter instance.
+ */
+ protected GlassFillPainter painter;
+
+ /**
+ * Creates new classic title painter.
+ */
+ public GlassHighlightPainter() {
+ this.painter = new GlassFillPainter();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return DISPLAY_NAME;
+ }
+
+ @Override
+ public void paintHighlight(Graphics2D graphics, Component comp, int width,
+ int height, SubstanceColorScheme colorScheme) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.translate(-3, -3);
+ this.painter
+ .paintContourBackground(g2d, comp, width + 6, height + 6,
+ new Rectangle(width + 6, height + 6), false,
+ colorScheme, false);
+ g2d.dispose();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/highlight/SubstanceHighlightPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/highlight/SubstanceHighlightPainter.java
new file mode 100644
index 0000000..2b46aa2
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/highlight/SubstanceHighlightPainter.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.highlight;
+
+import java.awt.Component;
+import java.awt.Graphics2D;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.trait.SubstanceTrait;
+
+/**
+ * Highlight painter interface for <b>Substance</b> look and feel. This class is
+ * part of officially supported API.<br>
+ * <br>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.3
+ */
+public interface SubstanceHighlightPainter extends SubstanceTrait {
+ /**
+ * Paints the highlight.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param width
+ * Width.
+ * @param height
+ * Height.
+ * @param colorScheme
+ * The color scheme for painting the highlight.
+ */
+ public void paintHighlight(Graphics2D graphics, Component comp, int width,
+ int height, SubstanceColorScheme colorScheme);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/BottomLineOverlayPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/BottomLineOverlayPainter.java
new file mode 100644
index 0000000..7a2b386
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/BottomLineOverlayPainter.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.overlay;
+
+import java.awt.*;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.SubstancePainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+/**
+ * Overlay painter that paints a single line at the bottom edge of the relevant
+ * decoration area. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.3
+ */
+public final class BottomLineOverlayPainter implements SubstanceOverlayPainter {
+ /**
+ * Used to compute the color of the line painted by this overlay painter.
+ */
+ ColorSchemeSingleColorQuery colorSchemeQuery;
+
+ /**
+ * Creates a new overlay painter that paints a single line at the bottom
+ * edge of the relevant decoration area
+ *
+ * @param colorSchemeQuery
+ * Used to compute the color of the line painted by this overlay
+ * painter.
+ */
+ public BottomLineOverlayPainter(ColorSchemeSingleColorQuery colorSchemeQuery) {
+ this.colorSchemeQuery = colorSchemeQuery;
+ }
+
+ @Override
+ public void paintOverlay(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+ Component topMostWithSameDecorationAreaType = SubstancePainterUtils
+ .getTopMostParentWithDecorationAreaType(comp,
+ decorationAreaType);
+
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(comp);
+ float borderStrokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(fontSize);
+ graphics.setStroke(new BasicStroke(borderStrokeWidth));
+
+ SubstanceColorScheme colorScheme = // skin.getColorScheme(comp,
+ // ColorSchemeAssociationKind.SEPARATOR, ComponentState.DEFAULT);
+ // colorScheme =
+ skin.getBackgroundColorScheme(decorationAreaType);
+ graphics.setColor(this.colorSchemeQuery.query(colorScheme));
+ graphics.drawLine(0, topMostWithSameDecorationAreaType.getHeight()
+ - (int) borderStrokeWidth, width,
+ topMostWithSameDecorationAreaType.getHeight()
+ - (int) borderStrokeWidth);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Bottom Line";
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/BottomShadowOverlayPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/BottomShadowOverlayPainter.java
new file mode 100644
index 0000000..a3690c9
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/BottomShadowOverlayPainter.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.overlay;
+
+import java.awt.*;
+
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.painter.SubstancePainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Overlay painter that paints a few pixel-high drop shadow at the bottom edge
+ * of the relevant decoration area. This class is part of officially supported
+ * API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.3
+ */
+public final class BottomShadowOverlayPainter implements
+ SubstanceOverlayPainter {
+ /**
+ * Singleton instance.
+ */
+ private static BottomShadowOverlayPainter INSTANCE;
+
+ /**
+ * Returns the single instance of this class.
+ *
+ * @return Single instance of this class.
+ */
+ public synchronized static BottomShadowOverlayPainter getInstance() {
+ if (INSTANCE == null)
+ INSTANCE = new BottomShadowOverlayPainter();
+ return INSTANCE;
+ }
+
+ /**
+ * Private constructor to enforce that {@link #getInstance()} is the only
+ * way an application can get an instance of this class.
+ */
+ private BottomShadowOverlayPainter() {
+ }
+
+ @Override
+ public void paintOverlay(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+ Color shadowColor = SubstanceColorUtilities
+ .getBackgroundFillColor(comp).darker();
+
+ Component topMostWithSameDecorationAreaType = SubstancePainterUtils
+ .getTopMostParentWithDecorationAreaType(comp,
+ decorationAreaType);
+ int topHeight = topMostWithSameDecorationAreaType.getHeight();
+
+ Point inTopMost = SwingUtilities.convertPoint(comp, new Point(0, 0),
+ topMostWithSameDecorationAreaType);
+ int dy = inTopMost.y;
+
+ Graphics2D fillGraphics = (Graphics2D) graphics.create();
+ fillGraphics.translate(0, -dy);
+
+ int shadowHeight = 4;
+ GradientPaint fillPaint = new GradientPaint(0,
+ topHeight - shadowHeight, SubstanceColorUtilities
+ .getAlphaColor(shadowColor, 0), 0, topHeight,
+ SubstanceColorUtilities.getAlphaColor(shadowColor, 128));
+ fillGraphics.setPaint(fillPaint);
+ fillGraphics.fillRect(0, topHeight - shadowHeight, width, shadowHeight);
+ fillGraphics.dispose();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Bottom Shadow";
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/SubstanceOverlayPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/SubstanceOverlayPainter.java
new file mode 100644
index 0000000..428bf7f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/SubstanceOverlayPainter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.overlay;
+
+import java.awt.Component;
+import java.awt.Graphics2D;
+
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.trait.SubstanceTrait;
+
+/**
+ * Overlay painter interface for <b>Substance</b> look and feel. This class is
+ * part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.3
+ */
+public interface SubstanceOverlayPainter extends SubstanceTrait {
+ /**
+ * Paints the overlay.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param decorationAreaType
+ * Decoration area type. Must not be <code>null</code>.
+ * @param width
+ * Width.
+ * @param height
+ * Height.
+ * @param skin
+ * Skin for painting the overlay.
+ */
+ public void paintOverlay(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/TopBezelOverlayPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/TopBezelOverlayPainter.java
new file mode 100644
index 0000000..e50cb10
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/TopBezelOverlayPainter.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.overlay;
+
+import java.awt.*;
+
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.SubstancePainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+/**
+ * Overlay painter that paints a bezel line at the top edge of the relevant
+ * decoration area. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.3
+ */
+public final class TopBezelOverlayPainter implements SubstanceOverlayPainter {
+ /**
+ * Used to compute the color of the top line painted by this overlay
+ * painter.
+ */
+ ColorSchemeSingleColorQuery colorSchemeQueryTop;
+
+ /**
+ * Used to compute the color of the bottom line painted by this overlay
+ * painter.
+ */
+ ColorSchemeSingleColorQuery colorSchemeQueryBottom;
+
+ /**
+ * Creates a new overlay painter that paints a bezel line at the top edge of
+ * the relevant decoration area
+ *
+ * @param colorSchemeQueryTop
+ * Used to compute the color of the top line painted by this
+ * overlay painter.
+ * @param colorSchemeQueryBottom
+ * Used to compute the color of the top line painted by this
+ * overlay painter.
+ */
+ public TopBezelOverlayPainter(
+ ColorSchemeSingleColorQuery colorSchemeQueryTop,
+ ColorSchemeSingleColorQuery colorSchemeQueryBottom) {
+ this.colorSchemeQueryTop = colorSchemeQueryTop;
+ this.colorSchemeQueryBottom = colorSchemeQueryBottom;
+ }
+
+ @Override
+ public void paintOverlay(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+ Component topMostWithSameDecorationAreaType = SubstancePainterUtils
+ .getTopMostParentWithDecorationAreaType(comp,
+ decorationAreaType);
+
+ Point inTopMost = SwingUtilities.convertPoint(comp, new Point(0, 0),
+ topMostWithSameDecorationAreaType);
+ int dy = inTopMost.y;
+
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(comp);
+ float borderStrokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(fontSize);
+ graphics.setStroke(new BasicStroke(borderStrokeWidth));
+
+ SubstanceColorScheme colorScheme = skin
+ .getBackgroundColorScheme(decorationAreaType);
+ // skin.getColorScheme(comp,
+ // ColorSchemeAssociationKind.SEPARATOR, ComponentState.DEFAULT);
+ graphics.setColor(this.colorSchemeQueryTop.query(colorScheme));
+ graphics.drawLine(0, (int) (borderStrokeWidth) - dy - 1, width,
+ (int) (borderStrokeWidth) - dy - 1);
+
+ graphics.setColor(this.colorSchemeQueryBottom.query(colorScheme));
+ graphics.drawLine(0, (int) (2 * borderStrokeWidth) - dy - 1, width,
+ (int) (2 * borderStrokeWidth) - dy - 1);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Top Line Shadow";
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/TopLineOverlayPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/TopLineOverlayPainter.java
new file mode 100644
index 0000000..ed43d66
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/TopLineOverlayPainter.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.overlay;
+
+import java.awt.*;
+
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.SubstancePainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+/**
+ * Overlay painter that paints a single line at the bottom edge of the relevant
+ * decoration area. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.3
+ */
+public final class TopLineOverlayPainter implements SubstanceOverlayPainter {
+ /**
+ * Used to compute the color of the line painted by this overlay painter.
+ */
+ ColorSchemeSingleColorQuery colorSchemeQuery;
+
+ /**
+ * Creates a new overlay painter that paints a single line at the top edge
+ * of the relevant decoration area
+ *
+ * @param colorSchemeQuery
+ * Used to compute the color of the line painted by this overlay
+ * painter.
+ */
+ public TopLineOverlayPainter(ColorSchemeSingleColorQuery colorSchemeQuery) {
+ this.colorSchemeQuery = colorSchemeQuery;
+ }
+
+ @Override
+ public void paintOverlay(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+ Component topMostWithSameDecorationAreaType = SubstancePainterUtils
+ .getTopMostParentWithDecorationAreaType(comp,
+ decorationAreaType);
+
+ Point inTopMost = SwingUtilities.convertPoint(comp, new Point(0, 0),
+ topMostWithSameDecorationAreaType);
+ int dy = inTopMost.y;
+
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(comp);
+ float borderStrokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(fontSize);
+ graphics.setStroke(new BasicStroke(borderStrokeWidth));
+
+ SubstanceColorScheme colorScheme = skin
+ .getBackgroundColorScheme(decorationAreaType);
+ // skin.getColorScheme(comp,
+ // ColorSchemeAssociationKind.SEPARATOR, ComponentState.DEFAULT);
+ graphics.setColor(this.colorSchemeQuery.query(colorScheme));
+ graphics.drawLine(0, (int) borderStrokeWidth - dy - 1, width,
+ (int) borderStrokeWidth - dy - 1);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Top Line";
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/TopShadowOverlayPainter.java b/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/TopShadowOverlayPainter.java
new file mode 100644
index 0000000..ba4dbfe
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/painter/overlay/TopShadowOverlayPainter.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.painter.overlay;
+
+import java.awt.*;
+
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.painter.SubstancePainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Overlay painter that paints a few pixel-high drop shadow at the top edge of
+ * the relevant decoration area. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.3
+ */
+public final class TopShadowOverlayPainter implements SubstanceOverlayPainter {
+ /**
+ * Singleton instance.
+ */
+ private static TopShadowOverlayPainter INSTANCE;
+
+ /**
+ * Returns the single instance of this class.
+ *
+ * @return Single instance of this class.
+ */
+ public synchronized static TopShadowOverlayPainter getInstance() {
+ if (INSTANCE == null)
+ INSTANCE = new TopShadowOverlayPainter();
+ return INSTANCE;
+ }
+
+ /**
+ * Private constructor to enforce that {@link #getInstance()} is the only
+ * way an application can get an instance of this class.
+ */
+ private TopShadowOverlayPainter() {
+ }
+
+ @Override
+ public void paintOverlay(Graphics2D graphics, Component comp,
+ DecorationAreaType decorationAreaType, int width, int height,
+ SubstanceSkin skin) {
+ Color shadowColor = SubstanceColorUtilities
+ .getBackgroundFillColor(comp).darker();
+
+ // need to handle components "embedded" in other components
+ Component topMostWithSameDecorationAreaType = SubstancePainterUtils
+ .getTopMostParentWithDecorationAreaType(comp,
+ decorationAreaType);
+ Point inTopMost = SwingUtilities.convertPoint(comp, new Point(0, 0),
+ topMostWithSameDecorationAreaType);
+ int dy = inTopMost.y;
+
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.translate(0, -dy);
+ g2d.setPaint(new GradientPaint(0, 0, SubstanceColorUtilities
+ .getAlphaColor(shadowColor, 160), 0, 4, SubstanceColorUtilities
+ .getAlphaColor(shadowColor, 16)));
+ g2d.fillRect(0, 0, comp.getWidth(), 4);
+ g2d.dispose();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Top Shadow";
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultComboBoxRenderer.java b/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultComboBoxRenderer.java
new file mode 100644
index 0000000..d20e526
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultComboBoxRenderer.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.renderers;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.util.Map;
+
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.ColorUIResource;
+import javax.swing.plaf.ComboBoxUI;
+import javax.swing.plaf.ListUI;
+
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.ComponentStateFacet;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.ModelStateInfo;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.StateContributionInfo;
+import org.pushingpixels.substance.internal.ui.SubstanceComboBoxUI;
+import org.pushingpixels.substance.internal.ui.SubstanceListUI;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceStripingUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceTextUtilities;
+import org.pushingpixels.substance.internal.utils.UpdateOptimizationInfo;
+
+/**
+ * Renderer for combo boxes.
+ *
+ * @author Kirill Grouchnikov
+ */
+ at SubstanceRenderer
+public class SubstanceDefaultComboBoxRenderer extends
+ SubstanceDefaultListCellRenderer {
+ /**
+ * The associated combo box.
+ */
+ private JComboBox combo;
+
+ /**
+ * Simple constructor.
+ *
+ * @param combo
+ * The associated combo box.
+ */
+ public SubstanceDefaultComboBoxRenderer(JComboBox combo) {
+ super();
+ this.combo = combo;
+ // this.setOpaque(true);
+
+ Insets ins = SubstanceSizeUtils
+ .getListCellRendererInsets(SubstanceSizeUtils
+ .getComponentFontSize(combo));
+ this
+ .setBorder(new EmptyBorder(ins.top, ins.left, ins.bottom,
+ ins.right));
+ //
+ // Insets i = b.getBorderInsets(combo);
+ // System.out.println("Combo inner - " + combo.getFont().getSize() +" :
+ // "
+ // + i.top + ", " + i.left + ", " + i.bottom + ", " + i.right);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.ListCellRenderer#getListCellRendererComponent(javax.swing
+ * .JList, java.lang.Object, int, boolean, boolean)
+ */
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+
+ JComponent result = (JComponent) super.getListCellRendererComponent(
+ list, value, index, isSelected, cellHasFocus);
+
+ ListUI baseListUI = list.getUI();
+ ComboBoxUI baseComboUI = combo.getUI();
+ if ((baseListUI instanceof SubstanceListUI)
+ && (baseComboUI instanceof SubstanceComboBoxUI)) {
+ SubstanceListUI listUI = (SubstanceListUI) baseListUI;
+ SubstanceComboBoxUI comboUI = (SubstanceComboBoxUI) baseComboUI;
+
+ // special case for the combobox. The selected value is
+ // painted using the renderer of the list, and the index
+ // is -1.
+ if (index == -1) {
+ StateTransitionTracker stateTransitionTracker = comboUI
+ .getTransitionTracker();
+ ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ ComponentState currState = modelStateInfo.getCurrModelState();
+ float comboAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ combo, currState);
+ Color fg = SubstanceTextUtilities
+ .getForegroundColor(combo, ((JLabel) result).getText(),
+ modelStateInfo, comboAlpha);
+ result.setForeground(fg);
+ } else {
+ // use highlight color scheme for selected and rollover
+ // elements in the drop down list
+ StateTransitionTracker.ModelStateInfo modelStateInfo = listUI
+ .getModelStateInfo(index, result);
+ ComponentState currState = listUI.getCellState(index, result);
+ if (modelStateInfo == null) {
+ SubstanceColorScheme scheme = getColorSchemeForState(list,
+ index, listUI, currState);
+ // SubstanceColorScheme scheme =
+ // SubstanceColorSchemeUtilities
+ // .getColorScheme(list, kindForCurr, currState);
+ result.setForeground(new ColorUIResource(scheme
+ .getForegroundColor()));
+ } else {
+ Map<ComponentState, StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ SubstanceColorScheme colorScheme = getColorSchemeForState(
+ list, index, listUI, currState);
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ super.setForeground(new ColorUIResource(colorScheme
+ .getForegroundColor()));
+ } else {
+ float aggrRed = 0.0f;
+ float aggrGreen = 0.0f;
+ float aggrBlue = 0.0f;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : modelStateInfo
+ .getStateContributionMap().entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ float activeContribution = activeEntry.getValue()
+ .getContribution();
+ if (activeContribution == 0.0f)
+ continue;
+
+ SubstanceColorScheme scheme = getColorSchemeForState(
+ list, index, listUI, activeState);
+ // SubstanceColorScheme scheme = (activeState ==
+ // ComponentState.DEFAULT) ? listUI
+ // .getDefaultColorScheme()
+ // : listUI.getHighlightColorScheme(activeState);
+ // if (scheme == null) {
+ // scheme = (activeState == ComponentState.DEFAULT)
+ // ?
+ // SubstanceColorSchemeUtilities
+ // .getColorScheme(list, activeState)
+ // : SubstanceColorSchemeUtilities
+ // .getColorScheme(
+ // list,
+ // ColorSchemeAssociationKind.HIGHLIGHT,
+ // activeState);
+ // }
+ Color schemeFg = scheme.getForegroundColor();
+ aggrRed += schemeFg.getRed() * activeContribution;
+ aggrGreen += schemeFg.getGreen()
+ * activeContribution;
+ aggrBlue += schemeFg.getBlue() * activeContribution;
+ }
+ result
+ .setForeground(new ColorUIResource(new Color(
+ (int) aggrRed, (int) aggrGreen,
+ (int) aggrBlue)));
+ }
+ }
+ }
+
+ SubstanceStripingUtils.applyStripedBackground(list, index, this);
+ }
+ result.setEnabled(combo.isEnabled());
+ return result;
+ }
+
+ private SubstanceColorScheme getColorSchemeForState(JList list, int index,
+ SubstanceListUI listUI, ComponentState state) {
+ boolean toUseHighlightKindForCurrState = (index >= 0)
+ && (state.isFacetActive(ComponentStateFacet.ROLLOVER) || state
+ .isFacetActive(ComponentStateFacet.SELECTION));
+ UpdateOptimizationInfo updateOptimizationInfo = listUI
+ .getUpdateOptimizationInfo();
+ if (toUseHighlightKindForCurrState) {
+ if (updateOptimizationInfo == null) {
+ return SubstanceColorSchemeUtilities.getColorScheme(list,
+ ColorSchemeAssociationKind.HIGHLIGHT, state);
+ } else {
+ return updateOptimizationInfo.getHighlightColorScheme(state);
+ }
+ } else {
+ if (updateOptimizationInfo == null) {
+ return SubstanceColorSchemeUtilities
+ .getColorScheme(list, state);
+ } else {
+ return updateOptimizationInfo.getDefaultScheme();
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#getPreferredSize()
+ */
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size;
+
+ if ((this.getText() == null) || (this.getText().equals(""))) {
+ this.setText(" ");
+ size = super.getPreferredSize();
+ this.setText("");
+ } else {
+ size = super.getPreferredSize();
+ }
+
+ return size;
+ }
+
+ /**
+ * UI resource for renderer (does nothing yet).
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class SubstanceUIResource extends
+ SubstanceDefaultComboBoxRenderer implements
+ javax.swing.plaf.UIResource {
+ /**
+ * Creates a new renderer resource.
+ *
+ * @param combo
+ * Combobox.
+ */
+ public SubstanceUIResource(JComboBox combo) {
+ super(combo);
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultListCellRenderer.java b/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultListCellRenderer.java
new file mode 100644
index 0000000..fd24930
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultListCellRenderer.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.renderers;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.util.Map;
+
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.Icon;
+import javax.swing.JList;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.ColorUIResource;
+import javax.swing.plaf.ListUI;
+
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.StateContributionInfo;
+import org.pushingpixels.substance.internal.ui.SubstanceListUI;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceStripingUtils;
+import org.pushingpixels.substance.internal.utils.UpdateOptimizationInfo;
+
+/**
+ * Default renderer for list cells.
+ *
+ * @author Kirill Grouchnikov
+ */
+ at SubstanceRenderer
+public class SubstanceDefaultListCellRenderer extends DefaultListCellRenderer {
+ /**
+ * Constructs a default renderer object for an item in a list.
+ */
+ public SubstanceDefaultListCellRenderer() {
+ super();
+ this.putClientProperty(SubstanceLookAndFeel.COLORIZATION_FACTOR, 1.0);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.ListCellRenderer#getListCellRendererComponent(javax.swing
+ * .JList, java.lang.Object, int, boolean, boolean)
+ */
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ this.setComponentOrientation(list.getComponentOrientation());
+
+ ListUI listUI = list.getUI();
+ if (listUI instanceof SubstanceListUI) {
+ SubstanceListUI ui = (SubstanceListUI) listUI;
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = ui
+ .getModelStateInfo(index, this);
+ ComponentState currState = ui.getCellState(index, this);
+
+ // special case for drop location
+ JList.DropLocation dropLocation = list.getDropLocation();
+ boolean isDropLocation = (dropLocation != null
+ && !dropLocation.isInsert() && dropLocation.getIndex() == index);
+
+ if (!isDropLocation && (modelStateInfo != null)) {
+ Map<ComponentState, StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ SubstanceColorScheme colorScheme = getColorSchemeForState(list,
+ ui, currState);
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ super.setForeground(new ColorUIResource(colorScheme
+ .getForegroundColor()));
+ } else {
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : modelStateInfo
+ .getStateContributionMap().entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ SubstanceColorScheme scheme = getColorSchemeForState(
+ list, ui, activeState);
+ Color schemeFg = scheme.getForegroundColor();
+ float contribution = activeEntry.getValue()
+ .getContribution();
+ aggrRed += schemeFg.getRed() * contribution;
+ aggrGreen += schemeFg.getGreen() * contribution;
+ aggrBlue += schemeFg.getBlue() * contribution;
+ }
+ super.setForeground(new ColorUIResource(new Color(
+ (int) aggrRed, (int) aggrGreen, (int) aggrBlue)));
+ }
+ } else {
+ SubstanceColorScheme scheme = getColorSchemeForState(list, ui,
+ currState);
+ if (isDropLocation) {
+ scheme = SubstanceColorSchemeUtilities.getColorScheme(list,
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ currState);
+ }
+ super.setForeground(new ColorUIResource(scheme
+ .getForegroundColor()));
+ }
+ } else {
+ if (isSelected) {
+ this.setForeground(list.getSelectionForeground());
+ } else {
+ this.setForeground(list.getForeground());
+ }
+ }
+
+ if (SubstanceLookAndFeel.isCurrentLookAndFeel()
+ && (list.getLayoutOrientation() == JList.VERTICAL))
+ SubstanceStripingUtils.applyStripedBackground(list, index, this);
+
+ if (value instanceof Icon) {
+ this.setIcon((Icon) value);
+ this.setText("");
+ } else {
+ this.setIcon(null);
+ this.setText((value == null) ? "" : value.toString());
+ }
+
+ this.setEnabled(list.isEnabled());
+ this.setFont(list.getFont());
+
+ Insets ins = SubstanceSizeUtils
+ .getListCellRendererInsets(SubstanceSizeUtils
+ .getComponentFontSize(list));
+ this
+ .setBorder(new EmptyBorder(ins.top, ins.left, ins.bottom,
+ ins.right));
+
+ this.setOpaque(false);
+ return this;
+ }
+
+ private SubstanceColorScheme getColorSchemeForState(JList list,
+ SubstanceListUI ui, ComponentState state) {
+ UpdateOptimizationInfo updateOptimizationInfo = ui
+ .getUpdateOptimizationInfo();
+ if (state == ComponentState.ENABLED) {
+ if (updateOptimizationInfo == null) {
+ return SubstanceColorSchemeUtilities
+ .getColorScheme(list, state);
+ } else {
+ return updateOptimizationInfo.getDefaultScheme();
+ }
+ } else {
+ if (updateOptimizationInfo == null) {
+ return SubstanceColorSchemeUtilities.getColorScheme(list,
+ ColorSchemeAssociationKind.HIGHLIGHT, state);
+ } else {
+ return updateOptimizationInfo.getHighlightColorScheme(state);
+ }
+ }
+ }
+
+ /**
+ * UI resource for renderer (does nothing yet).
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class SubstanceUIResource extends
+ SubstanceDefaultListCellRenderer implements
+ javax.swing.plaf.UIResource {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.ListCellRenderer#getListCellRendererComponent(javax.swing
+ * .JList, java.lang.Object, int, boolean, boolean)
+ */
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ return super.getListCellRendererComponent(list, value, index,
+ isSelected, cellHasFocus);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paint(java.awt.Graphics)
+ */
+ @Override
+ public final void paint(Graphics g) {
+ super.paint(g);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
+ */
+ @Override
+ protected final void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Container#paintComponents(java.awt.Graphics)
+ */
+ @Override
+ public final void paintComponents(Graphics g) {
+ super.paintComponents(g);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paintBorder(java.awt.Graphics)
+ */
+ @Override
+ protected final void paintBorder(Graphics g) {
+ super.paintBorder(g);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultTableCellRenderer.java b/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultTableCellRenderer.java
new file mode 100644
index 0000000..1de9a04
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultTableCellRenderer.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.renderers;
+
+import java.awt.*;
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.plaf.ColorUIResource;
+import javax.swing.plaf.TableUI;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableCellRenderer;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.StateContributionInfo;
+import org.pushingpixels.substance.internal.ui.SubstanceTableUI;
+import org.pushingpixels.substance.internal.ui.SubstanceTableUI.TableCellId;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.border.SubstanceTableCellBorder;
+
+/**
+ * Default renderer for table cells.
+ *
+ * @author Kirill Grouchnikov
+ */
+ at SubstanceRenderer
+public class SubstanceDefaultTableCellRenderer extends DefaultTableCellRenderer {
+ /**
+ * Renderer for boolean columns.
+ *
+ * @author Kirill Grouchnikov
+ */
+ @SubstanceRenderer
+ public static class BooleanRenderer extends JCheckBox implements
+ TableCellRenderer {
+ /**
+ * Border for cells that do not have focus.
+ */
+ private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
+
+ /**
+ * Creates a new renderer for boolean columns.
+ */
+ public BooleanRenderer() {
+ super();
+ this.setHorizontalAlignment(SwingConstants.CENTER);
+ this.setBorderPainted(true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.table.TableCellRenderer#getTableCellRendererComponent
+ * (javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
+ */
+ @Override
+ public Component getTableCellRendererComponent(JTable table,
+ Object value, boolean isSelected, boolean hasFocus, int row,
+ int column) {
+ if (isSelected) {
+ this.setForeground(table.getSelectionForeground());
+ // super.setBackground(table.getSelectionBackground());
+ } else {
+ this.setForeground(table.getForeground());
+ }
+ SubstanceStripingUtils.applyStripedBackground(table, row, this);
+
+ this.setSelected(((value != null) && (Boolean) value));
+ this.setEnabled(table.isEnabled());
+
+ TableUI tableUI = table.getUI();
+ if (tableUI instanceof SubstanceTableUI) {
+ SubstanceTableUI ui = (SubstanceTableUI) tableUI;
+
+ // Recompute the focus indication to prevent flicker - JTable
+ // registers a listener on selection changes and repaints the
+ // relevant cell before our listener (in TableUI) gets the
+ // chance to start the fade sequence. The result is that the
+ // first frame uses full opacity, and the next frame starts the
+ // fade sequence. So, we use the UI delegate to compute the
+ // focus indication.
+ hasFocus = ui.isFocusedCell(row, column);
+
+ TableCellId cellFocusId = new TableCellId(row, column);
+
+ StateTransitionTracker stateTransitionTracker = ui
+ .getStateTransitionTracker(cellFocusId);
+ if (hasFocus || (stateTransitionTracker != null)) {
+ SubstanceTableCellBorder border = new SubstanceTableCellBorder(
+ new Insets(0, 0, 0, 0), ui, cellFocusId);
+ if (stateTransitionTracker != null) {
+ border.setAlpha(stateTransitionTracker
+ .getFocusStrength(hasFocus));
+ }
+ this.setBorder(border);
+ } else {
+ this.setBorder(BooleanRenderer.noFocusBorder);
+ }
+ } else {
+ if (hasFocus) {
+ this.setBorder(UIManager
+ .getBorder("Table.focusCellHighlightBorder"));
+ } else {
+ this.setBorder(BooleanRenderer.noFocusBorder);
+ }
+ }
+
+ this.setOpaque(false);
+
+ return this;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paint(java.awt.Graphics)
+ */
+ @Override
+ public final void paint(Graphics g) {
+ super.paint(g);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
+ */
+ @Override
+ protected final void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ }
+
+ @Override
+ protected final void paintBorder(Graphics g) {
+ super.paintBorder(g);
+ }
+
+ @Override
+ public final void paintComponents(Graphics g) {
+ }
+ }
+
+ /**
+ * Renderer for icon columns.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class IconRenderer extends SubstanceDefaultTableCellRenderer {
+ /**
+ * Creates a new renderer for icon columns.
+ */
+ public IconRenderer() {
+ super();
+ this.setHorizontalAlignment(SwingConstants.CENTER);
+ }
+
+ @Override
+ public void setValue(Object value) {
+ this.setIcon((value instanceof Icon) ? (Icon) value : null);
+ this.setText(null);
+ }
+ }
+
+ /**
+ * Renderer for number columns.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class NumberRenderer extends
+ SubstanceDefaultTableCellRenderer {
+ /**
+ * Creates a new renderer for number columns.
+ */
+ public NumberRenderer() {
+ super();
+ this.setHorizontalAlignment(SwingConstants.RIGHT);
+ }
+ }
+
+ /**
+ * Renderer for double columns.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class DoubleRenderer extends NumberRenderer {
+ /**
+ * Number formatter for this renderer.
+ */
+ NumberFormat formatter;
+
+ /**
+ * Creates a new renderer for double columns.
+ */
+ public DoubleRenderer() {
+ super();
+ }
+
+ @Override
+ public void setValue(Object value) {
+ if (this.formatter == null) {
+ this.formatter = NumberFormat.getInstance();
+ }
+ this.setText((value == null) ? "" : this.formatter.format(value));
+ }
+ }
+
+ /**
+ * Renderer for date columns.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class DateRenderer extends SubstanceDefaultTableCellRenderer {
+ /**
+ * Date formatter for this renderer.
+ */
+ DateFormat formatter;
+
+ /**
+ * Creates a new renderer for date columns.
+ */
+ public DateRenderer() {
+ super();
+ }
+
+ @Override
+ public void setValue(Object value) {
+ if (this.formatter == null) {
+ this.formatter = DateFormat.getDateInstance();
+ }
+ this.setText((value == null) ? "" : this.formatter.format(value));
+ }
+ }
+
+ /**
+ * Creates a default opaque table cell renderer.
+ */
+ public SubstanceDefaultTableCellRenderer() {
+ this.putClientProperty(SubstanceLookAndFeel.COLORIZATION_FACTOR, 1.0);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.table.TableCellRenderer#getTableCellRendererComponent(javax
+ * .swing.JTable, java.lang.Object, boolean, boolean, int, int)
+ */
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value,
+ boolean isSelected, boolean hasFocus, int row, int column) {
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return super.getTableCellRendererComponent(table, value,
+ isSelected, hasFocus, row, column);
+
+ TableUI tableUI = table.getUI();
+ SubstanceTableUI ui = (SubstanceTableUI) tableUI;
+
+ // Recompute the focus indication to prevent flicker - JTable
+ // registers a listener on selection changes and repaints the
+ // relevant cell before our listener (in TableUI) gets the
+ // chance to start the fade sequence. The result is that the
+ // first frame uses full opacity, and the next frame starts the
+ // fade sequence. So, we use the UI delegate to compute the
+ // focus indication.
+ hasFocus = ui.isFocusedCell(row, column);
+
+ TableCellId cellId = new TableCellId(row, column);
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = ui
+ .getModelStateInfo(cellId);
+ ComponentState currState = ui.getCellState(cellId);
+ // special case for drop location
+ JTable.DropLocation dropLocation = table.getDropLocation();
+ boolean isDropLocation = (dropLocation != null
+ && !dropLocation.isInsertRow()
+ && !dropLocation.isInsertColumn()
+ && dropLocation.getRow() == row && dropLocation.getColumn() == column);
+
+ if (!isDropLocation && (modelStateInfo != null)) {
+ if (ui.hasRolloverAnimations() || ui.hasSelectionAnimations()) {
+ Map<ComponentState, StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ SubstanceColorScheme colorScheme = getColorSchemeForState(
+ table, ui, currState);
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ super.setForeground(new ColorUIResource(colorScheme
+ .getForegroundColor()));
+ } else {
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : modelStateInfo
+ .getStateContributionMap().entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ SubstanceColorScheme scheme = getColorSchemeForState(
+ table, ui, activeState);
+ Color schemeFg = scheme.getForegroundColor();
+ float contribution = activeEntry.getValue()
+ .getContribution();
+ aggrRed += schemeFg.getRed() * contribution;
+ aggrGreen += schemeFg.getGreen() * contribution;
+ aggrBlue += schemeFg.getBlue() * contribution;
+ }
+ super.setForeground(new ColorUIResource(new Color(
+ (int) aggrRed, (int) aggrGreen, (int) aggrBlue)));
+ }
+ } else {
+ SubstanceColorScheme scheme = getColorSchemeForState(table, ui,
+ currState);
+ super.setForeground(new ColorUIResource(scheme
+ .getForegroundColor()));
+ }
+ } else {
+ SubstanceColorScheme scheme = getColorSchemeForState(table, ui,
+ currState);
+ if (isDropLocation) {
+ scheme = SubstanceColorSchemeUtilities.getColorScheme(table,
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT, currState);
+ }
+ super
+ .setForeground(new ColorUIResource(scheme
+ .getForegroundColor()));
+ }
+
+ SubstanceStripingUtils.applyStripedBackground(table, row, this);
+
+ this.setFont(table.getFont());
+
+ TableCellId cellFocusId = new TableCellId(row, column);
+
+ StateTransitionTracker focusStateTransitionTracker = ui
+ .getStateTransitionTracker(cellFocusId);
+
+ Insets regInsets = ui.getCellRendererInsets();
+ if (hasFocus || (focusStateTransitionTracker != null)) {
+ SubstanceTableCellBorder border = new SubstanceTableCellBorder(
+ regInsets, ui, cellFocusId);
+
+ // System.out.println("[" + row + ":" + column + "] hasFocus : "
+ // + hasFocus + ", focusState : " + focusState);
+ if (focusStateTransitionTracker != null) {
+ border.setAlpha(focusStateTransitionTracker
+ .getFocusStrength(hasFocus));
+ }
+
+ // special case for tables with no grids
+ if (!table.getShowHorizontalLines()
+ && !table.getShowVerticalLines()) {
+ this.setBorder(new CompoundBorder(new EmptyBorder(table
+ .getRowMargin() / 2, 0, table.getRowMargin() / 2, 0),
+ border));
+ } else {
+ this.setBorder(border);
+ }
+ } else {
+ this.setBorder(new EmptyBorder(regInsets.top, regInsets.left,
+ regInsets.bottom, regInsets.right));
+ }
+
+ this.setValue(value);
+ this.setOpaque(false);
+ this.setEnabled(table.isEnabled());
+ return this;
+ }
+
+ private SubstanceColorScheme getColorSchemeForState(JTable table,
+ SubstanceTableUI ui, ComponentState state) {
+ UpdateOptimizationInfo updateOptimizationInfo = ui
+ .getUpdateOptimizationInfo();
+ if (state == ComponentState.ENABLED) {
+ if (updateOptimizationInfo == null) {
+ return SubstanceColorSchemeUtilities.getColorScheme(table,
+ state);
+ } else {
+ return updateOptimizationInfo.getDefaultScheme();
+ }
+ } else {
+ if (updateOptimizationInfo == null) {
+ return SubstanceColorSchemeUtilities.getColorScheme(table,
+ ColorSchemeAssociationKind.HIGHLIGHT, state);
+ } else {
+ return updateOptimizationInfo.getHighlightColorScheme(state);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paint(java.awt.Graphics)
+ */
+ @Override
+ public final void paint(Graphics g) {
+ super.paint(g);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
+ */
+ @Override
+ protected final void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ }
+
+ @Override
+ protected final void paintBorder(Graphics g) {
+ super.paintBorder(g);
+ }
+
+ @Override
+ public final void paintComponents(Graphics g) {
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultTableHeaderCellRenderer.java b/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultTableHeaderCellRenderer.java
new file mode 100644
index 0000000..ec1240c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultTableHeaderCellRenderer.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.renderers;
+
+import java.awt.*;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.*;
+import javax.swing.table.*;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.StateContributionInfo;
+import org.pushingpixels.substance.internal.ui.SubstanceTableHeaderUI;
+import org.pushingpixels.substance.internal.ui.SubstanceTableUI;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Default renderer for table header cells.
+ *
+ * @author Kirill Grouchnikov
+ */
+ at SubstanceRenderer
+public class SubstanceDefaultTableHeaderCellRenderer extends
+ DefaultTableCellRenderer implements UIResource {
+ /**
+ * Creates a new cell renderer.
+ */
+ public SubstanceDefaultTableHeaderCellRenderer() {
+ setHorizontalAlignment(JLabel.CENTER);
+ this.putClientProperty(SubstanceLookAndFeel.COLORIZATION_FACTOR, 1.0);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.table.TableCellRenderer#getTableCellRendererComponent(javax
+ * .swing.JTable, java.lang.Object, boolean, boolean, int, int)
+ */
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value,
+ boolean isSelected, boolean hasFocus, int row, int column) {
+ if (table == null) {
+ setBorder(DefaultTableCellRenderer.noFocusBorder);
+ setValue(value);
+ setOpaque(false);
+ return this;
+ }
+
+ if (table.getTableHeader() == null) {
+ return super.getTableCellRendererComponent(table, value,
+ isSelected, hasFocus, row, column);
+ }
+
+ JTableHeader tableHeader = table.getTableHeader();
+ TableHeaderUI tableHeaderUI = tableHeader.getUI();
+ if (SubstanceLookAndFeel.isCurrentLookAndFeel()
+ && (tableHeaderUI instanceof SubstanceTableHeaderUI)) {
+ SubstanceTableHeaderUI ui = (SubstanceTableHeaderUI) tableHeaderUI;
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = ui
+ .getModelStateInfo(column);
+ ComponentState currState = ui.getColumnState(column);
+
+ if (modelStateInfo != null) {
+ Map<ComponentState, StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ SubstanceColorScheme colorScheme = getColorSchemeForState(
+ tableHeader, currState);
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ super.setForeground(new ColorUIResource(colorScheme
+ .getForegroundColor()));
+ } else {
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : modelStateInfo
+ .getStateContributionMap().entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ SubstanceColorScheme scheme = getColorSchemeForState(
+ tableHeader, activeState);
+ Color schemeFg = scheme.getForegroundColor();
+ float contribution = activeEntry.getValue()
+ .getContribution();
+ aggrRed += schemeFg.getRed() * contribution;
+ aggrGreen += schemeFg.getGreen() * contribution;
+ aggrBlue += schemeFg.getBlue() * contribution;
+ }
+ super.setForeground(new ColorUIResource(new Color(
+ (int) aggrRed, (int) aggrGreen, (int) aggrBlue)));
+ }
+ } else {
+ SubstanceColorScheme scheme = getColorSchemeForState(
+ tableHeader, currState);
+ super.setForeground(new ColorUIResource(scheme
+ .getForegroundColor()));
+ }
+ } else {
+ super.setForeground(table.getForeground());
+ }
+
+ this.setBackground(tableHeader.getBackground());
+
+ // fix for issue 319 - using font from the table header
+ if (tableHeader.getFont() != null) {
+ setFont(tableHeader.getFont());
+ } else {
+ setFont(table.getFont());
+ }
+
+ TableUI tableUI = table.getUI();
+ if (SubstanceLookAndFeel.isCurrentLookAndFeel()
+ && (tableUI instanceof SubstanceTableUI)) {
+ this.setBorder(new EmptyBorder(((SubstanceTableUI) tableUI)
+ .getCellRendererInsets()));
+ }
+
+ this.setValue(value);
+ this.setOpaque(false);
+
+ this.setEnabled(tableHeader.isEnabled() && table.isEnabled());
+
+ // fix for defect 242 - not showing sort icon
+ if (SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ this.setIcon(null);
+ RowSorter<? extends TableModel> rowSorter = table.getRowSorter();
+ if (rowSorter != null) {
+ setHorizontalTextPosition(JLabel.LEADING);
+ java.util.List<? extends RowSorter.SortKey> sortKeys = rowSorter
+ .getSortKeys();
+ Icon sortIcon = null;
+ SubstanceColorScheme scheme = null;
+ if (tableHeaderUI instanceof SubstanceTableHeaderUI) {
+ SubstanceTableHeaderUI ui = (SubstanceTableHeaderUI) tableHeaderUI;
+ ComponentState state = ui.getColumnState(column);
+ ColorSchemeAssociationKind colorSchemeAssociationKind = (state == ComponentState.ENABLED) ? ColorSchemeAssociationKind.MARK
+ : ColorSchemeAssociationKind.HIGHLIGHT_MARK;
+ scheme = SubstanceColorSchemeUtilities.getColorScheme(
+ tableHeader, colorSchemeAssociationKind, state);
+ } else {
+ scheme = SubstanceColorSchemeUtilities.getColorScheme(
+ tableHeader, ComponentState.ENABLED);
+ }
+
+ if (sortKeys.size() > 0
+ && sortKeys.get(0).getColumn() == table
+ .convertColumnIndexToModel(column)) {
+ switch (sortKeys.get(0).getSortOrder()) {
+ case ASCENDING:
+ sortIcon = SubstanceImageCreator.getArrowIcon(
+ SubstanceSizeUtils
+ .getComponentFontSize(tableHeader),
+ SwingConstants.NORTH, scheme);
+ break;
+ case DESCENDING:
+ sortIcon = SubstanceImageCreator.getArrowIcon(
+ SubstanceSizeUtils
+ .getComponentFontSize(tableHeader),
+ SwingConstants.SOUTH, scheme);
+ break;
+ case UNSORTED:
+ sortIcon = null;
+ }
+ this.setIcon(sortIcon);
+ }
+ }
+ }
+
+ return this;
+ }
+
+ private SubstanceColorScheme getColorSchemeForState(
+ JTableHeader tableHeader, ComponentState activeState) {
+ SubstanceColorScheme scheme = (activeState == ComponentState.ENABLED) ? SubstanceColorSchemeUtilities
+ .getColorScheme(tableHeader, activeState)
+ : SubstanceColorSchemeUtilities.getColorScheme(tableHeader,
+ ColorSchemeAssociationKind.HIGHLIGHT, activeState);
+ return scheme;
+ }
+
+ // /**
+ // * Returns <code>true</code> if the specified column is sorted.
+ // *
+ // * @param table
+ // * Table.
+ // * @param columnIndex
+ // * Column index.
+ // * @return <code>true</code> if the specified column is sorted,
+ // * <code>false</code> otherwise.
+ // */
+ // public static boolean isColumnSorted(JTable table, int columnIndex) {
+ // RowSorter<? extends TableModel> rowSorter = table.getRowSorter();
+ // if (rowSorter != null) {
+ // java.util.List<? extends RowSorter.SortKey> sortKeys = rowSorter
+ // .getSortKeys();
+ // if (sortKeys.size() > 0
+ // && sortKeys.get(0).getColumn() == table
+ // .convertColumnIndexToModel(columnIndex)) {
+ // switch (sortKeys.get(0).getSortOrder()) {
+ // case ASCENDING:
+ // case DESCENDING:
+ // return true;
+ // }
+ // }
+ // }
+ // return false;
+ // }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paint(java.awt.Graphics)
+ */
+ @Override
+ public final void paint(Graphics g) {
+ super.paint(g);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
+ */
+ @Override
+ protected final void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultTreeCellRenderer.java b/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultTreeCellRenderer.java
new file mode 100644
index 0000000..2a654a8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceDefaultTreeCellRenderer.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.renderers;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.util.Map;
+
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.JTree;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.plaf.BorderUIResource;
+import javax.swing.plaf.ColorUIResource;
+import javax.swing.plaf.FontUIResource;
+import javax.swing.plaf.TreeUI;
+import javax.swing.tree.TreeCellRenderer;
+
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.StateContributionInfo;
+import org.pushingpixels.substance.internal.ui.SubstanceTreeUI;
+import org.pushingpixels.substance.internal.ui.SubstanceTreeUI.TreePathId;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+import org.pushingpixels.substance.internal.utils.SubstanceStripingUtils;
+
+/**
+ * Default renderer for tree cells.
+ *
+ * @author Kirill Grouchnikov
+ */
+ at SubstanceRenderer
+public class SubstanceDefaultTreeCellRenderer extends JLabel implements
+ TreeCellRenderer {
+ /** Last tree the renderer was painted in. */
+ private JTree tree;
+
+ /** Is the value currently selected. */
+ protected boolean selected;
+
+ /** True if has focus. */
+ protected boolean hasFocus;
+
+ /**
+ * Returns a new instance of SubstanceDefaultTreeCellRenderer. Alignment is
+ * set to left aligned. Icons and text color are determined from the
+ * UIManager.
+ */
+ public SubstanceDefaultTreeCellRenderer() {
+ this.setHorizontalAlignment(SwingConstants.LEFT);
+ this.putClientProperty(SubstanceLookAndFeel.COLORIZATION_FACTOR, 1.0);
+ }
+
+ /**
+ * Returns the default icon that is used to represent non-leaf nodes that
+ * are expanded.
+ *
+ * @return The default icon for non-leaf expanded nodes.
+ */
+ public Icon getDefaultOpenIcon() {
+ return UIManager.getIcon("Tree.openIcon");
+ }
+
+ /**
+ * Returns the default icon that is used to represent non-leaf nodes that
+ * are not expanded.
+ *
+ * @return The default icon for non-leaf non-expanded nodes.
+ */
+ public Icon getDefaultClosedIcon() {
+ return UIManager.getIcon("Tree.closedIcon");
+ }
+
+ /**
+ * Returns the default icon that is used to represent leaf nodes.
+ *
+ * @return The default icon for leaf nodes.
+ */
+ public Icon getDefaultLeafIcon() {
+ return UIManager.getIcon("Tree.leafIcon");
+ }
+
+ /**
+ * Subclassed to map <code>FontUIResource</code>s to null. If
+ * <code>font</code> is null, or a <code>FontUIResource</code>, this has the
+ * effect of letting the font of the JTree show through. On the other hand,
+ * if <code>font</code> is non-null, and not a <code>FontUIResource</code>,
+ * the font becomes <code>font</code>.
+ */
+ @Override
+ public void setFont(Font font) {
+ if (font instanceof FontUIResource)
+ font = null;
+ super.setFont(font);
+ }
+
+ /**
+ * Gets the font of this component.
+ *
+ * @return this component's font; if a font has not been set for this
+ * component, the font of its parent is returned
+ */
+ @Override
+ public Font getFont() {
+ Font font = super.getFont();
+
+ if ((font == null) && (this.tree != null)) {
+ // Strive to return a non-null value, otherwise the html support
+ // will typically pick up the wrong font in certain situations.
+ font = this.tree.getFont();
+ }
+ return font;
+ }
+
+ /**
+ * Configures the renderer based on the passed in components. The value is
+ * set from messaging the tree with <code>convertValueToText</code>, which
+ * ultimately invokes <code>toString</code> on <code>value</code>. The
+ * foreground color is set based on the selection and the icon is set based
+ * on on leaf and expanded.
+ */
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree, Object value,
+ boolean sel, boolean expanded, boolean leaf, int row,
+ boolean hasFocus) {
+ String stringValue = tree.convertValueToText(value, sel, expanded,
+ leaf, row, hasFocus);
+
+ this.tree = tree;
+ this.hasFocus = hasFocus;
+ this.setText(stringValue);
+
+ TreeUI treeUI = tree.getUI();
+ if (treeUI instanceof SubstanceTreeUI) {
+ SubstanceTreeUI ui = (SubstanceTreeUI) treeUI;
+ TreePathId pathId = new TreePathId(tree.getPathForRow(row));
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = ui
+ .getModelStateInfo(pathId);
+ ComponentState currState = ui.getPathState(pathId);
+
+ // special case for drop location
+ JTree.DropLocation dropLocation = tree.getDropLocation();
+ boolean isDropLocation = (dropLocation != null
+ && dropLocation.getChildIndex() == -1 && tree
+ .getRowForPath(dropLocation.getPath()) == row);
+
+ if (!isDropLocation && (modelStateInfo != null)) {
+ Map<ComponentState, StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ SubstanceColorScheme colorScheme = getColorSchemeForState(tree,
+ ui, currState);
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ super.setForeground(new ColorUIResource(colorScheme
+ .getForegroundColor()));
+ } else {
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : modelStateInfo
+ .getStateContributionMap().entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ SubstanceColorScheme scheme = getColorSchemeForState(
+ tree, ui, activeState);
+ Color schemeFg = scheme.getForegroundColor();
+ float contribution = activeEntry.getValue()
+ .getContribution();
+ aggrRed += schemeFg.getRed() * contribution;
+ aggrGreen += schemeFg.getGreen() * contribution;
+ aggrBlue += schemeFg.getBlue() * contribution;
+ }
+ super.setForeground(new ColorUIResource(new Color(
+ (int) aggrRed, (int) aggrGreen, (int) aggrBlue)));
+ }
+ } else {
+ SubstanceColorScheme scheme = getColorSchemeForState(tree, ui,
+ currState);
+ if (isDropLocation) {
+ scheme = SubstanceColorSchemeUtilities.getColorScheme(tree,
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ currState);
+ }
+ super.setForeground(new ColorUIResource(scheme
+ .getForegroundColor()));
+ }
+ } else {
+ if (sel)
+ this.setForeground(UIManager
+ .getColor("Tree.selectionForeground"));
+ else
+ this.setForeground(UIManager.getColor("Tree.textForeground"));
+ }
+
+ if (SubstanceLookAndFeel.isCurrentLookAndFeel())
+ SubstanceStripingUtils.applyStripedBackground(tree, row, this);
+
+ // There needs to be a way to specify disabled icons.
+ if (!tree.isEnabled()) {
+ this.setEnabled(false);
+ if (leaf) {
+ this.setDisabledIcon(SubstanceImageCreator
+ .toGreyscale(SubstanceImageCreator.makeTransparent(
+ tree, this.getDefaultLeafIcon(), 0.5)));
+ } else if (expanded) {
+ this.setDisabledIcon(SubstanceImageCreator
+ .toGreyscale(SubstanceImageCreator.makeTransparent(
+ tree, this.getDefaultOpenIcon(), 0.5)));
+ // setIcon(SubstanceImageCreator.toGreyscale(
+ // SubstanceImageCreator
+ // .makeTransparent(getDefaultOpenIcon(), 0.5)));
+ } else {
+ this.setDisabledIcon(SubstanceImageCreator
+ .toGreyscale(SubstanceImageCreator.makeTransparent(
+ tree, this.getDefaultClosedIcon(), 0.5)));
+ // setIcon(SubstanceImageCreator.toGreyscale(
+ // SubstanceImageCreator
+ // .makeTransparent(getDefaultClosedIcon(), 0.5)));
+ }
+ } else {
+ this.setEnabled(true);
+ if (leaf) {
+ this.setIcon(this.getDefaultLeafIcon());
+ } else if (expanded) {
+ this.setIcon(this.getDefaultOpenIcon());
+ } else {
+ this.setIcon(this.getDefaultClosedIcon());
+ }
+ }
+ this.setComponentOrientation(tree.getComponentOrientation());
+
+ this.setOpaque(false);
+
+ this.selected = sel;
+
+ if (treeUI instanceof SubstanceTreeUI) {
+ SubstanceTreeUI ui = (SubstanceTreeUI) treeUI;
+ Insets regInsets = ui.getCellRendererInsets();
+ this
+ .setBorder(new BorderUIResource.EmptyBorderUIResource(
+ regInsets));
+ }
+
+ return this;
+ }
+
+ private SubstanceColorScheme getColorSchemeForState(JTree tree,
+ SubstanceTreeUI ui, ComponentState activeState) {
+ SubstanceColorScheme scheme = (activeState == ComponentState.ENABLED) ? ui
+ .getDefaultColorScheme()
+ : SubstanceColorSchemeUtilities.getColorScheme(tree,
+ ColorSchemeAssociationKind.HIGHLIGHT, activeState);
+ if (scheme == null) {
+ scheme = SubstanceColorSchemeUtilities.getColorScheme(tree,
+ ColorSchemeAssociationKind.HIGHLIGHT, activeState);
+ }
+ return scheme;
+ }
+
+ /**
+ * Overrides <code>JComponent.getPreferredSize</code> to return slightly
+ * wider preferred size value.
+ */
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension retDimension = super.getPreferredSize();
+
+ if (retDimension != null)
+ retDimension = new Dimension(retDimension.width + 3,
+ retDimension.height);
+ return retDimension;
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ public void validate() {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ *
+ * @since 1.5
+ */
+ @Override
+ public void invalidate() {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ public void revalidate() {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ public void repaint(long tm, int x, int y, int width, int height) {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ public void repaint(Rectangle r) {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ *
+ * @since 1.5
+ */
+ @Override
+ public void repaint() {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ protected void firePropertyChange(String propertyName, Object oldValue,
+ Object newValue) {
+ if ("text".equals(propertyName))
+ super.firePropertyChange(propertyName, oldValue, newValue);
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ public void firePropertyChange(String propertyName, byte oldValue,
+ byte newValue) {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ public void firePropertyChange(String propertyName, char oldValue,
+ char newValue) {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ public void firePropertyChange(String propertyName, short oldValue,
+ short newValue) {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ public void firePropertyChange(String propertyName, int oldValue,
+ int newValue) {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ public void firePropertyChange(String propertyName, long oldValue,
+ long newValue) {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ public void firePropertyChange(String propertyName, float oldValue,
+ float newValue) {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ public void firePropertyChange(String propertyName, double oldValue,
+ double newValue) {
+ }
+
+ /**
+ * Overridden for performance reasons. See the <a
+ * href="#override">Implementation Note</a> for more information.
+ */
+ @Override
+ public void firePropertyChange(String propertyName, boolean oldValue,
+ boolean newValue) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paint(java.awt.Graphics)
+ */
+ @Override
+ public final void paint(Graphics g) {
+ super.paint(g);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
+ */
+ @Override
+ protected final void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceRenderer.java b/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceRenderer.java
new file mode 100644
index 0000000..fc68350
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/renderers/SubstanceRenderer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.renderers;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Marks the default base Substance renderer classes.
+ *
+ * @author Kirill Grouchnikov
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface SubstanceRenderer {
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/shaper/ClassicButtonShaper.java b/substance/src/main/java/org/pushingpixels/substance/api/shaper/ClassicButtonShaper.java
new file mode 100644
index 0000000..31dbd89
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/shaper/ClassicButtonShaper.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.shaper;
+
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+import java.util.Set;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+
+import org.pushingpixels.substance.api.SubstanceConstants;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.border.SubstanceBorder;
+import org.pushingpixels.substance.internal.utils.border.SubstanceButtonBorder;
+
+/**
+ * Button shaper that returns rectangular buttons with slightly rounded corners
+ * (ala Windows XP). This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ClassicButtonShaper implements SubstanceButtonShaper,
+ RectangularButtonShaper {
+ /**
+ * Cache of already computed contours.
+ */
+ private final static LazyResettableHashMap<GeneralPath> contours = new LazyResettableHashMap<GeneralPath>(
+ "ClassicButtonShaper");
+
+ /**
+ * Reusable instance of this shaper.
+ */
+ public static final ClassicButtonShaper INSTANCE = new ClassicButtonShaper();
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.button.SubstanceButtonShaper#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return "Classic";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.shaper.SubstanceButtonShaper#getButtonOutline
+ * (javax .swing.AbstractButton, java.awt.Insets, int, int, boolean)
+ */
+ @Override
+ public GeneralPath getButtonOutline(AbstractButton button, Insets insets,
+ int width, int height, boolean isInner) {
+ Set<SubstanceConstants.Side> straightSides = SubstanceCoreUtilities
+ .getSides(button, SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY);
+
+ float radius = this.getCornerRadius(button, insets);
+ if (isInner) {
+ radius -= (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(button));
+ if (radius < 0.0f)
+ radius = 0.0f;
+ }
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
+ straightSides, radius, insets);
+
+ GeneralPath result = contours.get(key);
+ if (result != null) {
+ return result;
+ }
+
+ result = SubstanceOutlineUtilities.getBaseOutline(width, height,
+ radius, straightSides, insets);
+ contours.put(key, result);
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.button.SubstanceButtonShaper#getButtonBorder
+ * (javax .swing.AbstractButton)
+ */
+ @Override
+ public Border getButtonBorder(final AbstractButton button) {
+ return new SubstanceButtonBorder(ClassicButtonShaper.class) {
+ @Override
+ public Insets getBorderInsets(Component c) {
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(button);
+ Insets buttonInsets = SubstanceSizeUtils
+ .getButtonInsets(fontSize);
+ int focusPadding = SubstanceSizeUtils
+ .getFocusRingPadding(fontSize);
+ int lrPadding = SubstanceCoreUtilities.hasText(button) ? SubstanceSizeUtils
+ .getTextButtonLRPadding(fontSize)
+ : 0;
+ Set<SubstanceConstants.Side> openSides = SubstanceCoreUtilities
+ .getSides(button,
+ SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY);
+ int left = lrPadding
+ + buttonInsets.left
+ + focusPadding
+ + ((openSides != null)
+ && openSides
+ .contains(SubstanceConstants.Side.LEFT) ? -1
+ : 0);
+ int right = lrPadding
+ + buttonInsets.right
+ + focusPadding
+ + ((openSides != null)
+ && openSides
+ .contains(SubstanceConstants.Side.RIGHT) ? -1
+ : 0);
+ int top = buttonInsets.top
+ + ((openSides != null)
+ && openSides
+ .contains(SubstanceConstants.Side.TOP) ? -1
+ : 0);
+ int bottom = buttonInsets.bottom
+ + ((openSides != null)
+ && openSides
+ .contains(SubstanceConstants.Side.BOTTOM) ? -1
+ : 0);
+ return new Insets(top, left, bottom, right);
+ }
+ };
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.button.SubstanceButtonShaper#getPreferredSize
+ * (javax .swing.AbstractButton, java.awt.Dimension)
+ */
+ @Override
+ public Dimension getPreferredSize(AbstractButton button,
+ Dimension uiPreferredSize) {
+ Dimension result;
+ boolean toTweakWidth = false;
+ boolean toTweakHeight = false;
+
+ Icon icon = button.getIcon();
+ boolean hasIcon = SubstanceCoreUtilities.hasIcon(button);
+ boolean hasText = SubstanceCoreUtilities.hasText(button);
+ Insets margin = button.getMargin();
+
+ result = uiPreferredSize;
+
+ boolean hasNoMinSizeProperty = SubstanceCoreUtilities
+ .hasNoMinSizeProperty(button);
+ if ((!hasNoMinSizeProperty) && hasText) {
+ int baseWidth = uiPreferredSize.width;
+ baseWidth = Math.max(baseWidth, SubstanceSizeUtils
+ .getMinButtonWidth(SubstanceSizeUtils
+ .getComponentFontSize(button)));
+ result = new Dimension(baseWidth, uiPreferredSize.height);
+ int baseHeight = result.height;
+ // int padding = SubstanceSizeUtils
+ // .getButtonHeightPadding(SubstanceSizeUtils
+ // .getComponentFontSize(button));
+ // baseHeight += padding;
+ // baseHeight = Math.max(baseHeight, SubstanceSizeUtils
+ // .getMinButtonHeight(SubstanceSizeUtils
+ // .getComponentFontSize(button)));
+ result = new Dimension(result.width, baseHeight);
+ } else {
+ if (hasNoMinSizeProperty) {
+ if (margin != null) {
+ result = new Dimension(result.width + margin.left
+ + margin.right, result.height + margin.top
+ + margin.bottom);
+ }
+ }
+ }
+
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(button);
+ int extraPadding = SubstanceSizeUtils.getExtraPadding(fontSize);
+ int focusPadding = SubstanceSizeUtils.getFocusRingPadding(fontSize);
+ int iconPaddingWidth = 6 + 2 * extraPadding + 2 * focusPadding;
+ int iconPaddingHeight = 6 + 2 * extraPadding;
+ if (margin != null) {
+ iconPaddingWidth = Math.max(iconPaddingWidth, margin.left
+ + margin.right);
+ iconPaddingHeight = Math.max(iconPaddingHeight, margin.top
+ + margin.bottom);
+ }
+ if (hasIcon) {
+ // check the icon height
+ int iconHeight = icon.getIconHeight();
+ if (iconHeight > (result.getHeight() - iconPaddingHeight)) {
+ result = new Dimension(result.width, iconHeight);
+ toTweakHeight = true;
+ }
+ int iconWidth = icon.getIconWidth();
+ if (iconWidth > (result.getWidth() - iconPaddingWidth)) {
+ result = new Dimension(iconWidth, result.height);
+ toTweakWidth = true;
+ }
+ }
+ // else {
+ // if (hasText) {
+ // result = new Dimension(result.width + 2 * extraPadding,
+ // result.height);
+ // }
+ // }
+
+ if (SubstanceCoreUtilities.isScrollBarButton(button)) {
+ toTweakWidth = false;
+ toTweakHeight = false;
+ }
+
+ if (toTweakWidth) {
+ result = new Dimension(result.width + iconPaddingWidth,
+ result.height);
+ }
+ if (toTweakHeight) {
+ result = new Dimension(result.width, result.height
+ + iconPaddingHeight);
+ }
+
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.button.SubstanceButtonShaper#isProportionate
+ * ()
+ */
+ @Override
+ public boolean isProportionate() {
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.shaper.RectangularButtonShaper#getCornerRadius
+ * (javax .swing.JComponent, java.awt.Insets)
+ */
+ @Override
+ public float getCornerRadius(AbstractButton button, Insets insets) {
+ float radius = SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(button));
+ if (button instanceof SubstanceInternalArrowButton) {
+ Border parentBorder = ((JComponent) button.getParent()).getBorder();
+ if (parentBorder instanceof SubstanceBorder) {
+ radius *= ((SubstanceBorder) parentBorder)
+ .getRadiusScaleFactor();
+ }
+ }
+ if (SubstanceCoreUtilities.isToolBarButton(button)) {
+ radius = SubstanceCoreUtilities.getToolbarButtonCornerRadius(
+ button, insets);
+ }
+ return radius;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/shaper/RectangularButtonShaper.java b/substance/src/main/java/org/pushingpixels/substance/api/shaper/RectangularButtonShaper.java
new file mode 100644
index 0000000..8be2627
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/shaper/RectangularButtonShaper.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.shaper;
+
+import java.awt.Insets;
+
+import javax.swing.AbstractButton;
+
+/**
+ * Interface for rectangular-based button shapers.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface RectangularButtonShaper {
+ /**
+ * Returns the corner radius of the specified button.
+ *
+ * @param button
+ * Button.
+ * @param insets
+ * Button insets.
+ * @return Corner radius of the specified button.
+ */
+ public float getCornerRadius(AbstractButton button, Insets insets);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/shaper/StandardButtonShaper.java b/substance/src/main/java/org/pushingpixels/substance/api/shaper/StandardButtonShaper.java
new file mode 100644
index 0000000..193979a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/shaper/StandardButtonShaper.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.shaper;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.awt.geom.GeneralPath;
+import java.util.Set;
+
+import javax.swing.AbstractButton;
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.border.Border;
+
+import org.pushingpixels.substance.api.SubstanceConstants;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.utils.HashMapKey;
+import org.pushingpixels.substance.internal.utils.LazyResettableHashMap;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceInternalArrowButton;
+import org.pushingpixels.substance.internal.utils.SubstanceOutlineUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.border.SubstanceBorder;
+import org.pushingpixels.substance.internal.utils.border.SubstanceButtonBorder;
+
+/**
+ * Button shaper that returns buttons with completely rounded corners (ala Mac
+ * 10.4). This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class StandardButtonShaper implements SubstanceButtonShaper,
+ RectangularButtonShaper {
+ /**
+ * Cache of already computed contours.
+ */
+ private final static LazyResettableHashMap<GeneralPath> contours = new LazyResettableHashMap<GeneralPath>(
+ "StandardButtonShaper");
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.button.SubstanceButtonShaper#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return "Standard";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.shaper.SubstanceButtonShaper#getButtonOutline
+ * (javax .swing.AbstractButton, java.awt.Insets, int, int, boolean)
+ */
+ @Override
+ public GeneralPath getButtonOutline(AbstractButton button, Insets insets,
+ int width, int height, boolean isInner) {
+ Set<SubstanceConstants.Side> straightSides = SubstanceCoreUtilities
+ .getSides(button, SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY);
+
+ float radius = this.getCornerRadius(button, insets);
+ if (isInner) {
+ radius -= (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(button));
+ if (radius < 0.0f)
+ radius = 0.0f;
+ }
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
+ straightSides, radius, insets);
+
+ GeneralPath result = contours.get(key);
+ if (result != null) {
+ return result;
+ }
+
+ result = SubstanceOutlineUtilities.getBaseOutline(width, height,
+ radius, straightSides, insets);
+ contours.put(key, result);
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.button.SubstanceButtonShaper#getButtonBorder
+ * (javax .swing.AbstractButton)
+ */
+ @Override
+ public Border getButtonBorder(final AbstractButton button) {
+ return new SubstanceButtonBorder(StandardButtonShaper.class) {
+ @Override
+ public Insets getBorderInsets(Component c) {
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(button);
+ Insets buttonInsets = SubstanceSizeUtils
+ .getButtonInsets(fontSize);
+ int focusPadding = SubstanceSizeUtils
+ .getFocusRingPadding(fontSize);
+ int lrPadding = SubstanceCoreUtilities.hasText(button) ? SubstanceSizeUtils
+ .getTextButtonLRPadding(fontSize)
+ : 0;
+ Set<SubstanceConstants.Side> openSides = SubstanceCoreUtilities
+ .getSides(button,
+ SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY);
+ int left = lrPadding
+ + buttonInsets.left
+ + focusPadding
+ + ((openSides != null)
+ && openSides
+ .contains(SubstanceConstants.Side.LEFT) ? -1
+ : 0);
+ int right = lrPadding
+ + buttonInsets.right
+ + focusPadding
+ + ((openSides != null)
+ && openSides
+ .contains(SubstanceConstants.Side.RIGHT) ? -1
+ : 0);
+ int top = buttonInsets.top
+ + ((openSides != null)
+ && openSides
+ .contains(SubstanceConstants.Side.TOP) ? -1
+ : 0);
+ int bottom = buttonInsets.bottom
+ + ((openSides != null)
+ && openSides
+ .contains(SubstanceConstants.Side.BOTTOM) ? -1
+ : 0);
+ return new Insets(top, left, bottom, right);
+ }
+ };
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.button.SubstanceButtonShaper#getPreferredSize
+ * (javax .swing.AbstractButton, java.awt.Dimension)
+ */
+ @Override
+ public Dimension getPreferredSize(AbstractButton button,
+ Dimension uiPreferredSize) {
+ Dimension result;
+ boolean toTweakWidth = false;
+ boolean toTweakHeight = false;
+
+ Icon icon = button.getIcon();
+ boolean hasIcon = SubstanceCoreUtilities.hasIcon(button);
+ boolean hasText = SubstanceCoreUtilities.hasText(button);
+ Insets margin = button.getMargin();
+
+ result = uiPreferredSize;
+
+ boolean hasNoMinSizeProperty = SubstanceCoreUtilities
+ .hasNoMinSizeProperty(button);
+ if ((!hasNoMinSizeProperty) && hasText) {
+ int baseWidth = uiPreferredSize.width;
+ baseWidth = Math.max(baseWidth + uiPreferredSize.height,
+ SubstanceSizeUtils.getMinButtonWidth(SubstanceSizeUtils
+ .getComponentFontSize(button)));
+ // if (baseWidth < DEFAULT_WIDTH) {
+ // baseWidth = DEFAULT_WIDTH;
+ // }
+ result = new Dimension(baseWidth, uiPreferredSize.height);
+ int baseHeight = result.height;
+ // baseHeight = Math.max(baseHeight, SubstanceSizeUtils
+ // .getMinButtonHeight(SubstanceSizeUtils
+ // .getComponentFontSize(button)));
+ result = new Dimension(result.width, baseHeight);
+ } else {
+ if (hasNoMinSizeProperty) {
+ if (margin != null) {
+ result = new Dimension(result.width + margin.left
+ + margin.right, result.height + margin.top
+ + margin.bottom);
+ }
+ }
+ }
+
+ int extraPadding = SubstanceSizeUtils
+ .getExtraPadding(SubstanceSizeUtils
+ .getComponentFontSize(button));
+ int iconPaddingWidth = 6 + 2 * extraPadding;
+ int iconPaddingHeight = 6 + 2 * extraPadding;
+ if (margin != null) {
+ iconPaddingWidth = Math.max(iconPaddingWidth, margin.left
+ + margin.right);
+ iconPaddingHeight = Math.max(iconPaddingHeight, margin.top
+ + margin.bottom);
+ }
+ if (hasIcon) {
+ // check the icon height
+ int iconHeight = icon.getIconHeight();
+ if (iconHeight > (result.getHeight() - iconPaddingHeight)) {
+ result = new Dimension(result.width, iconHeight);
+ toTweakHeight = true;
+ }
+ int iconWidth = icon.getIconWidth();
+ if (iconWidth > (result.getWidth() - iconPaddingWidth)) {
+ result = new Dimension(iconWidth, result.height);
+ toTweakWidth = true;
+ }
+ }
+
+ if (SubstanceCoreUtilities.isScrollBarButton(button)) {
+ toTweakWidth = false;
+ toTweakHeight = false;
+ }
+
+ if (toTweakWidth) {
+ result = new Dimension(result.width + iconPaddingWidth,
+ result.height);
+ }
+ if (toTweakHeight) {
+ result = new Dimension(result.width, result.height
+ + iconPaddingHeight);
+ }
+
+ if (result.height % 2 != 0)
+ result.height++;
+
+ return result;
+ }
+
+ /**
+ * Returns indication whether the specified button should be drawn with
+ * completely round corners.
+ *
+ * @param button
+ * A button.
+ * @return <code>true</code> if the specified button should be drawn with
+ * completely round corners, <code>false</code> otherwise.
+ */
+ public static boolean isRoundButton(AbstractButton button) {
+ return (!SubstanceCoreUtilities.isComboBoxButton(button))
+ && (!SubstanceCoreUtilities.isScrollButton(button))
+ && SubstanceCoreUtilities.hasText(button);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.button.SubstanceButtonShaper#isProportionate
+ * ()
+ */
+ @Override
+ public boolean isProportionate() {
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.shaper.RectangularButtonShaper#getCornerRadius
+ * (javax .swing.JComponent, java.awt.Insets)
+ */
+ @Override
+ public float getCornerRadius(AbstractButton button, Insets insets) {
+ int width = button.getWidth();
+ int height = button.getHeight();
+
+ boolean isRoundCorners = isRoundButton(button);
+ float radius = SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(button));
+ if (button instanceof SubstanceInternalArrowButton) {
+ Border parentBorder = ((JComponent) button.getParent()).getBorder();
+ if (parentBorder instanceof SubstanceBorder) {
+ radius *= ((SubstanceBorder) parentBorder)
+ .getRadiusScaleFactor();
+ }
+ }
+
+ if (insets != null) {
+ width -= (insets.left + insets.right);
+ height -= (insets.top + insets.bottom);
+ }
+ if (isRoundCorners) {
+ if (width > height) {
+ radius = (height) / 2.0f;
+ } else {
+ radius = (width) / 2.0f;
+ }
+ }
+
+ if (SubstanceCoreUtilities.isToolBarButton(button)) {
+ radius = SubstanceCoreUtilities.getToolbarButtonCornerRadius(
+ button, insets);
+ }
+ return radius;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/shaper/SubstanceButtonShaper.java b/substance/src/main/java/org/pushingpixels/substance/api/shaper/SubstanceButtonShaper.java
new file mode 100644
index 0000000..eb93068
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/shaper/SubstanceButtonShaper.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.shaper;
+
+import java.awt.*;
+
+import javax.swing.AbstractButton;
+import javax.swing.border.Border;
+
+import org.pushingpixels.substance.api.trait.SubstanceTrait;
+
+/**
+ * Button shaper interface for <b>Substance</b> look and feel. This class is
+ * part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SubstanceButtonShaper extends SubstanceTrait {
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName();
+
+ /**
+ * Returns the outline path for the specified button.
+ *
+ * @param button
+ * A button.
+ * @param insets
+ * Button insets.
+ * @param width
+ * Button width.
+ * @param height
+ * Button height.
+ * @param isInner
+ * Indication whether the returned outline is used for the inner
+ * contour.
+ * @return The outline path for the specified button.
+ */
+ public Shape getButtonOutline(AbstractButton button, Insets insets,
+ int width, int height, boolean isInner);
+
+ /**
+ * Returns the border for the specified button.
+ *
+ * @param button
+ * A button.
+ * @return The border for the specified button.
+ */
+ public Border getButtonBorder(AbstractButton button);
+
+ /**
+ * Returns the preferred size for the specified button.
+ *
+ * @param button
+ * A button.
+ * @param uiPreferredSize
+ * Preferred size of the button under the regular conditions
+ * (plain rectangular button).
+ * @return The preferred size for the specified button.
+ */
+ public Dimension getPreferredSize(AbstractButton button,
+ Dimension uiPreferredSize);
+
+ /**
+ * Returns the boolean indication whether the shaper should maintain button
+ * proportions on the resize. This may be relevant for vector-based shapers
+ * (such as animals / other objects).
+ *
+ * @return <code>true</code> if <code>this</code> shaper should maintain
+ * button proportions on the resize, <code>false</code> otherwise.
+ *
+ */
+ public boolean isProportionate();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/AutumnSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/AutumnSkin.java
new file mode 100755
index 0000000..6b700b1
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/AutumnSkin.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.*;
+import org.pushingpixels.substance.api.painter.decoration.MarbleNoiseDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.MatteFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter;
+import org.pushingpixels.substance.api.painter.overlay.TopShadowOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Autumn</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public class AutumnSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Autumn";
+
+ /**
+ * Overlay painter to paint separator lines on some decoration areas.
+ */
+ private BottomLineOverlayPainter bottomLineOverlayPainter;
+
+ /**
+ * Creates a new <code>Autumn</code> skin.
+ */
+ public AutumnSkin() {
+ SubstanceSkin.ColorSchemes schemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/autumn.colorschemes");
+
+ SubstanceColorScheme activeScheme = schemes.get("Autumn Active");
+ SubstanceColorScheme enabledScheme = schemes.get("Autumn Enabled");
+ SubstanceColorScheme disabledScheme = enabledScheme;
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+ defaultSchemeBundle.registerColorScheme(disabledScheme, 0.6f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(activeScheme, 0.6f,
+ ComponentState.DISABLED_SELECTED);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ SubstanceColorSchemeBundle titlePaneSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+ titlePaneSchemeBundle.registerColorScheme(disabledScheme, 0.6f,
+ ComponentState.DISABLED_UNSELECTED);
+ titlePaneSchemeBundle.registerColorScheme(activeScheme, 0.6f,
+ ComponentState.DISABLED_SELECTED);
+
+ SubstanceColorScheme borderScheme = enabledScheme.saturate(0.2f);
+ titlePaneSchemeBundle.registerColorScheme(borderScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED);
+
+ this.registerDecorationAreaSchemeBundle(titlePaneSchemeBundle,
+ activeScheme, DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE);
+
+ SubstanceColorScheme watermarkScheme = schemes.get("Autumn Watermark");
+
+ this.registerAsDecorationArea(activeScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ this.registerAsDecorationArea(watermarkScheme,
+ DecorationAreaType.GENERAL, DecorationAreaType.FOOTER,
+ DecorationAreaType.TOOLBAR);
+
+ // add an overlay painter to paint a drop shadow along the top
+ // edge of toolbars
+ this.addOverlayPainter(TopShadowOverlayPainter.getInstance(),
+ DecorationAreaType.TOOLBAR);
+
+ // add an overlay painter to paint separator lines along the bottom
+ // edges of title panes and menu bars
+ this.bottomLineOverlayPainter = new BottomLineOverlayPainter(
+ ColorSchemeSingleColorQuery.DARK);
+ this.addOverlayPainter(this.bottomLineOverlayPainter,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new MatteFillPainter();
+ this.borderPainter = new CompositeBorderPainter("Autumn",
+ new ClassicBorderPainter(), new DelegateBorderPainter(
+ "Autumn Inner", new ClassicBorderPainter(),
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.tint(0.8f);
+ }
+ }));
+
+ this.highlightPainter = new ClassicHighlightPainter();
+
+ MarbleNoiseDecorationPainter decorationPainter = new MarbleNoiseDecorationPainter();
+ decorationPainter.setTextureAlpha(0.7f);
+ this.decorationPainter = decorationPainter;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/BusinessBlackSteelSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/BusinessBlackSteelSkin.java
new file mode 100755
index 0000000..bf114db
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/BusinessBlackSteelSkin.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.EbonyColorScheme;
+import org.pushingpixels.substance.api.colorscheme.LightGrayColorScheme;
+import org.pushingpixels.substance.api.colorscheme.MetallicColorScheme;
+import org.pushingpixels.substance.api.colorscheme.SteelBlueColorScheme;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.decoration.BrushedMetalDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.ClassicFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.TopShadowOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Business Black Steel</code> skin. This class is part of officially
+ * supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.3
+ */
+public class BusinessBlackSteelSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Business Black Steel";
+
+ /**
+ * Creates a new <code>Business</code> skin.
+ */
+ public BusinessBlackSteelSkin() {
+ SubstanceColorScheme activeScheme = new SteelBlueColorScheme().tint(
+ 0.15).named("Business Black Steel Active");
+ SubstanceColorScheme enabledScheme = new MetallicColorScheme().tint(
+ 0.05).named("Business Black Steel Enabled");
+ SubstanceColorScheme disabledScheme = new LightGrayColorScheme().tint(
+ 0.05).named("Business Black Steel Disabled");
+
+ // the default color scheme bundle
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+ defaultSchemeBundle.registerHighlightColorScheme(activeScheme, 0.6f,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(activeScheme, 0.8f,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(activeScheme, 0.95f,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(activeScheme, 0.8f,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ // color scheme bundle for title panes
+ SubstanceColorScheme activeHeaderScheme = new EbonyColorScheme()
+ .shiftBackground(Color.black, 0.3).tint(0.05).named(
+ "Business Black Steel Active Header");
+ SubstanceColorScheme enabledHeaderScheme = new EbonyColorScheme().tint(
+ 0.05).named("Business Black Steel Enabled Header");
+ SubstanceColorSchemeBundle headerSchemeBundle = new SubstanceColorSchemeBundle(
+ activeHeaderScheme, enabledHeaderScheme, disabledScheme);
+ headerSchemeBundle.registerColorScheme(enabledHeaderScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED,
+ ComponentState.DISABLED_SELECTED);
+ headerSchemeBundle.registerHighlightColorScheme(activeScheme, 0.6f,
+ ComponentState.ROLLOVER_UNSELECTED);
+ headerSchemeBundle.registerHighlightColorScheme(activeScheme, 0.8f,
+ ComponentState.SELECTED);
+ headerSchemeBundle.registerHighlightColorScheme(activeScheme, 0.95f,
+ ComponentState.ROLLOVER_SELECTED);
+ headerSchemeBundle.registerHighlightColorScheme(activeScheme, 0.8f,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ this.registerDecorationAreaSchemeBundle(headerSchemeBundle,
+ activeHeaderScheme, DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ // color scheme bundle for general areas
+ SubstanceColorScheme activeGeneralScheme = activeScheme.shade(0.1)
+ .saturate(-0.5).named("Business Black Steel Active General");
+ SubstanceColorScheme enabledGeneralScheme = activeScheme.tint(0.3)
+ .saturate(-0.7).named("Business Black Steel Enabled General");
+ SubstanceColorSchemeBundle generalSchemeBundle = new SubstanceColorSchemeBundle(
+ activeGeneralScheme, enabledGeneralScheme, disabledScheme);
+ generalSchemeBundle.registerColorScheme(disabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ this.registerDecorationAreaSchemeBundle(generalSchemeBundle,
+ DecorationAreaType.FOOTER, DecorationAreaType.GENERAL);
+
+ // add an overlay painter to paint a drop shadow along the top
+ // edge of toolbars
+ this.addOverlayPainter(TopShadowOverlayPainter.getInstance(),
+ DecorationAreaType.TOOLBAR);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new ClassicFillPainter();
+ this.borderPainter = new ClassicBorderPainter();
+
+ BrushedMetalDecorationPainter decorationPainter = new BrushedMetalDecorationPainter();
+ decorationPainter.setBaseDecorationPainter(new ArcDecorationPainter());
+ decorationPainter.setTextureAlpha(0.02f);
+ this.decorationPainter = decorationPainter;
+
+ this.highlightPainter = new ClassicHighlightPainter();
+
+ this.borderPainter = new ClassicBorderPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/BusinessBlueSteelSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/BusinessBlueSteelSkin.java
new file mode 100755
index 0000000..651f84b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/BusinessBlueSteelSkin.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.LightGrayColorScheme;
+import org.pushingpixels.substance.api.colorscheme.MetallicColorScheme;
+import org.pushingpixels.substance.api.colorscheme.SteelBlueColorScheme;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.decoration.BrushedMetalDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.ClassicFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Business Blue Steel</code> skin. This class is part of officially
+ * supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.3
+ */
+public class BusinessBlueSteelSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Business Blue Steel";
+
+ /**
+ * Creates a new <code>Business</code> skin.
+ */
+ public BusinessBlueSteelSkin() {
+ SubstanceColorScheme activeScheme = new SteelBlueColorScheme().tint(
+ 0.15).named("Business Blue Steel Active");
+ SubstanceColorScheme enabledScheme = new MetallicColorScheme().tint(
+ 0.05).named("Business Blue Steel Enabled");
+ SubstanceColorScheme disabledScheme = new LightGrayColorScheme().tint(
+ 0.05).named("Business Blue Steel Disabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+
+ SubstanceSkin.ColorSchemes kitchenSinkSchemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/kitchen-sink.colorschemes");
+ SubstanceColorScheme highlightColorScheme = kitchenSinkSchemes
+ .get("Business Blue Steel Highlight");
+ defaultSchemeBundle.registerHighlightColorScheme(highlightColorScheme);
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ SubstanceColorScheme activeHeaderScheme = activeScheme.saturate(0.2)
+ .named("Business Blue Steel Active Header");
+ SubstanceColorScheme enabledHeaderScheme = activeScheme.saturate(-0.2)
+ .named("Business Blue Steel Enabled Header");
+ SubstanceColorSchemeBundle headerSchemeBundle = new SubstanceColorSchemeBundle(
+ activeHeaderScheme, enabledHeaderScheme, enabledHeaderScheme);
+ headerSchemeBundle.registerColorScheme(enabledHeaderScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED,
+ ComponentState.DISABLED_SELECTED);
+ this.registerDecorationAreaSchemeBundle(headerSchemeBundle,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER, DecorationAreaType.TOOLBAR);
+
+ SubstanceColorScheme activeGeneralScheme = activeScheme.saturate(-0.5)
+ .named("Business Blue Steel Active General");
+ SubstanceColorScheme enabledGeneralScheme = activeScheme.tint(0.3)
+ .saturate(-0.2).named("Business Blue Steel Enabled General");
+ SubstanceColorSchemeBundle generalSchemeBundle = new SubstanceColorSchemeBundle(
+ activeGeneralScheme, enabledGeneralScheme, disabledScheme);
+ generalSchemeBundle.registerColorScheme(enabledGeneralScheme, 0.7f,
+ ComponentState.DISABLED_UNSELECTED);
+ this.registerDecorationAreaSchemeBundle(generalSchemeBundle,
+ DecorationAreaType.FOOTER, DecorationAreaType.GENERAL);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new ClassicFillPainter();
+ this.borderPainter = new ClassicBorderPainter();
+
+ BrushedMetalDecorationPainter decorationPainter = new BrushedMetalDecorationPainter();
+ decorationPainter.setBaseDecorationPainter(new ArcDecorationPainter());
+ decorationPainter.setTextureAlpha(0.3f);
+ this.decorationPainter = decorationPainter;
+
+ this.highlightPainter = new ClassicHighlightPainter();
+ this.borderPainter = new ClassicBorderPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/BusinessSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/BusinessSkin.java
new file mode 100755
index 0000000..06e01dd
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/BusinessSkin.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.MetallicColorScheme;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.border.CompositeBorderPainter;
+import org.pushingpixels.substance.api.painter.border.DelegateBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.decoration.BrushedMetalDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.ClassicFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Business</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class BusinessSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Business";
+
+ /**
+ * Creates a new <code>Business</code> skin.
+ */
+ public BusinessSkin() {
+ SubstanceColorScheme activeScheme = new MetallicColorScheme()
+ .tint(0.15).named("Business Active");
+ SubstanceColorScheme enabledScheme = new MetallicColorScheme().shade(
+ 0.1).named("Business Enabled");
+ SubstanceColorScheme disabledScheme = enabledScheme;
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+
+ SubstanceSkin.ColorSchemes kitchenSinkSchemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/kitchen-sink.colorschemes");
+ SubstanceColorScheme highlightColorScheme = kitchenSinkSchemes
+ .get("Business Highlight");
+ defaultSchemeBundle.registerHighlightColorScheme(highlightColorScheme);
+
+ defaultSchemeBundle.registerColorScheme(disabledScheme, 0.4f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(activeScheme, 0.4f,
+ ComponentState.DISABLED_SELECTED);
+ defaultSchemeBundle.registerColorScheme(activeScheme,
+ ComponentState.SELECTED);
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerAsDecorationArea(enabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER, DecorationAreaType.FOOTER);
+
+ this.registerAsDecorationArea(disabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE,
+ DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE);
+
+ SubstanceSkin.ColorSchemes kitchenSkinSchemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/kitchen-sink.colorschemes");
+ this
+ .registerAsDecorationArea(kitchenSkinSchemes
+ .get("LightGray General Watermark"),
+ DecorationAreaType.GENERAL);
+
+ this.setSelectedTabFadeStart(0.6);
+ this.setSelectedTabFadeEnd(1.0);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new ClassicFillPainter();
+ this.borderPainter = new CompositeBorderPainter("Business",
+ new ClassicBorderPainter(), new DelegateBorderPainter(
+ "Business Inner", new ClassicBorderPainter(),
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.tint(0.9f);
+ }
+ }));
+
+ BrushedMetalDecorationPainter decorationPainter = new BrushedMetalDecorationPainter();
+ decorationPainter.setBaseDecorationPainter(new ArcDecorationPainter());
+ decorationPainter.setTextureAlpha(0.2f);
+ this.decorationPainter = decorationPainter;
+
+ this.highlightPainter = new ClassicHighlightPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/CeruleanSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/CeruleanSkin.java
new file mode 100755
index 0000000..c2cf42f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/CeruleanSkin.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2005-2012 Substance Kirill Grouchnikov and contributing authors. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.BaseColorScheme;
+import org.pushingpixels.substance.api.colorscheme.SteelBlueColorScheme;
+import org.pushingpixels.substance.api.painter.border.GlassBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.ClassicFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.GlassHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.internal.colorscheme.BlendBiColorScheme;
+
+import java.awt.Color;
+
+/**
+ * <code>Cerulean</code> skin.
+ */
+public class CeruleanSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Cerulean";
+
+
+ /**
+ * Overlay painter to paint separator lines on some decoration areas.
+ */
+ private BottomLineOverlayPainter bottomLineOverlayPainter;
+
+ /**
+ * Creates a new <code>Nebulous</code> skin.
+ */
+ public CeruleanSkin() {
+ super();
+
+ ColorSchemes schemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/nebula.colorschemes");
+
+ SubstanceColorScheme activeScheme = schemes.get("Nebula Active");
+ SubstanceColorScheme enabledScheme = schemes.get("Nebula Enabled").saturate(-0.9);
+ SubstanceColorScheme rolloverUnselectedScheme = schemes
+ .get("Nebula Rollover Unselected");
+ final SubstanceColorScheme pressedScheme = schemes.get("Nebula Pressed");
+ SubstanceColorScheme rolloverSelectedScheme = schemes
+ .get("Nebula Rollover Selected");
+ SubstanceColorScheme disabledScheme = schemes.get("Nebula Disabled").saturate(-0.9);
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+
+ CopyMutableColorScheme steelBlue= new CopyMutableColorScheme("Cerulean Hover", new SteelBlueColorScheme().tint(0.4));
+ steelBlue.setForegroundColor(enabledScheme.getForegroundColor());
+
+ double saturate = 0.1;
+ double tint = 0.4;
+ double shade = tint/4;
+ CopyMutableColorScheme pressed = new CopyMutableColorScheme("Cerulean Pressed", steelBlue.saturate(saturate).shade(shade));
+ //pressed.setForegroundColor(pressedScheme.getForegroundColor());
+ defaultSchemeBundle.registerColorScheme(pressed,
+ ComponentState.PRESSED_SELECTED, ComponentState.PRESSED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(new BlendBiColorScheme(
+ steelBlue, disabledScheme, 0.25),
+ ComponentState.DISABLED_SELECTED);
+ defaultSchemeBundle.registerColorScheme(
+ steelBlue.tint(tint).saturate(saturate),
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerColorScheme(
+ steelBlue.shade(shade / 2).saturate(saturate/2),
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerColorScheme(
+ steelBlue.tint(tint / 2).saturate(saturate/2),
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(steelBlue.shade(0.5),
+ ColorSchemeAssociationKind.MARK, ComponentState.getActiveStates());
+ defaultSchemeBundle.registerColorScheme(steelBlue,
+ ColorSchemeAssociationKind.BORDER, ComponentState.getActiveStates());
+
+ // for progress bars
+ ComponentState determinateState = new ComponentState(
+ "determinate enabled", new ComponentStateFacet[] {
+ ComponentStateFacet.ENABLE,
+ ComponentStateFacet.DETERMINATE,
+ ComponentStateFacet.SELECTION }, null);
+ ComponentState determinateDisabledState = new ComponentState(
+ "determinate disabled", new ComponentStateFacet[] {
+ ComponentStateFacet.DETERMINATE,
+ ComponentStateFacet.SELECTION },
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE });
+ ComponentState indeterminateState = new ComponentState(
+ "indeterminate enabled",
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE,
+ ComponentStateFacet.SELECTION },
+ new ComponentStateFacet[] { ComponentStateFacet.DETERMINATE });
+ ComponentState indeterminateDisabledState = new ComponentState(
+ "indeterminate disabled", null, new ComponentStateFacet[] {
+ ComponentStateFacet.DETERMINATE,
+ ComponentStateFacet.ENABLE, ComponentStateFacet.SELECTION });
+ defaultSchemeBundle.registerColorScheme(rolloverSelectedScheme,
+ determinateState, indeterminateState);
+ defaultSchemeBundle.registerColorScheme(rolloverSelectedScheme,
+ ColorSchemeAssociationKind.BORDER,
+ determinateState, indeterminateState);
+ defaultSchemeBundle.registerColorScheme(disabledScheme,
+ determinateDisabledState, indeterminateDisabledState);
+ defaultSchemeBundle.registerColorScheme(disabledScheme,
+ ColorSchemeAssociationKind.BORDER,
+ determinateDisabledState, indeterminateDisabledState);
+
+ // for uneditable fields
+ ComponentState editable = new ComponentState("editable",
+ new ComponentStateFacet[] {ComponentStateFacet.ENABLE, ComponentStateFacet.EDITABLE},
+ null);
+ ComponentState uneditable = new ComponentState("uneditable",
+ editable, new ComponentStateFacet[] {ComponentStateFacet.ENABLE},
+ new ComponentStateFacet[] {ComponentStateFacet.EDITABLE});
+ defaultSchemeBundle.registerColorScheme(
+ defaultSchemeBundle.getColorScheme(editable),
+ ColorSchemeAssociationKind.FILL, uneditable
+ );
+
+ // for text highlight
+ ColorSchemes kitchenSinkSchemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/kitchen-sink.colorschemes");
+ SubstanceColorScheme highlightColorScheme = kitchenSinkSchemes
+ .get("Moderate Highlight");
+// SubstanceColorScheme highlightColorScheme = activeScheme.hueShift(-0.5).saturate(0.5).tint(0.5);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightColorScheme);
+
+ registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ CopyMutableColorScheme chrome = new CopyMutableColorScheme("Cerulean Chrome", pressedScheme);
+ chrome.setUltraDarkColor(chrome.getExtraLightColor());
+ registerDecorationAreaSchemeBundle(new SubstanceColorSchemeBundle(pressedScheme, pressedScheme, disabledScheme), chrome,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE);
+
+
+ this.registerAsDecorationArea(enabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE,
+ DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE);
+
+
+ registerAsDecorationArea(activeScheme.saturate(-0.75),
+ DecorationAreaType.HEADER, DecorationAreaType.FOOTER,
+ DecorationAreaType.GENERAL);
+
+
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new ClassicFillPainter();
+
+ this.decorationPainter = new ArcDecorationPainter();
+
+ this.highlightPainter = new GlassHighlightPainter();
+ this.borderPainter = new GlassBorderPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
+
+class CopyMutableColorScheme extends BaseColorScheme {
+
+ Color foregroundColor;
+ Color ultraLightColor;
+ Color extraLightColor;
+ Color lightColor;
+ Color midColor;
+ Color darkColor;
+ Color ultraDarkColor;
+
+ public CopyMutableColorScheme(String name, SubstanceColorScheme copy) {
+ super(name, copy.isDark());
+ foregroundColor = copy.getForegroundColor();
+ ultraLightColor = copy.getUltraLightColor();
+ extraLightColor = copy.getExtraLightColor();
+ lightColor = copy.getLightColor();
+ midColor = copy.getMidColor();
+ darkColor = copy.getDarkColor();
+ ultraDarkColor = copy.getUltraDarkColor();
+ }
+
+ public void setDark(boolean isDark) {
+ this.isDark = isDark;
+ }
+
+ @Override
+ public Color getDarkColor() {
+ return darkColor;
+ }
+
+ public void setDarkColor(Color darkColor) {
+ this.darkColor = darkColor;
+ }
+
+ @Override
+ public Color getExtraLightColor() {
+ return extraLightColor;
+ }
+
+ public void setExtraLightColor(Color extraLightColor) {
+ this.extraLightColor = extraLightColor;
+ }
+
+ @Override
+ public Color getForegroundColor() {
+ return foregroundColor;
+ }
+
+ public void setForegroundColor(Color foregroundColor) {
+ this.foregroundColor = foregroundColor;
+ }
+
+ @Override
+ public Color getLightColor() {
+ return lightColor;
+ }
+
+ public void setLightColor(Color lightColor) {
+ this.lightColor = lightColor;
+ }
+
+ @Override
+ public Color getMidColor() {
+ return midColor;
+ }
+
+ public void setMidColor(Color midColor) {
+ this.midColor = midColor;
+ }
+
+ @Override
+ public Color getUltraDarkColor() {
+ return ultraDarkColor;
+ }
+
+ public void setUltraDarkColor(Color ultraDarkColor) {
+ this.ultraDarkColor = ultraDarkColor;
+ }
+
+ @Override
+ public Color getUltraLightColor() {
+ return ultraLightColor;
+ }
+
+ public void setUltraLightColor(Color ultraLightColor) {
+ this.ultraLightColor = ultraLightColor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/ChallengerDeepSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/ChallengerDeepSkin.java
new file mode 100755
index 0000000..708945d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/ChallengerDeepSkin.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.UltramarineColorScheme;
+import org.pushingpixels.substance.api.painter.border.GlassBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.GlassFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Challenger Deep</code> skin. This class is part of officially supported
+ * API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.2
+ */
+public class ChallengerDeepSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Challenger Deep";
+
+ /**
+ * Creates a new <code>Challenger Deep</code> skin.
+ */
+ public ChallengerDeepSkin() {
+ SubstanceColorScheme activeScheme = new UltramarineColorScheme();
+ SubstanceColorScheme enabledScheme = activeScheme.tone(0.1f).named(
+ "Challenger Deep Enabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, enabledScheme);
+ defaultSchemeBundle.registerColorScheme(activeScheme, 0.3f,
+ ComponentState.DISABLED_SELECTED);
+ defaultSchemeBundle.registerColorScheme(enabledScheme, 0.4f,
+ ComponentState.DISABLED_UNSELECTED);
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerAsDecorationArea(enabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER, DecorationAreaType.FOOTER,
+ DecorationAreaType.GENERAL, DecorationAreaType.TOOLBAR);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new GlassFillPainter();
+ this.decorationPainter = new ArcDecorationPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+ this.borderPainter = new GlassBorderPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/CremeCoffeeSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/CremeCoffeeSkin.java
new file mode 100755
index 0000000..0e27dda
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/CremeCoffeeSkin.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.CremeColorScheme;
+import org.pushingpixels.substance.api.colorscheme.LightGrayColorScheme;
+import org.pushingpixels.substance.api.painter.border.CompositeBorderPainter;
+import org.pushingpixels.substance.api.painter.border.DelegateBorderPainter;
+import org.pushingpixels.substance.api.painter.border.GlassBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.MatteFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Creme Coffee</code> skin. This class is part of officially supported
+ * API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.1
+ */
+public class CremeCoffeeSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Creme Coffee";
+
+ /**
+ * Creates a new <code>Creme Coffee</code> skin.
+ */
+ public CremeCoffeeSkin() {
+ SubstanceSkin.ColorSchemes kitchenSinkSchemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/kitchen-sink.colorschemes");
+ SubstanceColorScheme activeScheme = kitchenSinkSchemes
+ .get("Coffee Active");
+ SubstanceColorScheme enabledScheme = new CremeColorScheme();
+ SubstanceColorScheme disabledScheme = new LightGrayColorScheme().tint(
+ 0.35).named("Creme Coffee Disabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerAsDecorationArea(enabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER, DecorationAreaType.FOOTER,
+ DecorationAreaType.GENERAL, DecorationAreaType.TOOLBAR);
+
+ this.registerAsDecorationArea(disabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE,
+ DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new MatteFillPainter();
+ this.decorationPainter = new ArcDecorationPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+ this.borderPainter = new CompositeBorderPainter("Creme Coffee",
+ new GlassBorderPainter(), new DelegateBorderPainter(
+ "Creme Coffee Inner", new GlassBorderPainter(),
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.tint(0.8f);
+ }
+ }));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/CremeSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/CremeSkin.java
new file mode 100755
index 0000000..71a9fed
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/CremeSkin.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.CremeColorScheme;
+import org.pushingpixels.substance.api.colorscheme.LightAquaColorScheme;
+import org.pushingpixels.substance.api.colorscheme.LightGrayColorScheme;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.border.CompositeBorderPainter;
+import org.pushingpixels.substance.api.painter.border.DelegateBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.ClassicFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Creme</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class CremeSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Creme";
+
+ /**
+ * Creates a new <code>Creme</code> skin.
+ */
+ public CremeSkin() {
+ SubstanceColorScheme activeScheme = new LightAquaColorScheme()
+ .tint(0.3).named("Creme Active");
+ SubstanceColorScheme enabledScheme = new CremeColorScheme();
+ SubstanceColorScheme disabledScheme = new LightGrayColorScheme().tint(
+ 0.35).named("Creme Disabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerAsDecorationArea(enabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER, DecorationAreaType.FOOTER,
+ DecorationAreaType.GENERAL, DecorationAreaType.TOOLBAR);
+
+ this.registerAsDecorationArea(disabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE,
+ DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE);
+
+ setSelectedTabFadeStart(0.2);
+ setSelectedTabFadeEnd(0.4);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new ClassicFillPainter();
+ this.decorationPainter = new ArcDecorationPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+ this.borderPainter = new CompositeBorderPainter("Creme",
+ new ClassicBorderPainter(), new DelegateBorderPainter(
+ "Creme Inner", new ClassicBorderPainter(),
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.tint(0.9f);
+ }
+ }));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/DustCoffeeSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/DustCoffeeSkin.java
new file mode 100644
index 0000000..c0d85df
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/DustCoffeeSkin.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceColorSchemeBundle;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.painter.fill.MatteFillPainter;
+
+/**
+ * <code>Dust Coffee</code> skin. This class is part of officially supported
+ * API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.2
+ */
+public class DustCoffeeSkin extends DustSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Dust Coffee";
+
+ /**
+ * Creates a new <code>Dust Coffee</code> skin.
+ */
+ public DustCoffeeSkin() {
+ SubstanceSkin.ColorSchemes kitchenSinkSchemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/kitchen-sink.colorschemes");
+ SubstanceColorScheme activeScheme = kitchenSinkSchemes
+ .get("Coffee Active");
+
+ SubstanceSkin.ColorSchemes schemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/dust.colorschemes");
+ SubstanceColorScheme enabledScheme = schemes.get("Dust Coffee Enabled");
+
+ SubstanceColorScheme watermarkScheme = schemes
+ .get("Dust Coffee Watermark");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, enabledScheme);
+ defaultSchemeBundle.registerColorScheme(enabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(activeScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED);
+
+ // borders and marks
+ SubstanceColorScheme borderEnabledScheme = schemes
+ .get("Dust Border Enabled");
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED,
+ ComponentState.DISABLED_SELECTED,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(activeScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState
+ .getActiveStates());
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.MARK);
+
+ // text highlight
+ SubstanceColorScheme textHighlightScheme = schemes
+ .get("Dust Coffee Text Highlight");
+ defaultSchemeBundle.registerColorScheme(textHighlightScheme,
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ ComponentState.SELECTED, ComponentState.ROLLOVER_SELECTED);
+
+ // custom highlight alphas
+ defaultSchemeBundle.registerHighlightColorScheme(activeScheme, 0.6f,
+ ComponentState.ROLLOVER_UNSELECTED, ComponentState.ARMED);
+ defaultSchemeBundle.registerHighlightColorScheme(activeScheme, 0.8f,
+ ComponentState.SELECTED);
+ defaultSchemeBundle
+ .registerHighlightColorScheme(activeScheme, 1.0f,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.ROLLOVER_ARMED);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ watermarkScheme, DecorationAreaType.NONE);
+
+ this.fillPainter = new MatteFillPainter();
+ }
+
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/DustSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/DustSkin.java
new file mode 100755
index 0000000..b335151
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/DustSkin.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.border.CompositeBorderPainter;
+import org.pushingpixels.substance.api.painter.border.DelegateBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.MatteDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.StandardFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter;
+import org.pushingpixels.substance.api.painter.overlay.TopLineOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Dust</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.2
+ */
+public class DustSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Dust";
+
+ /**
+ * Overlay painter to paint a dark line along the bottom edge of the
+ * menubar.
+ */
+ private BottomLineOverlayPainter menuOverlayPainter;
+
+ /**
+ * Overlay painter to paint a light line along the top edge of the toolbars.
+ */
+ private TopLineOverlayPainter toolbarOverlayPainter;
+
+ /**
+ * Creates a new <code>Dust</code> skin.
+ */
+ public DustSkin() {
+ SubstanceSkin.ColorSchemes schemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/dust.colorschemes");
+ SubstanceColorScheme activeScheme = schemes.get("Dust Active");
+ SubstanceColorScheme enabledScheme = schemes.get("Dust Enabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, enabledScheme);
+ defaultSchemeBundle.registerColorScheme(enabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(activeScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED);
+
+ // borders
+ SubstanceColorScheme borderEnabledScheme = schemes
+ .get("Dust Border Enabled");
+ SubstanceColorScheme borderActiveScheme = schemes
+ .get("Dust Border Active");
+
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED,
+ ComponentState.DISABLED_SELECTED,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(borderActiveScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState
+ .getActiveStates());
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.MARK);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ // header color scheme bundle
+ SubstanceColorScheme headerActiveScheme = schemes
+ .get("Dust Header Active");
+ SubstanceColorScheme headerEnabledScheme = schemes
+ .get("Dust Header Enabled");
+
+ SubstanceColorScheme headerWatermarkScheme = schemes
+ .get("Dust Header Watermark");
+
+ SubstanceColorScheme headerSeparatorScheme = schemes
+ .get("Dust Header Separator");
+
+ SubstanceColorScheme headerBorderScheme = schemes
+ .get("Dust Header Border");
+
+ SubstanceColorSchemeBundle headerSchemeBundle = new SubstanceColorSchemeBundle(
+ headerActiveScheme, headerEnabledScheme, headerEnabledScheme);
+ headerSchemeBundle.registerColorScheme(headerEnabledScheme, 0.7f,
+ ComponentState.DISABLED_UNSELECTED);
+ headerSchemeBundle.registerColorScheme(headerActiveScheme, 0.7f,
+ ComponentState.DISABLED_SELECTED);
+
+ headerSchemeBundle.registerColorScheme(headerBorderScheme,
+ ColorSchemeAssociationKind.BORDER);
+ headerSchemeBundle.registerColorScheme(headerSeparatorScheme,
+ ColorSchemeAssociationKind.SEPARATOR);
+
+ headerSchemeBundle.registerHighlightColorScheme(headerActiveScheme,
+ 1.0f);
+ // the next line is to have consistent coloring during the rollover
+ // menu animations
+ headerSchemeBundle.registerHighlightColorScheme(headerActiveScheme,
+ 0.0f, ComponentState.ENABLED);
+
+ this.registerDecorationAreaSchemeBundle(headerSchemeBundle,
+ DecorationAreaType.TOOLBAR);
+
+ this.registerDecorationAreaSchemeBundle(headerSchemeBundle,
+ headerWatermarkScheme, DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER, DecorationAreaType.FOOTER);
+
+ setSelectedTabFadeStart(0.1);
+ setSelectedTabFadeEnd(0.3);
+
+ // add two overlay painters to create a bezel line between
+ // menu bar and toolbars
+ this.menuOverlayPainter = new BottomLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getUltraDarkColor().darker();
+ }
+ });
+ this.toolbarOverlayPainter = new TopLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ Color fg = scheme.getForegroundColor();
+ return new Color(fg.getRed(), fg.getGreen(), fg
+ .getBlue(), 32);
+ }
+ });
+ this.addOverlayPainter(this.menuOverlayPainter,
+ DecorationAreaType.HEADER);
+ this.addOverlayPainter(this.toolbarOverlayPainter,
+ DecorationAreaType.TOOLBAR);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.watermark = null;
+ this.fillPainter = new StandardFillPainter();
+ this.decorationPainter = new MatteDecorationPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+ this.borderPainter = new CompositeBorderPainter("Dust",
+ new ClassicBorderPainter(), new DelegateBorderPainter(
+ "Dust Inner", new ClassicBorderPainter(), 0x60FFFFFF,
+ 0x30FFFFFF, 0x18FFFFFF, new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.shiftBackground(
+ scheme.getUltraLightColor(), 0.8).tint(
+ 0.6).saturate(0.2);
+ }
+ }));
+ }
+
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/EmeraldDuskSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/EmeraldDuskSkin.java
new file mode 100755
index 0000000..87742b1
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/EmeraldDuskSkin.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.CharcoalColorScheme;
+import org.pushingpixels.substance.api.painter.border.GlassBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.GlassFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Emerald Dusk</code> skin. This class is part of officially supported
+ * API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.2
+ */
+public class EmeraldDuskSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Emerald Dusk";
+
+ /**
+ * Creates a new <code>Emerald Dusk</code> skin.
+ */
+ public EmeraldDuskSkin() {
+ SubstanceColorScheme activeScheme = new CharcoalColorScheme().hueShift(
+ 0.3).named("Emerald Dusk Active");
+ SubstanceColorScheme enabledScheme = new CharcoalColorScheme()
+ .hueShift(0.4).named("Emerald Dusk Enabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, enabledScheme);
+ defaultSchemeBundle.registerColorScheme(activeScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED);
+ defaultSchemeBundle.registerColorScheme(enabledScheme, 0.4f,
+ ComponentState.DISABLED_UNSELECTED);
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerAsDecorationArea(enabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER, DecorationAreaType.FOOTER,
+ DecorationAreaType.GENERAL, DecorationAreaType.TOOLBAR);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new GlassFillPainter();
+ this.decorationPainter = new ArcDecorationPainter();
+ this.borderPainter = new GlassBorderPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/GeminiSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/GeminiSkin.java
new file mode 100755
index 0000000..ad3c1cf
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/GeminiSkin.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.border.CompositeBorderPainter;
+import org.pushingpixels.substance.api.painter.border.DelegateFractionBasedBorderPainter;
+import org.pushingpixels.substance.api.painter.border.FractionBasedBorderPainter;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.MatteDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.FractionBasedFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomShadowOverlayPainter;
+import org.pushingpixels.substance.api.painter.overlay.TopBezelOverlayPainter;
+import org.pushingpixels.substance.api.painter.overlay.TopLineOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Gemini</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.3
+ */
+public class GeminiSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Gemini";
+
+ /**
+ * Overlay painter to paint a dark line along the bottom edge of the
+ * menubar.
+ */
+ private BottomLineOverlayPainter menuOverlayPainter;
+
+ /**
+ * Overlay painter to paint a light line along the top edge of the toolbars.
+ */
+ private TopLineOverlayPainter toolbarOverlayPainter;
+
+ /**
+ * Overlay painter to paint a dark line along the bottom edge of the
+ * toolbars.
+ */
+ private BottomLineOverlayPainter toolbarBottomLineOverlayPainter;
+
+ /**
+ * Overlay painter to paint a bezel line along the top edge of the footer.
+ */
+ private TopBezelOverlayPainter footerTopBezelOverlayPainter;
+
+ /**
+ * Creates a new <code>Gemini</code> skin.
+ */
+ public GeminiSkin() {
+ SubstanceSkin.ColorSchemes schemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/gemini.colorschemes");
+
+ SubstanceColorScheme grayScheme = schemes.get("Gemini Gray");
+ SubstanceColorScheme lightGrayScheme = schemes.get("Gemini Light Gray");
+
+ // use the same color scheme for active and enabled controls
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ grayScheme, grayScheme, lightGrayScheme);
+
+ // highlight fill scheme + custom alpha for rollover unselected state
+ SubstanceColorScheme highlightScheme = schemes.get("Gemini Highlight");
+ SubstanceColorScheme highlightBorderScheme = schemes
+ .get("Gemini Highlight Border");
+ applyHighlightColorScheme(defaultSchemeBundle, highlightScheme,
+ highlightBorderScheme);
+
+ // borders, separators, marks
+ SubstanceColorScheme grayBorderScheme = schemes
+ .get("Gemini Gray Border");
+ SubstanceColorScheme lightGrayBorderScheme = schemes
+ .get("Gemini Light Gray Border");
+ SubstanceColorScheme lightGraySeparatorScheme = schemes
+ .get("Gemini Light Gray Separator");
+ defaultSchemeBundle.registerColorScheme(grayBorderScheme,
+ ColorSchemeAssociationKind.BORDER);
+ defaultSchemeBundle.registerColorScheme(lightGrayBorderScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.DISABLED_DEFAULT,
+ ComponentState.DISABLED_SELECTED,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(grayScheme,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(lightGraySeparatorScheme,
+ ColorSchemeAssociationKind.SEPARATOR);
+ defaultSchemeBundle.registerColorScheme(grayScheme,
+ ColorSchemeAssociationKind.MARK);
+
+ defaultSchemeBundle.registerColorScheme(lightGrayScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(lightGrayScheme, 0.7f,
+ ComponentState.DISABLED_SELECTED);
+
+ SubstanceColorScheme whiteBackgroundScheme = schemes
+ .get("Gemini White Background");
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ whiteBackgroundScheme, DecorationAreaType.NONE);
+
+ // general color scheme bundle
+ SubstanceColorSchemeBundle generalSchemeBundle = new SubstanceColorSchemeBundle(
+ grayScheme, grayScheme, lightGrayScheme);
+ generalSchemeBundle.registerColorScheme(grayScheme,
+ ComponentState.ROLLOVER_UNSELECTED);
+ generalSchemeBundle.registerColorScheme(grayScheme,
+ ColorSchemeAssociationKind.MARK);
+ generalSchemeBundle.registerColorScheme(grayBorderScheme,
+ ColorSchemeAssociationKind.BORDER);
+ applyHighlightColorScheme(generalSchemeBundle, highlightScheme,
+ highlightBorderScheme);
+ this.registerDecorationAreaSchemeBundle(generalSchemeBundle,
+ grayScheme, DecorationAreaType.GENERAL,
+ DecorationAreaType.FOOTER);
+
+ // header color scheme bundle
+ SubstanceColorScheme blackColorScheme = schemes.get("Gemini Black");
+ SubstanceColorSchemeBundle headerSchemeBundle = new SubstanceColorSchemeBundle(
+ blackColorScheme, blackColorScheme, blackColorScheme);
+ headerSchemeBundle.registerColorScheme(blackColorScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED,
+ ComponentState.DISABLED_UNSELECTED);
+ headerSchemeBundle.registerColorScheme(blackColorScheme,
+ ComponentState.ROLLOVER_UNSELECTED);
+ headerSchemeBundle.registerColorScheme(blackColorScheme,
+ ColorSchemeAssociationKind.MARK);
+ headerSchemeBundle.registerColorScheme(blackColorScheme,
+ ColorSchemeAssociationKind.BORDER);
+ applyHighlightColorScheme(headerSchemeBundle, highlightScheme,
+ highlightBorderScheme);
+ this.registerDecorationAreaSchemeBundle(headerSchemeBundle,
+ blackColorScheme, DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ // toolbar color scheme bundle
+ SubstanceColorScheme darkBlueColorScheme = schemes
+ .get("Gemini Dark Blue");
+ SubstanceColorScheme darkBlueBackgroundColorScheme = schemes
+ .get("Gemini Dark Blue Background");
+ SubstanceColorSchemeBundle toolbarSchemeBundle = new SubstanceColorSchemeBundle(
+ blackColorScheme, darkBlueColorScheme, darkBlueColorScheme);
+ toolbarSchemeBundle.registerColorScheme(blackColorScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED);
+ toolbarSchemeBundle.registerColorScheme(darkBlueColorScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ toolbarSchemeBundle.registerColorScheme(blackColorScheme,
+ ComponentState.ROLLOVER_UNSELECTED);
+ toolbarSchemeBundle.registerColorScheme(darkBlueColorScheme,
+ ColorSchemeAssociationKind.MARK);
+ toolbarSchemeBundle.registerColorScheme(darkBlueColorScheme,
+ ColorSchemeAssociationKind.BORDER);
+ applyHighlightColorScheme(toolbarSchemeBundle, highlightScheme,
+ darkBlueColorScheme);
+ this.registerDecorationAreaSchemeBundle(toolbarSchemeBundle,
+ darkBlueBackgroundColorScheme, DecorationAreaType.TOOLBAR);
+
+ this.setSelectedTabFadeStart(0.15);
+ this.setSelectedTabFadeEnd(0.25);
+
+ // add an overlay painter to paint a bezel line along the top
+ // edge of footer
+ this.footerTopBezelOverlayPainter = new TopBezelOverlayPainter(
+ ColorSchemeSingleColorQuery.DARK,
+ ColorSchemeSingleColorQuery.ULTRALIGHT);
+ this.addOverlayPainter(this.footerTopBezelOverlayPainter,
+ DecorationAreaType.FOOTER);
+
+ // add two overlay painters to create a bezel line between
+ // menu bar and toolbars
+ this.menuOverlayPainter = new BottomLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getUltraDarkColor().darker();
+ }
+ });
+ this.toolbarOverlayPainter = new TopLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ Color fg = scheme.getForegroundColor();
+ return new Color(fg.getRed(), fg.getGreen(), fg
+ .getBlue(), 32);
+ }
+ });
+ this.addOverlayPainter(this.menuOverlayPainter,
+ DecorationAreaType.HEADER);
+ this.addOverlayPainter(this.toolbarOverlayPainter,
+ DecorationAreaType.TOOLBAR);
+
+ // add overlay painter to paint drop shadows along the bottom
+ // edges of toolbars
+ this.addOverlayPainter(BottomShadowOverlayPainter.getInstance(),
+ DecorationAreaType.TOOLBAR);
+
+ // add overlay painter to paint a dark line along the bottom
+ // edge of toolbars
+ this.toolbarBottomLineOverlayPainter = new BottomLineOverlayPainter(
+ ColorSchemeSingleColorQuery.ULTRADARK);
+ this.addOverlayPainter(this.toolbarBottomLineOverlayPainter,
+ DecorationAreaType.TOOLBAR);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.watermark = null;
+ this.fillPainter = new FractionBasedFillPainter("Gemini", new float[] {
+ 0.0f, 0.5f, 1.0f }, new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.EXTRALIGHT,
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.MID });
+
+ this.decorationPainter = new MatteDecorationPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+
+ FractionBasedBorderPainter outerBorderPainter = new FractionBasedBorderPainter(
+ "Gemini Outer", new float[] { 0.0f, 0.5f, 1.0f },
+ new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.ULTRADARK,
+ ColorSchemeSingleColorQuery.ULTRADARK,
+ ColorSchemeSingleColorQuery.ULTRADARK });
+ SubstanceBorderPainter innerBorderPainter = new DelegateFractionBasedBorderPainter(
+ "Gemini Inner", outerBorderPainter, new int[] { 0x60FFFFFF,
+ 0x40FFFFFF, 0x20FFFFFF }, new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.tint(0.7f);
+ }
+ });
+
+ this.borderPainter = new CompositeBorderPainter("Gemini",
+ outerBorderPainter, innerBorderPainter);
+ this.highlightBorderPainter = new ClassicBorderPainter();
+ }
+
+ /**
+ * Applies the specified highlight schemes on the relevant parts of the
+ * specified scheme bundle.
+ *
+ * @param schemeBundle
+ * Scheme bundle.
+ * @param highlightScheme
+ * Highlight scheme.
+ * @param highlightBorderScheme
+ * Highlight border scheme.
+ */
+ private static void applyHighlightColorScheme(
+ SubstanceColorSchemeBundle schemeBundle,
+ SubstanceColorScheme highlightScheme,
+ SubstanceColorScheme highlightBorderScheme) {
+
+ // specify custom alpha values for the highlights
+ schemeBundle.registerHighlightColorScheme(highlightScheme, 0.75f,
+ ComponentState.ROLLOVER_UNSELECTED);
+ schemeBundle.registerHighlightColorScheme(highlightScheme, 0.9f,
+ ComponentState.SELECTED);
+ schemeBundle.registerHighlightColorScheme(highlightScheme, 1.0f,
+ ComponentState.ROLLOVER_SELECTED);
+ schemeBundle.registerHighlightColorScheme(highlightScheme, 1.0f,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ // use for borders on rollover controls
+ schemeBundle.registerColorScheme(highlightBorderScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ROLLOVER_ARMED,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.ROLLOVER_UNSELECTED);
+
+ // use for fill of selected controls
+ schemeBundle.registerColorScheme(highlightScheme,
+ ColorSchemeAssociationKind.FILL, ComponentState.SELECTED,
+ ComponentState.ROLLOVER_SELECTED);
+
+ // use for borders of highlights
+ schemeBundle.registerColorScheme(highlightScheme,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER, ComponentState
+ .getActiveStates());
+
+ // use for text highlight
+ schemeBundle.registerColorScheme(highlightScheme,
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ ComponentState.SELECTED, ComponentState.ROLLOVER_SELECTED);
+
+ // use for armed controls
+ schemeBundle.registerColorScheme(highlightScheme, ComponentState.ARMED,
+ ComponentState.ROLLOVER_ARMED);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/GraphiteAquaSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/GraphiteAquaSkin.java
new file mode 100644
index 0000000..b9983d4
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/GraphiteAquaSkin.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ColorSchemeSingleColorQuery;
+import org.pushingpixels.substance.api.ColorSchemeTransform;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceColorSchemeBundle;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.border.CompositeBorderPainter;
+import org.pushingpixels.substance.api.painter.border.DelegateBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.FlatDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.FractionBasedFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.FractionBasedHighlightPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Graphite Aqua</code> skin. This class is part of officially supported
+ * API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.3
+ */
+public class GraphiteAquaSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Graphite Aqua";
+
+ /**
+ * Creates a new <code>Graphite</code> skin.
+ */
+ public GraphiteAquaSkin() {
+ SubstanceSkin.ColorSchemes schemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/graphite.colorschemes");
+
+ SubstanceColorScheme selectedDisabledScheme = schemes
+ .get("Graphite Selected Disabled");
+ SubstanceColorScheme selectedScheme = schemes.get("Graphite Selected");
+ SubstanceColorScheme disabledScheme = schemes.get("Graphite Disabled");
+
+ SubstanceColorScheme enabledScheme = schemes.get("Graphite Enabled");
+ SubstanceColorScheme backgroundScheme = schemes
+ .get("Graphite Background");
+
+ // use the same color scheme for active and enabled controls
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ enabledScheme, enabledScheme, disabledScheme);
+
+ // highlight fill scheme + custom alpha for rollover unselected state
+ SubstanceColorScheme highlightScheme = schemes.get("Graphite Aqua");
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme,
+ 0.75f, ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme, 0.9f,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme, 1.0f,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme, 1.0f,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ defaultSchemeBundle.registerColorScheme(highlightScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ROLLOVER_ARMED,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(highlightScheme,
+ ColorSchemeAssociationKind.FILL, ComponentState.SELECTED,
+ ComponentState.ROLLOVER_SELECTED);
+
+ // border schemes
+ SubstanceColorScheme borderScheme = schemes.get("Graphite Border");
+ SubstanceColorScheme separatorScheme = schemes
+ .get("Graphite Separator");
+ defaultSchemeBundle.registerColorScheme(highlightScheme,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER, ComponentState
+ .getActiveStates());
+ defaultSchemeBundle.registerColorScheme(borderScheme,
+ ColorSchemeAssociationKind.BORDER);
+ defaultSchemeBundle.registerColorScheme(separatorScheme,
+ ColorSchemeAssociationKind.SEPARATOR);
+ defaultSchemeBundle.registerColorScheme(borderScheme,
+ ColorSchemeAssociationKind.MARK);
+
+ // text highlight scheme
+ defaultSchemeBundle.registerColorScheme(highlightScheme,
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ ComponentState.SELECTED, ComponentState.ROLLOVER_SELECTED);
+
+ defaultSchemeBundle.registerColorScheme(highlightScheme,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ defaultSchemeBundle.registerColorScheme(disabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(selectedDisabledScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED);
+
+ defaultSchemeBundle.registerColorScheme(highlightScheme,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerColorScheme(selectedScheme,
+ ComponentState.SELECTED);
+
+ SubstanceColorScheme tabHighlightScheme = schemes
+ .get("Graphite Tab Highlight");
+ defaultSchemeBundle.registerColorScheme(tabHighlightScheme,
+ ColorSchemeAssociationKind.TAB,
+ ComponentState.ROLLOVER_SELECTED);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ backgroundScheme, DecorationAreaType.NONE);
+
+ this.setSelectedTabFadeStart(0.15);
+ this.setSelectedTabFadeEnd(0.25);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.watermark = null;
+ this.fillPainter = new FractionBasedFillPainter("Graphite Aqua",
+ new float[] { 0.0f, 0.5f, 1.0f },
+ new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.MID,
+ ColorSchemeSingleColorQuery.MID });
+
+ this.decorationPainter = new FlatDecorationPainter();
+ this.highlightPainter = new FractionBasedHighlightPainter(
+ "Graphite Aqua", new float[] { 0.0f, 0.5f, 1.0f },
+ new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.EXTRALIGHT,
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.MID });
+ this.borderPainter = new CompositeBorderPainter("Graphite Aqua",
+ new ClassicBorderPainter(), new DelegateBorderPainter(
+ "Graphite Aqua Inner", new ClassicBorderPainter(),
+ 0xC0FFFFFF, 0x90FFFFFF, 0x30FFFFFF,
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.tint(0.25f);
+ }
+ }));
+ this.highlightBorderPainter = new ClassicBorderPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/GraphiteGlassSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/GraphiteGlassSkin.java
new file mode 100755
index 0000000..a7004fc
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/GraphiteGlassSkin.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.EbonyColorScheme;
+import org.pushingpixels.substance.api.painter.border.*;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.GlassFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.GlassHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter;
+import org.pushingpixels.substance.api.painter.overlay.TopLineOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Graphite Glass</code> skin. This class is part of officially supported
+ * API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.3
+ */
+public class GraphiteGlassSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Graphite Glass";
+
+ /**
+ * Overlay painter to paint a dark line along the bottom edge of the
+ * menubar.
+ */
+ private BottomLineOverlayPainter menuOverlayPainter;
+
+ /**
+ * Overlay painter to paint a light line along the top edge of the toolbars.
+ */
+ private TopLineOverlayPainter toolbarOverlayPainter;
+
+ /**
+ * Creates a new <code>Graphite</code> skin.
+ */
+ public GraphiteGlassSkin() {
+ SubstanceSkin.ColorSchemes schemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/graphite.colorschemes");
+
+ SubstanceColorScheme activeScheme = schemes.get("Graphite Active");
+ SubstanceColorScheme selectedDisabledScheme = schemes
+ .get("Graphite Selected Disabled");
+ SubstanceColorScheme selectedScheme = schemes.get("Graphite Selected");
+ SubstanceColorScheme disabledScheme = schemes.get("Graphite Disabled");
+
+ SubstanceColorScheme enabledScheme = schemes.get("Graphite Enabled");
+ SubstanceColorScheme backgroundScheme = schemes
+ .get("Graphite Background");
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+
+ // highlight fill scheme + custom alphas
+ SubstanceColorScheme highlightScheme = schemes
+ .get("Graphite Highlight");
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme, 0.6f,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme, 0.8f,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme, 1.0f,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme,
+ 0.75f, ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ // highlight border scheme
+ SubstanceColorScheme borderScheme = schemes.get("Graphite Border");
+ SubstanceColorScheme separatorScheme = schemes
+ .get("Graphite Separator");
+ defaultSchemeBundle.registerColorScheme(new EbonyColorScheme(),
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER, ComponentState
+ .getActiveStates());
+ defaultSchemeBundle.registerColorScheme(borderScheme,
+ ColorSchemeAssociationKind.BORDER);
+ defaultSchemeBundle.registerColorScheme(separatorScheme,
+ ColorSchemeAssociationKind.SEPARATOR);
+
+ // text highlight scheme
+ SubstanceColorScheme textHighlightScheme = schemes
+ .get("Graphite Text Highlight");
+ defaultSchemeBundle.registerColorScheme(textHighlightScheme,
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ ComponentState.SELECTED, ComponentState.ROLLOVER_SELECTED);
+
+ defaultSchemeBundle.registerColorScheme(highlightScheme,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ SubstanceColorScheme highlightMarkScheme = schemes
+ .get("Graphite Highlight Mark");
+ defaultSchemeBundle.registerColorScheme(highlightMarkScheme,
+ ColorSchemeAssociationKind.HIGHLIGHT_MARK, ComponentState
+ .getActiveStates());
+ defaultSchemeBundle.registerColorScheme(highlightMarkScheme,
+ ColorSchemeAssociationKind.MARK,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(borderScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState.SELECTED);
+
+ // register schemes for disabled states
+ defaultSchemeBundle.registerColorScheme(disabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(selectedDisabledScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED);
+
+ defaultSchemeBundle.registerColorScheme(highlightScheme,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerColorScheme(selectedScheme,
+ ComponentState.SELECTED);
+
+ SubstanceColorScheme tabHighlightScheme = schemes
+ .get("Graphite Tab Highlight");
+ defaultSchemeBundle.registerColorScheme(tabHighlightScheme,
+ ColorSchemeAssociationKind.TAB,
+ ComponentState.ROLLOVER_SELECTED);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ backgroundScheme, DecorationAreaType.NONE);
+
+ this.registerAsDecorationArea(backgroundScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);// , DecorationAreaType.TOOLBAR);
+
+ this.setSelectedTabFadeStart(0.1);
+ this.setSelectedTabFadeEnd(0.3);
+
+ // add two overlay painters to create a bezel line between
+ // menu bar and toolbars
+ this.menuOverlayPainter = new BottomLineOverlayPainter(
+ ColorSchemeSingleColorQuery.MID);
+ this.toolbarOverlayPainter = new TopLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ Color origFg = scheme.getForegroundColor();
+ return new Color(origFg.getRed(), origFg.getGreen(),
+ origFg.getBlue(), 32);
+ }
+ });
+ this.addOverlayPainter(this.menuOverlayPainter,
+ DecorationAreaType.HEADER);
+ this.addOverlayPainter(this.toolbarOverlayPainter,
+ DecorationAreaType.TOOLBAR);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.watermark = null;
+ this.fillPainter = new GlassFillPainter();
+ this.decorationPainter = new ArcDecorationPainter();
+ this.highlightPainter = new GlassHighlightPainter();
+ this.borderPainter = new CompositeBorderPainter("Graphite Glass",
+ new ClassicBorderPainter(), new DelegateBorderPainter(
+ "Graphite Glass Inner", new ClassicBorderPainter(),
+ 0xA0FFFFFF, 0x60FFFFFF, 0x60FFFFFF,
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.tint(0.25f);
+ }
+ }));
+ this.highlightBorderPainter = new ClassicBorderPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/GraphiteSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/GraphiteSkin.java
new file mode 100644
index 0000000..0790675
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/GraphiteSkin.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.EbonyColorScheme;
+import org.pushingpixels.substance.api.painter.border.*;
+import org.pushingpixels.substance.api.painter.decoration.FlatDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.FractionBasedFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Graphite</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.3
+ */
+public class GraphiteSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Graphite";
+
+ /**
+ * Creates a new <code>Graphite</code> skin.
+ */
+ public GraphiteSkin() {
+ SubstanceSkin.ColorSchemes schemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/graphite.colorschemes");
+
+ SubstanceColorScheme activeScheme = schemes.get("Graphite Active");
+ SubstanceColorScheme selectedDisabledScheme = schemes
+ .get("Graphite Selected Disabled");
+ SubstanceColorScheme selectedScheme = schemes.get("Graphite Selected");
+ SubstanceColorScheme disabledScheme = schemes.get("Graphite Disabled");
+
+ SubstanceColorScheme enabledScheme = schemes.get("Graphite Enabled");
+ SubstanceColorScheme backgroundScheme = schemes
+ .get("Graphite Background");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+
+ // highlight fill scheme + custom alpha for rollover unselected state
+ SubstanceColorScheme highlightScheme = schemes
+ .get("Graphite Highlight");
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme, 0.6f,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme, 0.8f,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme, 1.0f,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme,
+ 0.75f, ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ // highlight border scheme
+ SubstanceColorScheme borderScheme = schemes.get("Graphite Border");
+ SubstanceColorScheme separatorScheme = schemes
+ .get("Graphite Separator");
+ defaultSchemeBundle.registerColorScheme(new EbonyColorScheme(),
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER, ComponentState
+ .getActiveStates());
+ defaultSchemeBundle.registerColorScheme(borderScheme,
+ ColorSchemeAssociationKind.BORDER);
+ defaultSchemeBundle.registerColorScheme(separatorScheme,
+ ColorSchemeAssociationKind.SEPARATOR);
+
+ // text highlight scheme
+ SubstanceColorScheme textHighlightScheme = schemes
+ .get("Graphite Text Highlight");
+ defaultSchemeBundle.registerColorScheme(textHighlightScheme,
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ ComponentState.SELECTED, ComponentState.ROLLOVER_SELECTED);
+
+ defaultSchemeBundle.registerColorScheme(highlightScheme,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ SubstanceColorScheme highlightMarkScheme = schemes
+ .get("Graphite Highlight Mark");
+ defaultSchemeBundle.registerColorScheme(highlightMarkScheme,
+ ColorSchemeAssociationKind.HIGHLIGHT_MARK, ComponentState
+ .getActiveStates());
+ defaultSchemeBundle.registerColorScheme(highlightMarkScheme,
+ ColorSchemeAssociationKind.MARK,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(borderScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState.SELECTED);
+
+ defaultSchemeBundle.registerColorScheme(disabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(selectedDisabledScheme, 0.65f,
+ ComponentState.DISABLED_SELECTED);
+
+ defaultSchemeBundle.registerColorScheme(highlightScheme,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerColorScheme(selectedScheme,
+ ComponentState.SELECTED);
+
+ SubstanceColorScheme tabHighlightScheme = schemes
+ .get("Graphite Tab Highlight");
+ defaultSchemeBundle.registerColorScheme(tabHighlightScheme,
+ ColorSchemeAssociationKind.TAB,
+ ComponentState.ROLLOVER_SELECTED);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ backgroundScheme, DecorationAreaType.NONE);
+
+ this.setSelectedTabFadeStart(0.1);
+ this.setSelectedTabFadeEnd(0.3);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.watermark = null;
+ this.fillPainter = new FractionBasedFillPainter("Graphite",
+ new float[] { 0.0f, 0.5f, 1.0f },
+ new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.ULTRALIGHT,
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.LIGHT });
+ this.decorationPainter = new FlatDecorationPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+ this.borderPainter = new CompositeBorderPainter("Graphite",
+ new ClassicBorderPainter(), new DelegateBorderPainter(
+ "Graphite Inner", new ClassicBorderPainter(),
+ 0xA0FFFFFF, 0x60FFFFFF, 0x60FFFFFF,
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.tint(0.25f);
+ }
+ }));
+ this.highlightBorderPainter = new ClassicBorderPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/MagellanSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/MagellanSkin.java
new file mode 100755
index 0000000..3d2e61f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/MagellanSkin.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.border.CompositeBorderPainter;
+import org.pushingpixels.substance.api.painter.border.DelegateBorderPainter;
+import org.pushingpixels.substance.api.painter.border.FractionBasedBorderPainter;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.MatteDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.FractionBasedFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomShadowOverlayPainter;
+import org.pushingpixels.substance.api.painter.overlay.TopBezelOverlayPainter;
+import org.pushingpixels.substance.api.painter.overlay.TopLineOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Magellan</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.3
+ */
+public class MagellanSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Magellan";
+
+ /**
+ * Overlay painter to paint a dark line along the bottom edge of the
+ * toolbars.
+ */
+ private BottomLineOverlayPainter toolbarBottomLineOverlayPainter;
+
+ /**
+ * Overlay painter to paint a light line along the top edge of the toolbars.
+ */
+ private TopLineOverlayPainter toolbarTopLineOverlayPainter;
+
+ /**
+ * Overlay painter to paint a bezel line along the top edge of the footer.
+ */
+ private TopBezelOverlayPainter footerTopBezelOverlayPainter;
+
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+
+ /**
+ * Creates a new instance of Magellan skin.
+ */
+ public MagellanSkin() {
+ SubstanceSkin.ColorSchemes colorSchemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/magellan.colorschemes");
+
+ SubstanceColorScheme blueControlsActive = colorSchemes
+ .get("Magellan Blue Controls Active");
+ SubstanceColorScheme blueControlsEnabled = colorSchemes
+ .get("Magellan Blue Controls Enabled");
+
+ SubstanceColorSchemeBundle defaultColorSchemeBundle = new SubstanceColorSchemeBundle(
+ blueControlsActive, blueControlsEnabled, blueControlsEnabled);
+ defaultColorSchemeBundle.registerColorScheme(blueControlsEnabled, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultColorSchemeBundle.registerColorScheme(blueControlsActive, 0.5f,
+ ComponentState.DISABLED_SELECTED);
+
+ // color schemes for the active states
+ SubstanceColorScheme blueControlsActiveBorder = colorSchemes
+ .get("Magellan Blue Controls Active Border");
+ SubstanceColorScheme blueControlsEnabledBorder = colorSchemes
+ .get("Magellan Blue Controls Enabled Border");
+ defaultColorSchemeBundle.registerColorScheme(blueControlsActiveBorder,
+ ColorSchemeAssociationKind.BORDER, ComponentState
+ .getActiveStates());
+ defaultColorSchemeBundle.registerColorScheme(blueControlsActiveBorder,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.DISABLED_SELECTED);
+ defaultColorSchemeBundle.registerColorScheme(blueControlsEnabledBorder,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED,
+ ComponentState.DISABLED_UNSELECTED);
+
+ // color schemes for the pressed states
+ SubstanceColorScheme blueControlsPressed = colorSchemes
+ .get("Magellan Blue Controls Pressed");
+ SubstanceColorScheme blueControlsPressedBorder = colorSchemes
+ .get("Magellan Blue Controls Pressed Border");
+ defaultColorSchemeBundle.registerColorScheme(blueControlsPressed,
+ ColorSchemeAssociationKind.FILL,
+ ComponentState.PRESSED_SELECTED,
+ ComponentState.PRESSED_UNSELECTED);
+ defaultColorSchemeBundle.registerColorScheme(blueControlsPressedBorder,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.PRESSED_SELECTED,
+ ComponentState.PRESSED_UNSELECTED);
+
+ // color schemes for the rollover / armed states
+ SubstanceColorScheme greenControls = colorSchemes
+ .get("Magellan Green Controls");
+ SubstanceColorScheme greenControlsMark = colorSchemes
+ .get("Magellan Green Controls Mark");
+ SubstanceColorScheme greenControlsBorder = colorSchemes
+ .get("Magellan Green Controls Border");
+ defaultColorSchemeBundle.registerColorScheme(greenControls,
+ ColorSchemeAssociationKind.FILL,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.ROLLOVER_UNSELECTED, ComponentState.ARMED,
+ ComponentState.ROLLOVER_ARMED);
+ defaultColorSchemeBundle.registerColorScheme(greenControlsMark,
+ ColorSchemeAssociationKind.MARK,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.ROLLOVER_UNSELECTED, ComponentState.ARMED,
+ ComponentState.ROLLOVER_ARMED);
+ defaultColorSchemeBundle.registerColorScheme(greenControlsBorder,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.ROLLOVER_UNSELECTED, ComponentState.ARMED,
+ ComponentState.ROLLOVER_ARMED);
+
+ // color scheme for the uneditable text components
+ ComponentState uneditable = new ComponentState("uneditable",
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE },
+ new ComponentStateFacet[] { ComponentStateFacet.EDITABLE });
+ SubstanceColorScheme uneditableControls = colorSchemes
+ .get("Magellan Uneditable Controls");
+ defaultColorSchemeBundle.registerColorScheme(uneditableControls,
+ ColorSchemeAssociationKind.FILL, uneditable);
+
+ // color scheme for the selected state - preventing fallback to the
+ // rollover selected state
+ defaultColorSchemeBundle.registerColorScheme(blueControlsActive,
+ ColorSchemeAssociationKind.FILL, ComponentState.SELECTED);
+
+ // highlight alphas
+ defaultColorSchemeBundle.registerHighlightColorScheme(greenControls,
+ 0.7f, ComponentState.ROLLOVER_UNSELECTED);
+ defaultColorSchemeBundle.registerHighlightColorScheme(greenControls,
+ 0.8f, ComponentState.SELECTED);
+ defaultColorSchemeBundle.registerHighlightColorScheme(greenControls,
+ 0.95f, ComponentState.ROLLOVER_SELECTED);
+ defaultColorSchemeBundle.registerHighlightColorScheme(greenControls,
+ 1.0f, ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ SubstanceColorScheme lightBlueBackground = colorSchemes
+ .get("Magellan Light Blue Background");
+
+ this.registerDecorationAreaSchemeBundle(defaultColorSchemeBundle,
+ lightBlueBackground, DecorationAreaType.NONE);
+
+ SubstanceColorScheme mediumBlueBackground = colorSchemes
+ .get("Magellan Medium Blue Background");
+ SubstanceColorScheme darkBlueBackground = colorSchemes
+ .get("Magellan Dark Blue Background");
+ this.registerAsDecorationArea(mediumBlueBackground,
+ DecorationAreaType.GENERAL, DecorationAreaType.TOOLBAR);
+ this.registerAsDecorationArea(darkBlueBackground,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ SubstanceColorScheme lightBlueControlsActive = colorSchemes
+ .get("Magellan Light Blue Controls Active");
+ SubstanceColorScheme lightBlueControlsEnabled = colorSchemes
+ .get("Magellan Light Blue Controls Enabled");
+ SubstanceColorScheme lightBlueBordersEnabled = colorSchemes
+ .get("Magellan Light Blue Borders Enabled");
+ SubstanceColorSchemeBundle footerColorSchemeBundle = new SubstanceColorSchemeBundle(
+ lightBlueControlsActive, lightBlueControlsEnabled,
+ lightBlueControlsEnabled);
+ footerColorSchemeBundle.registerColorScheme(lightBlueControlsEnabled,
+ 0.5f, ComponentState.DISABLED_UNSELECTED);
+ footerColorSchemeBundle.registerColorScheme(lightBlueControlsActive,
+ 0.5f, ComponentState.DISABLED_SELECTED);
+ footerColorSchemeBundle.registerColorScheme(lightBlueBordersEnabled,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED);
+
+ SubstanceColorScheme lightBlueSeparator = colorSchemes
+ .get("Magellan Light Blue Separator");
+ footerColorSchemeBundle.registerColorScheme(lightBlueSeparator,
+ ColorSchemeAssociationKind.SEPARATOR);
+
+ SubstanceColorScheme ultraLightBlueBackground = colorSchemes
+ .get("Magellan Ultralight Blue Background");
+ this.registerDecorationAreaSchemeBundle(footerColorSchemeBundle,
+ ultraLightBlueBackground, DecorationAreaType.FOOTER);
+
+ // Add overlay painter to paint drop shadows along the bottom
+ // edges of toolbars
+ this.addOverlayPainter(BottomShadowOverlayPainter.getInstance(),
+ DecorationAreaType.TOOLBAR);
+
+ // add an overlay painter to paint a dark line along the bottom
+ // edge of toolbars
+ this.toolbarBottomLineOverlayPainter = new BottomLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getUltraDarkColor();
+ }
+ });
+ this.addOverlayPainter(this.toolbarBottomLineOverlayPainter,
+ DecorationAreaType.TOOLBAR);
+
+ // add an overlay painter to paint a light line along the top
+ // edge of toolbars
+ this.toolbarTopLineOverlayPainter = new TopLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ Color fg = scheme.getForegroundColor();
+ return new Color(fg.getRed(), fg.getGreen(), fg
+ .getBlue(), 40);
+ }
+ });
+ this.addOverlayPainter(this.toolbarTopLineOverlayPainter,
+ DecorationAreaType.TOOLBAR);
+
+ // add an overlay painter to paint a bezel line along the top
+ // edge of footer
+ this.footerTopBezelOverlayPainter = new TopBezelOverlayPainter(
+ ColorSchemeSingleColorQuery.FOREGROUND,
+ ColorSchemeSingleColorQuery.ULTRALIGHT);
+ this.addOverlayPainter(this.footerTopBezelOverlayPainter,
+ DecorationAreaType.FOOTER);
+
+ setSelectedTabFadeStart(0.2);
+ setSelectedTabFadeEnd(0.9);
+
+ SubstanceBorderPainter outerBorderPainter = new FractionBasedBorderPainter(
+ "Magellan Outer", new float[] { 0.0f, 0.5f, 1.0f },
+ new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.ULTRADARK,
+ ColorSchemeSingleColorQuery.DARK,
+ ColorSchemeSingleColorQuery.DARK });
+ SubstanceBorderPainter innerBorderPainter = new DelegateBorderPainter(
+ "Magellan Inner", new ClassicBorderPainter(), 0xA0FFFFFF,
+ 0x60FFFFFF, 0x40FFFFFF, new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.tint(0.5);
+ }
+ });
+ this.borderPainter = new CompositeBorderPainter("Magellan",
+ outerBorderPainter, innerBorderPainter);
+ this.fillPainter = new FractionBasedFillPainter("Magellan",
+ new float[] { 0.0f, 0.5f, 1.0f },
+ new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.EXTRALIGHT,
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.MID });
+ this.highlightPainter = new ClassicHighlightPainter();
+ this.decorationPainter = new MatteDecorationPainter();
+ this.buttonShaper = new ClassicButtonShaper();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/MarinerSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/MarinerSkin.java
new file mode 100755
index 0000000..f1e3cdc
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/MarinerSkin.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.border.FractionBasedBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.MatteDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.FractionBasedFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.*;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.api.watermark.SubstanceCrosshatchWatermark;
+import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * <code>Mariner</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 6.1
+ */
+public class MarinerSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Mariner";
+
+ /**
+ * Overlay painter to paint a dark line along the bottom edge of the
+ * menubar.
+ */
+ private BottomLineOverlayPainter menuOverlayPainter;
+
+ /**
+ * Overlay painter to paint a light line along the top edge of the toolbars.
+ */
+ private TopLineOverlayPainter toolbarOverlayPainter;
+
+ /**
+ * Overlay painter to paint a dark line along the bottom edge of the
+ * toolbars.
+ */
+ private BottomLineOverlayPainter toolbarBottomLineOverlayPainter;
+
+ /**
+ * Overlay painter to paint a bezel line along the top edge of the footer.
+ */
+ private TopBezelOverlayPainter footerTopBezelOverlayPainter;
+
+ /**
+ * Creates a new <code>Mariner</code> skin.
+ */
+ public MarinerSkin() {
+ SubstanceSkin.ColorSchemes schemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/mariner.colorschemes");
+
+ SubstanceColorScheme activeScheme = schemes.get("Mariner Active");
+ SubstanceColorScheme enabledScheme = schemes.get("Mariner Enabled");
+ SubstanceColorScheme disabledScheme = schemes.get("Mariner Disabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+
+ defaultSchemeBundle.registerColorScheme(activeScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED);
+ defaultSchemeBundle.registerColorScheme(disabledScheme, 0.8f,
+ ComponentState.DISABLED_UNSELECTED);
+
+ // borders
+ SubstanceColorScheme activeBorderScheme = schemes
+ .get("Mariner Active Border");
+ SubstanceColorScheme enabledBorderScheme = schemes
+ .get("Mariner Enabled Border");
+ defaultSchemeBundle.registerColorScheme(activeBorderScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState
+ .getActiveStates());
+ defaultSchemeBundle.registerColorScheme(activeBorderScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.DISABLED_SELECTED);
+ defaultSchemeBundle.registerColorScheme(enabledBorderScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED);
+
+ // marks
+ SubstanceColorScheme activeMarkScheme = schemes
+ .get("Mariner Active Mark");
+ SubstanceColorScheme enabledMarkScheme = schemes
+ .get("Mariner Enabled Mark");
+ defaultSchemeBundle.registerColorScheme(activeMarkScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState
+ .getActiveStates());
+ defaultSchemeBundle.registerColorScheme(enabledMarkScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState.ENABLED);
+
+ ComponentState uneditable = new ComponentState("uneditable",
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE },
+ new ComponentStateFacet[] { ComponentStateFacet.EDITABLE });
+ SubstanceColorScheme uneditableControls = schemes
+ .get("Mariner Uneditable");
+ defaultSchemeBundle.registerColorScheme(uneditableControls,
+ ColorSchemeAssociationKind.FILL, uneditable);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ // header color scheme bundle
+ SubstanceColorScheme headerColorScheme = schemes.get("Mariner Header");
+ SubstanceColorScheme headerBorderColorScheme = schemes
+ .get("Mariner Header Border");
+ SubstanceColorSchemeBundle headerSchemeBundle = new SubstanceColorSchemeBundle(
+ headerColorScheme, headerColorScheme, headerColorScheme);
+ headerSchemeBundle.registerColorScheme(headerColorScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED,
+ ComponentState.DISABLED_UNSELECTED);
+ headerSchemeBundle.registerColorScheme(headerColorScheme,
+ ComponentState.ROLLOVER_UNSELECTED);
+ headerSchemeBundle.registerColorScheme(headerColorScheme,
+ ColorSchemeAssociationKind.MARK);
+ headerSchemeBundle.registerColorScheme(headerBorderColorScheme,
+ ColorSchemeAssociationKind.BORDER);
+ this.registerDecorationAreaSchemeBundle(headerSchemeBundle,
+ headerColorScheme, DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ // footer color scheme bundle
+ SubstanceColorScheme enabledFooterScheme = schemes
+ .get("Mariner Footer Enabled");
+ SubstanceColorScheme disabledFooterScheme = schemes
+ .get("Mariner Footer Disabled");
+
+ SubstanceColorSchemeBundle footerSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledFooterScheme, disabledFooterScheme);
+
+ footerSchemeBundle.registerColorScheme(activeScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED);
+ footerSchemeBundle.registerColorScheme(disabledFooterScheme, 0.8f,
+ ComponentState.DISABLED_UNSELECTED);
+
+ // borders
+ SubstanceColorScheme footerEnabledBorderScheme = schemes
+ .get("Mariner Footer Enabled Border");
+ footerSchemeBundle.registerColorScheme(activeBorderScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState
+ .getActiveStates());
+ footerSchemeBundle.registerColorScheme(activeBorderScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.DISABLED_SELECTED);
+ footerSchemeBundle.registerColorScheme(footerEnabledBorderScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED);
+
+ // marks
+ SubstanceColorScheme footerEnabledMarkScheme = schemes
+ .get("Mariner Footer Enabled Mark");
+ footerSchemeBundle.registerColorScheme(activeMarkScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState
+ .getActiveStates());
+ footerSchemeBundle.registerColorScheme(footerEnabledMarkScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState.ENABLED);
+
+ // separators
+ SubstanceColorScheme footerSeparatorScheme = schemes
+ .get("Mariner Footer Separator");
+ footerSchemeBundle.registerColorScheme(footerSeparatorScheme,
+ ColorSchemeAssociationKind.SEPARATOR);
+
+ SubstanceColorScheme footerWatermarkColorScheme = schemes
+ .get("Mariner Footer Watermark");
+ this.registerDecorationAreaSchemeBundle(footerSchemeBundle,
+ footerWatermarkColorScheme, DecorationAreaType.FOOTER,
+ DecorationAreaType.TOOLBAR, DecorationAreaType.GENERAL);
+
+ this.setSelectedTabFadeStart(0.15);
+ this.setSelectedTabFadeEnd(0.25);
+
+ // add an overlay painter to paint a bezel line along the top
+ // edge of footer
+ this.footerTopBezelOverlayPainter = new TopBezelOverlayPainter(
+ ColorSchemeSingleColorQuery.ULTRADARK,
+ ColorSchemeSingleColorQuery.LIGHT);
+ this.addOverlayPainter(this.footerTopBezelOverlayPainter,
+ DecorationAreaType.FOOTER);
+
+ // add two overlay painters to create a bezel line between
+ // menu bar and toolbars
+ this.menuOverlayPainter = new BottomLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getUltraDarkColor().darker();
+ }
+ });
+ this.toolbarOverlayPainter = new TopLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ Color fg = scheme.getForegroundColor();
+ return new Color(fg.getRed(), fg.getGreen(), fg
+ .getBlue(), 32);
+ }
+ });
+ this.addOverlayPainter(this.menuOverlayPainter,
+ DecorationAreaType.HEADER);
+ this.addOverlayPainter(this.toolbarOverlayPainter,
+ DecorationAreaType.TOOLBAR);
+
+ // add overlay painter to paint drop shadows along the bottom
+ // edges of toolbars
+ this.addOverlayPainter(BottomShadowOverlayPainter.getInstance(),
+ DecorationAreaType.TOOLBAR);
+
+ // add overlay painter to paint a dark line along the bottom
+ // edge of toolbars
+ this.toolbarBottomLineOverlayPainter = new BottomLineOverlayPainter(
+ ColorSchemeSingleColorQuery.ULTRADARK);
+ this.addOverlayPainter(this.toolbarBottomLineOverlayPainter,
+ DecorationAreaType.TOOLBAR);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.watermark = new TextureWatermark();
+ this.fillPainter = new FractionBasedFillPainter("Mariner", new float[] {
+ 0.0f, 0.5f, 1.0f }, new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.EXTRALIGHT,
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.MID });
+
+ this.decorationPainter = new MatteDecorationPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+
+ this.borderPainter = new FractionBasedBorderPainter("Mariner",
+ new float[] { 0.0f, 0.5f, 1.0f },
+ new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.ULTRADARK,
+ ColorSchemeSingleColorQuery.DARK,
+ ColorSchemeSingleColorQuery.MID });
+ this.highlightBorderPainter = new ClassicBorderPainter();
+
+ this.watermarkScheme = schemes.get("Mariner Watermark");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+
+ private static class TextureWatermark implements SubstanceWatermark {
+ /**
+ * Watermark image (screen-sized).
+ */
+ private static Image watermarkImage = null;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.substance.watermark.SubstanceWatermark#
+ * drawWatermarkImage(java .awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void drawWatermarkImage(Graphics graphics, Component c, int x,
+ int y, int width, int height) {
+ if (!c.isShowing())
+ return;
+ int dx = c.getLocationOnScreen().x;
+ int dy = c.getLocationOnScreen().y;
+ graphics.drawImage(TextureWatermark.watermarkImage, x, y,
+ x + width, y + height, x + dx, y + dy, x + dx + width, y
+ + dy + height, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.substance.watermark.SubstanceWatermark#
+ * updateWatermarkImage (org.pushingpixels.substance.skin.SubstanceSkin)
+ */
+ @Override
+ public boolean updateWatermarkImage(SubstanceSkin skin) {
+ // fix by Chris for bug 67 - support for multiple screens
+ Rectangle virtualBounds = new Rectangle();
+ GraphicsEnvironment ge = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice[] gds = ge.getScreenDevices();
+ for (GraphicsDevice gd : gds) {
+ GraphicsConfiguration gc = gd.getDefaultConfiguration();
+ virtualBounds = virtualBounds.union(gc.getBounds());
+ }
+
+ int screenWidth = virtualBounds.width;
+ int screenHeight = virtualBounds.height;
+ TextureWatermark.watermarkImage = SubstanceCoreUtilities
+ .getBlankImage(screenWidth, screenHeight);
+
+ Graphics2D graphics = (Graphics2D) TextureWatermark.watermarkImage
+ .getGraphics().create();
+ boolean status = this.drawWatermarkImage(skin, graphics, 0, 0,
+ screenWidth, screenHeight, false);
+ graphics.dispose();
+ return status;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.substance.api.watermark.SubstanceWatermark#
+ * previewWatermark (java.awt.Graphics,
+ * org.pushingpixels.substance.api.SubstanceSkin, int, int, int, int)
+ */
+ @Override
+ public void previewWatermark(Graphics g, SubstanceSkin skin, int x,
+ int y, int width, int height) {
+ this.drawWatermarkImage(skin, (Graphics2D) g, x, y, width, height,
+ true);
+ }
+
+ /**
+ * Draws the specified portion of the watermark image.
+ *
+ * @param skin
+ * Skin to use for painting the watermark.
+ * @param graphics
+ * Graphic context.
+ * @param x
+ * the <i>x</i> coordinate of the watermark to be drawn.
+ * @param y
+ * The <i>y</i> coordinate of the watermark to be drawn.
+ * @param width
+ * The width of the watermark to be drawn.
+ * @param height
+ * The height of the watermark to be drawn.
+ * @param isPreview
+ * Indication whether the result is a preview image.
+ * @return Indication whether the draw succeeded.
+ */
+ private boolean drawWatermarkImage(SubstanceSkin skin,
+ Graphics2D graphics, int x, int y, int width, int height,
+ boolean isPreview) {
+ Color stampColorDark;
+ Color stampColorAll;
+ //Color stampColorLight = null;
+ SubstanceColorScheme scheme = skin.getWatermarkColorScheme();
+ if (isPreview) {
+ stampColorDark = scheme.isDark() ? Color.white : Color.black;
+ stampColorAll = Color.lightGray;
+ //stampColorLight = scheme.isDark() ? Color.black : Color.white;
+ } else {
+ stampColorDark = scheme.getWatermarkDarkColor();
+ stampColorAll = scheme.getWatermarkStampColor();
+ //stampColorLight = scheme.getWatermarkLightColor();
+ }
+
+ graphics.setColor(stampColorAll);
+ graphics.fillRect(0, 0, width, height);
+
+ BufferedImage tile = SubstanceCoreUtilities.getBlankImage(8, 4);
+ int rgbDark = stampColorDark.getRGB();
+ tile.setRGB(0, 0, rgbDark);
+ tile.setRGB(0, 1, rgbDark);
+ tile.setRGB(0, 2, rgbDark);
+ tile.setRGB(0, 3, rgbDark);
+ tile.setRGB(1, 2, rgbDark);
+ tile.setRGB(2, 1, rgbDark);
+ tile.setRGB(3, 0, rgbDark);
+ tile.setRGB(4, 0, rgbDark);
+ tile.setRGB(4, 1, rgbDark);
+ tile.setRGB(4, 2, rgbDark);
+ tile.setRGB(4, 3, rgbDark);
+ tile.setRGB(5, 0, rgbDark);
+ tile.setRGB(6, 1, rgbDark);
+ tile.setRGB(7, 2, rgbDark);
+
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, 0.05f));
+ for (int row = y; row < (y + height); row += 4) {
+ for (int col = x; col < (x + width); col += 8) {
+ g2d.drawImage(tile, col, row, null);
+ }
+ }
+ g2d.dispose();
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.trait.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return SubstanceCrosshatchWatermark.getName();
+ }
+
+ /**
+ * Returns the name of all watermarks of <code>this</code> class.
+ *
+ * @return The name of all watermarks of <code>this</code> class.
+ */
+ public static String getName() {
+ return "Crosshatch";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.watermark.SubstanceWatermark#dispose()
+ */
+ @Override
+ public void dispose() {
+ watermarkImage = null;
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/MistAquaSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/MistAquaSkin.java
new file mode 100755
index 0000000..c2f20c8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/MistAquaSkin.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.AquaColorScheme;
+import org.pushingpixels.substance.api.colorscheme.LightGrayColorScheme;
+import org.pushingpixels.substance.api.colorscheme.MetallicColorScheme;
+import org.pushingpixels.substance.api.colorscheme.SteelBlueColorScheme;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.MatteDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.MatteFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.shaper.StandardButtonShaper;
+
+/**
+ * <code>Mist Aqua</code> skin. This class is experimental.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public class MistAquaSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Mist Aqua";
+
+ /**
+ * Creates a new <code>Silver Aqua</code> skin.
+ */
+ public MistAquaSkin() {
+ SubstanceColorScheme activeScheme = new AquaColorScheme();
+ SubstanceColorScheme enabledScheme = new MetallicColorScheme().shade(
+ 0.05).named("Mist Aqua Enabled");
+ SubstanceColorScheme disabledScheme = new LightGrayColorScheme().tone(
+ 0.2).named("Mist Aqua Disabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerDecorationAreaSchemeBundle(new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme),
+ new SteelBlueColorScheme().saturate(-0.3).tint(0.5).named(
+ "Mist Aqua Background"), DecorationAreaType.GENERAL);
+
+ this.registerAsDecorationArea(enabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER, DecorationAreaType.FOOTER,
+ DecorationAreaType.TOOLBAR);
+
+ this.registerAsDecorationArea(disabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE,
+ DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE);
+
+ this.buttonShaper = new StandardButtonShaper();
+ this.fillPainter = new MatteFillPainter();
+ this.borderPainter = new ClassicBorderPainter();
+
+ this.decorationPainter = new MatteDecorationPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/MistSilverSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/MistSilverSkin.java
new file mode 100755
index 0000000..d6b9627
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/MistSilverSkin.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.LightGrayColorScheme;
+import org.pushingpixels.substance.api.colorscheme.MetallicColorScheme;
+import org.pushingpixels.substance.api.colorscheme.SteelBlueColorScheme;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.MatteDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.MatteFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.shaper.StandardButtonShaper;
+
+/**
+ * <code>Mist Silver</code> skin. This class is experimental.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public class MistSilverSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Mist Silver";
+
+ /**
+ * Creates a new <code>Silver</code> skin.
+ */
+ public MistSilverSkin() {
+ SubstanceColorScheme activeScheme = new MetallicColorScheme().tint(0.1)
+ .named("Mist Silver Active");
+ SubstanceColorScheme enabledScheme = new MetallicColorScheme().shade(
+ 0.05).named("Mist Silver Enabled");
+ SubstanceColorScheme disabledScheme = new LightGrayColorScheme().tone(
+ 0.2).named("Mist Silver Disabled");
+ SubstanceColorScheme lightBlueScheme = new SteelBlueColorScheme()
+ .saturate(-0.3).tint(0.5).named("Mist Silver Light Blue");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+ defaultSchemeBundle.registerColorScheme(enabledScheme.tone(0.4),
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ ComponentState.SELECTED, ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerColorScheme(lightBlueScheme,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerColorScheme(enabledScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.SELECTED);
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerDecorationAreaSchemeBundle(new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme), lightBlueScheme,
+ DecorationAreaType.GENERAL);
+
+ this.registerAsDecorationArea(enabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER, DecorationAreaType.FOOTER,
+ DecorationAreaType.TOOLBAR);
+
+ this.registerAsDecorationArea(disabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE,
+ DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE);
+
+ this.setSelectedTabFadeStart(0.6);
+ this.setSelectedTabFadeEnd(1.0);
+
+ this.buttonShaper = new StandardButtonShaper();
+ this.fillPainter = new MatteFillPainter();
+ this.borderPainter = new ClassicBorderPainter();
+
+ this.decorationPainter = new MatteDecorationPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/ModerateSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/ModerateSkin.java
new file mode 100755
index 0000000..bc60594
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/ModerateSkin.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.LightGrayColorScheme;
+import org.pushingpixels.substance.api.colorscheme.MetallicColorScheme;
+import org.pushingpixels.substance.api.colorscheme.SteelBlueColorScheme;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ClassicDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.GlassFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Moderate</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class ModerateSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Moderate";
+
+ /**
+ * Creates a new <code>Moderate</code> skin.
+ */
+ public ModerateSkin() {
+ SubstanceColorScheme activeScheme = new SteelBlueColorScheme();
+ SubstanceColorScheme enabledScheme = new MetallicColorScheme();
+ SubstanceColorScheme disabledScheme = new LightGrayColorScheme();
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+ SubstanceSkin.ColorSchemes kitchenSinkSchemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/kitchen-sink.colorschemes");
+ SubstanceColorScheme highlightColorScheme = kitchenSinkSchemes
+ .get("Moderate Highlight");
+ defaultSchemeBundle.registerHighlightColorScheme(highlightColorScheme);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerAsDecorationArea(activeScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE);
+
+ this.registerAsDecorationArea(enabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE,
+ DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE);
+
+ this.registerAsDecorationArea(kitchenSinkSchemes
+ .get("LightGray General Watermark"),
+ DecorationAreaType.GENERAL, DecorationAreaType.HEADER);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new GlassFillPainter();
+ this.decorationPainter = new ClassicDecorationPainter();
+ this.borderPainter = new ClassicBorderPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/NebulaBrickWallSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/NebulaBrickWallSkin.java
new file mode 100644
index 0000000..8203848
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/NebulaBrickWallSkin.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.colorscheme.OrangeColorScheme;
+
+import javax.swing.UIManager;
+
+/**
+ * <code>Nebula Brick Wall</code> skin. This class is part of officially
+ * supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public class NebulaBrickWallSkin extends NebulaSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Nebula Brick Wall";
+
+ /**
+ * Creates a new <code>Nebula Brick Wall</code> skin.
+ */
+ public NebulaBrickWallSkin() {
+ super();
+
+ this.registerAsDecorationArea(new OrangeColorScheme(),
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/NebulaSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/NebulaSkin.java
new file mode 100755
index 0000000..9feabc7
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/NebulaSkin.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.FlatBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.decoration.MarbleNoiseDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.SubduedFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter;
+import org.pushingpixels.substance.api.painter.overlay.TopShadowOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Nebula</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public class NebulaSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Nebula";
+
+ /**
+ * Overlay painter to paint separator lines on some decoration areas.
+ */
+ private BottomLineOverlayPainter bottomLineOverlayPainter;
+
+ /**
+ * Creates a new <code>Nebula</code> skin.
+ */
+ public NebulaSkin() {
+ SubstanceSkin.ColorSchemes schemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/nebula.colorschemes");
+
+ SubstanceColorScheme activeScheme = schemes.get("Nebula Active");
+ SubstanceColorScheme enabledScheme = schemes.get("Nebula Enabled");
+ SubstanceColorScheme rolloverUnselectedScheme = schemes
+ .get("Nebula Rollover Unselected");
+ SubstanceColorScheme pressedScheme = schemes.get("Nebula Pressed");
+ SubstanceColorScheme rolloverSelectedScheme = schemes
+ .get("Nebula Rollover Selected");
+ SubstanceColorScheme disabledScheme = schemes.get("Nebula Disabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+ defaultSchemeBundle.registerColorScheme(rolloverUnselectedScheme,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(rolloverSelectedScheme,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerColorScheme(pressedScheme,
+ ComponentState.PRESSED_SELECTED,
+ ComponentState.PRESSED_UNSELECTED, ComponentState.ARMED,
+ ComponentState.ROLLOVER_ARMED);
+
+ defaultSchemeBundle.registerColorScheme(rolloverUnselectedScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.SELECTED);
+
+ defaultSchemeBundle.registerHighlightColorScheme(pressedScheme, 0.6f,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(pressedScheme, 0.8f,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(pressedScheme, 0.95f,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(pressedScheme, 0.8f,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ // for progress bars
+ ComponentState determinateState = new ComponentState("determinate",
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE,
+ ComponentStateFacet.DETERMINATE }, null);
+ ComponentState indeterminateState = new ComponentState("indeterminate",
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE },
+ new ComponentStateFacet[] { ComponentStateFacet.DETERMINATE });
+ SubstanceColorScheme determinateScheme = schemes
+ .get("Nebula Determinate");
+ SubstanceColorScheme determinateBorderScheme = schemes
+ .get("Nebula Determinate Border");
+ defaultSchemeBundle.registerColorScheme(determinateScheme,
+ determinateState, indeterminateState);
+ defaultSchemeBundle.registerColorScheme(determinateBorderScheme,
+ ColorSchemeAssociationKind.BORDER, determinateState,
+ indeterminateState);
+
+ ComponentState determinateDisabledState = new ComponentState(
+ "determinate disabled",
+ new ComponentStateFacet[] { ComponentStateFacet.DETERMINATE },
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE });
+ ComponentState indeterminateDisabledState = new ComponentState(
+ "indeterminate disabled", null, new ComponentStateFacet[] {
+ ComponentStateFacet.ENABLE,
+ ComponentStateFacet.DETERMINATE });
+ SubstanceColorScheme determinateDisabledScheme = schemes
+ .get("Nebula Determinate Disabled");
+ SubstanceColorScheme determinateDisabledBorderScheme = schemes
+ .get("Nebula Determinate Disabled Border");
+ defaultSchemeBundle.registerColorScheme(determinateDisabledScheme,
+ determinateDisabledState, indeterminateDisabledState);
+ defaultSchemeBundle.registerColorScheme(
+ determinateDisabledBorderScheme,
+ ColorSchemeAssociationKind.BORDER, determinateDisabledState,
+ indeterminateDisabledState);
+
+ registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerAsDecorationArea(activeScheme.saturate(-0.5),
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER, DecorationAreaType.FOOTER,
+ DecorationAreaType.GENERAL);
+
+ // add an overlay painter to paint a drop shadow along the top
+ // edge of toolbars
+ this.addOverlayPainter(TopShadowOverlayPainter.getInstance(),
+ DecorationAreaType.TOOLBAR);
+
+ // add an overlay painter to paint separator lines along the bottom
+ // edges of title panes and menu bars
+ this.bottomLineOverlayPainter = new BottomLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ Color dark = scheme.getDarkColor();
+ return new Color(dark.getRed(), dark.getGreen(), dark
+ .getBlue(), 160);
+ }
+ });
+ this.addOverlayPainter(this.bottomLineOverlayPainter,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new SubduedFillPainter();
+
+ MarbleNoiseDecorationPainter decorationPainter = new MarbleNoiseDecorationPainter();
+ decorationPainter.setBaseDecorationPainter(new ArcDecorationPainter());
+ decorationPainter.setTextureAlpha(0.3f);
+ this.decorationPainter = decorationPainter;
+
+ this.highlightPainter = new ClassicHighlightPainter();
+ this.borderPainter = new FlatBorderPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/OfficeBlack2007Skin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/OfficeBlack2007Skin.java
new file mode 100755
index 0000000..5c5b684
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/OfficeBlack2007Skin.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.LightGrayColorScheme;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.FractionBasedDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.FractionBasedFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Office Black 2007</code> skin. This class is part of officially
+ * supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class OfficeBlack2007Skin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Office Black 2007";
+
+ /**
+ * Creates a new <code>Office Black 2007</code> skin.
+ */
+ public OfficeBlack2007Skin() {
+ SubstanceSkin.ColorSchemes colorSchemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/office2007.colorschemes");
+
+ SubstanceColorScheme activeScheme = colorSchemes
+ .get("Office Silver Active");
+ SubstanceColorScheme enabledScheme = colorSchemes
+ .get("Office Black Enabled");
+ SubstanceColorScheme disabledScheme = new LightGrayColorScheme().tint(
+ 0.05).named("Office Black Disabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, enabledScheme);
+ defaultSchemeBundle.registerColorScheme(enabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(activeScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED);
+
+ SubstanceColorScheme rolloverScheme = colorSchemes
+ .get("Office Silver Rollover");
+ SubstanceColorScheme rolloverSelectedScheme = colorSchemes
+ .get("Office Silver Rollover Selected");
+ SubstanceColorScheme selectedScheme = colorSchemes
+ .get("Office Silver Selected");
+ SubstanceColorScheme pressedScheme = colorSchemes
+ .get("Office Silver Pressed");
+ SubstanceColorScheme pressedSelectedScheme = colorSchemes
+ .get("Office Silver Pressed Selected");
+
+ // register state-specific color schemes on rollovers and selections
+ defaultSchemeBundle.registerColorScheme(rolloverScheme,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(rolloverSelectedScheme,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerColorScheme(selectedScheme,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerColorScheme(pressedScheme,
+ ComponentState.PRESSED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(pressedSelectedScheme,
+ ComponentState.PRESSED_SELECTED);
+
+ // register state-specific highlight color schemes on rollover and
+ // selections
+ defaultSchemeBundle.registerHighlightColorScheme(rolloverScheme, 0.8f,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(selectedScheme, 0.8f,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(
+ rolloverSelectedScheme, 0.8f, ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(selectedScheme, 0.8f,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ // borders and marks
+ SubstanceColorScheme borderEnabledScheme = colorSchemes
+ .get("Office Silver Border Enabled");
+ SubstanceColorScheme borderActiveScheme = colorSchemes
+ .get("Office Silver Border Active");
+ SubstanceColorScheme borderRolloverScheme = colorSchemes
+ .get("Office Border Rollover");
+ SubstanceColorScheme borderRolloverSelectedScheme = colorSchemes
+ .get("Office Border Rollover Selected");
+ SubstanceColorScheme borderSelectedScheme = colorSchemes
+ .get("Office Border Selected");
+ SubstanceColorScheme borderPressedScheme = colorSchemes
+ .get("Office Border Pressed");
+
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED);
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.DISABLED_SELECTED,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(borderActiveScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.DEFAULT);
+ defaultSchemeBundle.registerColorScheme(borderRolloverScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(borderRolloverSelectedScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ROLLOVER_SELECTED, ComponentState.ARMED,
+ ComponentState.ROLLOVER_ARMED);
+ defaultSchemeBundle.registerColorScheme(borderSelectedScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.SELECTED);
+ defaultSchemeBundle.registerColorScheme(borderPressedScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.PRESSED_SELECTED,
+ ComponentState.PRESSED_UNSELECTED);
+
+ SubstanceColorScheme markEnabledScheme = colorSchemes
+ .get("Office Black Mark Enabled");
+ defaultSchemeBundle.registerColorScheme(markEnabledScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState.ENABLED);
+ defaultSchemeBundle.registerColorScheme(markEnabledScheme,
+ ColorSchemeAssociationKind.MARK,
+ ComponentState.DISABLED_SELECTED,
+ ComponentState.DISABLED_UNSELECTED);
+
+ registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ // tabs and tab borders
+ SubstanceColorScheme tabSelectedScheme = colorSchemes
+ .get("Office Silver Tab Selected");
+ SubstanceColorScheme tabRolloverScheme = colorSchemes
+ .get("Office Silver Tab Rollover");
+ defaultSchemeBundle.registerColorScheme(tabSelectedScheme,
+ ColorSchemeAssociationKind.TAB, ComponentState.SELECTED,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.PRESSED_SELECTED,
+ ComponentState.PRESSED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(tabRolloverScheme,
+ ColorSchemeAssociationKind.TAB,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.TAB_BORDER, ComponentState.SELECTED,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(rolloverSelectedScheme,
+ ColorSchemeAssociationKind.TAB_BORDER,
+ ComponentState.ROLLOVER_SELECTED);
+
+ // separator
+ SubstanceColorScheme separatorScheme = colorSchemes
+ .get("Office Silver Separator");
+ defaultSchemeBundle.registerColorScheme(separatorScheme,
+ ColorSchemeAssociationKind.SEPARATOR);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ // color scheme bundle for title panes
+ SubstanceColorScheme activeHeaderScheme = colorSchemes
+ .get("Office Black Header Active");
+ SubstanceColorScheme enabledHeaderScheme = colorSchemes
+ .get("Office Black Header Enabled");
+ SubstanceColorSchemeBundle headerSchemeBundle = new SubstanceColorSchemeBundle(
+ activeHeaderScheme, enabledHeaderScheme, disabledScheme);
+
+ SubstanceColorScheme headerMarkEnabledScheme = colorSchemes
+ .get("Office Black Header Mark Enabled");
+ headerSchemeBundle.registerColorScheme(headerMarkEnabledScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState.ENABLED);
+ headerSchemeBundle.registerColorScheme(markEnabledScheme,
+ ColorSchemeAssociationKind.MARK,
+ ComponentState.DISABLED_SELECTED,
+ ComponentState.DISABLED_UNSELECTED);
+
+ headerSchemeBundle.registerColorScheme(enabledHeaderScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED,
+ ComponentState.DISABLED_SELECTED);
+ headerSchemeBundle.registerHighlightColorScheme(activeScheme, 0.6f,
+ ComponentState.ROLLOVER_UNSELECTED);
+ headerSchemeBundle.registerHighlightColorScheme(activeScheme, 0.8f,
+ ComponentState.SELECTED);
+ headerSchemeBundle.registerHighlightColorScheme(activeScheme, 0.95f,
+ ComponentState.ROLLOVER_SELECTED);
+ headerSchemeBundle.registerHighlightColorScheme(activeScheme, 0.8f,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ this.registerDecorationAreaSchemeBundle(headerSchemeBundle,
+ activeHeaderScheme, DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ // color scheme bundle for footer
+ SubstanceColorScheme enabledFooterScheme = colorSchemes
+ .get("Office Black Footer Enabled");
+ SubstanceColorSchemeBundle footerSchemeBundle = new SubstanceColorSchemeBundle(
+ activeHeaderScheme, enabledFooterScheme, disabledScheme);
+ SubstanceColorScheme borderFooterEnabledScheme = colorSchemes
+ .get("Office Black Footer Border Enabled");
+ footerSchemeBundle.registerColorScheme(borderFooterEnabledScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED,
+ ComponentState.DISABLED_SELECTED,
+ ComponentState.DISABLED_UNSELECTED);
+ footerSchemeBundle.registerColorScheme(borderFooterEnabledScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState
+ .getActiveStates());
+
+ // register state-specific color schemes on rollovers and selections
+ footerSchemeBundle.registerColorScheme(rolloverScheme,
+ ComponentState.ROLLOVER_UNSELECTED);
+ footerSchemeBundle.registerColorScheme(rolloverSelectedScheme,
+ ComponentState.ROLLOVER_SELECTED);
+ footerSchemeBundle.registerColorScheme(selectedScheme,
+ ComponentState.SELECTED);
+ footerSchemeBundle.registerColorScheme(pressedScheme,
+ ComponentState.PRESSED_UNSELECTED);
+ footerSchemeBundle.registerColorScheme(pressedSelectedScheme,
+ ComponentState.PRESSED_SELECTED);
+
+ SubstanceColorScheme footerSeparatorScheme = colorSchemes
+ .get("Office Black Footer Separator");
+ footerSchemeBundle.registerColorScheme(footerSeparatorScheme,
+ ColorSchemeAssociationKind.SEPARATOR);
+
+ this.registerDecorationAreaSchemeBundle(footerSchemeBundle,
+ activeHeaderScheme, DecorationAreaType.FOOTER);
+
+ this.watermarkScheme = colorSchemes.get("Office Black Watermark");
+ this.registerAsDecorationArea(this.watermarkScheme,
+ DecorationAreaType.GENERAL);
+
+ SubstanceColorScheme headerWatermarkScheme = colorSchemes
+ .get("Office Black Header Watermark");
+ this.registerAsDecorationArea(headerWatermarkScheme,
+ DecorationAreaType.HEADER, DecorationAreaType.TOOLBAR);
+
+ setSelectedTabFadeStart(0.6);
+ setSelectedTabFadeEnd(0.9);
+
+ this.addOverlayPainter(new BottomLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ Color fg = scheme.getUltraDarkColor();
+ return new Color(fg.getRed(), fg.getGreen(), fg
+ .getBlue(), 192);
+ }
+ }), DecorationAreaType.PRIMARY_TITLE_PANE);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.watermark = null;
+
+ this.fillPainter = new FractionBasedFillPainter("Office Black 2007",
+ new float[] { 0.0f, 0.49999f, 0.5f, 1.0f },
+ new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.MID,
+ ColorSchemeSingleColorQuery.LIGHT });
+
+ this.borderPainter = new ClassicBorderPainter();
+
+ this.decorationPainter = new FractionBasedDecorationPainter(
+ "Office Black 2007", new float[] { 0.0f, 0.2499999f, 0.25f,
+ 0.6f, 1.0f }, new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.MID,
+ ColorSchemeSingleColorQuery.DARK,
+ ColorSchemeSingleColorQuery.DARK,
+ ColorSchemeSingleColorQuery.MID },
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.FOOTER);
+ this.highlightPainter = new ClassicHighlightPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/OfficeBlue2007Skin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/OfficeBlue2007Skin.java
new file mode 100755
index 0000000..3fa1772
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/OfficeBlue2007Skin.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.CompositeBorderPainter;
+import org.pushingpixels.substance.api.painter.border.DelegateFractionBasedBorderPainter;
+import org.pushingpixels.substance.api.painter.border.FractionBasedBorderPainter;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.FractionBasedDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.FractionBasedFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Office Blue 2007</code> skin. This class is part of officially
+ * supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class OfficeBlue2007Skin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Office Blue 2007";
+
+ /**
+ * Creates a new <code>Office Blue 2007</code> skin.
+ */
+ public OfficeBlue2007Skin() {
+ SubstanceSkin.ColorSchemes colorSchemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/office2007.colorschemes");
+
+ SubstanceColorScheme activeScheme = colorSchemes
+ .get("Office Blue Active");
+ SubstanceColorScheme enabledScheme = colorSchemes
+ .get("Office Blue Enabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, enabledScheme);
+ defaultSchemeBundle.registerColorScheme(enabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(activeScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED);
+
+ SubstanceColorScheme rolloverScheme = colorSchemes
+ .get("Office Blue Rollover");
+ SubstanceColorScheme rolloverSelectedScheme = colorSchemes
+ .get("Office Blue Rollover Selected");
+ SubstanceColorScheme selectedScheme = colorSchemes
+ .get("Office Blue Selected");
+ SubstanceColorScheme pressedScheme = colorSchemes
+ .get("Office Blue Pressed");
+ SubstanceColorScheme pressedSelectedScheme = colorSchemes
+ .get("Office Blue Pressed Selected");
+
+ // register state-specific color schemes on rollovers and selections
+ defaultSchemeBundle.registerColorScheme(rolloverScheme,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(rolloverSelectedScheme,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerColorScheme(selectedScheme,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerColorScheme(pressedScheme,
+ ComponentState.PRESSED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(pressedSelectedScheme,
+ ComponentState.PRESSED_SELECTED);
+
+ // register state-specific highlight color schemes on rollover and
+ // selections
+ defaultSchemeBundle.registerHighlightColorScheme(rolloverScheme, 0.8f,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(selectedScheme, 0.8f,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(
+ rolloverSelectedScheme, 0.8f, ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(selectedScheme, 0.8f,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ // borders and marks
+ SubstanceColorScheme borderEnabledScheme = colorSchemes
+ .get("Office Blue Border Enabled");
+ SubstanceColorScheme borderActiveScheme = colorSchemes
+ .get("Office Blue Border Active");
+ SubstanceColorScheme borderRolloverScheme = colorSchemes
+ .get("Office Border Rollover");
+ SubstanceColorScheme borderRolloverSelectedScheme = colorSchemes
+ .get("Office Border Rollover Selected");
+ SubstanceColorScheme borderSelectedScheme = colorSchemes
+ .get("Office Border Selected");
+ SubstanceColorScheme borderPressedScheme = colorSchemes
+ .get("Office Border Pressed");
+
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED);
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.DISABLED_SELECTED,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(borderActiveScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.DEFAULT);
+ defaultSchemeBundle.registerColorScheme(borderRolloverScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(borderRolloverSelectedScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ROLLOVER_SELECTED, ComponentState.ARMED,
+ ComponentState.ROLLOVER_ARMED);
+ defaultSchemeBundle.registerColorScheme(borderSelectedScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.SELECTED);
+ defaultSchemeBundle.registerColorScheme(borderPressedScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.PRESSED_SELECTED,
+ ComponentState.PRESSED_UNSELECTED);
+
+ // tabs and tab borders
+ SubstanceColorScheme tabSelectedScheme = colorSchemes
+ .get("Office Blue Tab Selected");
+ SubstanceColorScheme tabRolloverScheme = colorSchemes
+ .get("Office Blue Tab Rollover");
+ defaultSchemeBundle.registerColorScheme(tabSelectedScheme,
+ ColorSchemeAssociationKind.TAB, ComponentState.SELECTED,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.PRESSED_SELECTED,
+ ComponentState.PRESSED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(tabRolloverScheme,
+ ColorSchemeAssociationKind.TAB,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.TAB_BORDER, ComponentState.SELECTED,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(rolloverSelectedScheme,
+ ColorSchemeAssociationKind.TAB_BORDER,
+ ComponentState.ROLLOVER_SELECTED);
+
+ // separator
+ SubstanceColorScheme separatorScheme = colorSchemes
+ .get("Office Blue Separator");
+ defaultSchemeBundle.registerColorScheme(separatorScheme,
+ ColorSchemeAssociationKind.SEPARATOR);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.watermarkScheme = colorSchemes.get("Office Blue Watermark");
+
+ SubstanceColorScheme generalWatermarkScheme = colorSchemes
+ .get("Office Blue Header Watermark");
+
+ this.registerAsDecorationArea(generalWatermarkScheme,
+ DecorationAreaType.FOOTER, DecorationAreaType.HEADER,
+ DecorationAreaType.TOOLBAR);
+
+ SubstanceColorScheme titleWatermarkScheme = colorSchemes
+ .get("Office Blue Title Watermark");
+
+ this.registerAsDecorationArea(titleWatermarkScheme,
+ DecorationAreaType.GENERAL,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE);
+
+ setSelectedTabFadeStart(0.7);
+ setSelectedTabFadeEnd(0.9);
+
+ this.addOverlayPainter(new BottomLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ Color fg = scheme.getForegroundColor();
+ return new Color(fg.getRed(), fg.getGreen(), fg
+ .getBlue(), 72);
+ }
+ }), DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.watermark = null;
+
+ this.fillPainter = new FractionBasedFillPainter("Office Blue 2007",
+ new float[] { 0.0f, 0.49999f, 0.5f, 1.0f },
+ new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.ULTRALIGHT,
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.ULTRADARK,
+ ColorSchemeSingleColorQuery.EXTRALIGHT });
+
+ FractionBasedBorderPainter outerBorderPainter = new FractionBasedBorderPainter(
+ "Office Blue 2007 Outer", new float[] { 0.0f, 0.5f, 1.0f },
+ new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.EXTRALIGHT,
+ ColorSchemeSingleColorQuery.DARK,
+ ColorSchemeSingleColorQuery.MID });
+ SubstanceBorderPainter innerBorderPainter = new DelegateFractionBasedBorderPainter(
+ "Office Blue 2007 Inner", outerBorderPainter, new int[] {
+ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF },
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.tint(0.8f);
+ }
+ });
+ this.borderPainter = new CompositeBorderPainter("Office Blue 2007",
+ outerBorderPainter, innerBorderPainter);
+
+ this.decorationPainter = new FractionBasedDecorationPainter(
+ "Office Blue 2007", new float[] { 0.0f, 0.1199999f, 0.12f,
+ 0.5f, 0.9f, 1.0f }, new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.ULTRADARK,
+ ColorSchemeSingleColorQuery.MID,
+ ColorSchemeSingleColorQuery.ULTRALIGHT,
+ ColorSchemeSingleColorQuery.LIGHT });
+
+ this.highlightPainter = new ClassicHighlightPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/OfficeSilver2007Skin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/OfficeSilver2007Skin.java
new file mode 100755
index 0000000..ab2358b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/OfficeSilver2007Skin.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.*;
+import org.pushingpixels.substance.api.painter.decoration.FractionBasedDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.FractionBasedFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Office Silver 2007</code> skin. This class is part of officially
+ * supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class OfficeSilver2007Skin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Office Silver 2007";
+
+ /**
+ * Creates a new <code>Office Silver 2007</code> skin.
+ */
+ public OfficeSilver2007Skin() {
+ SubstanceSkin.ColorSchemes colorSchemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/office2007.colorschemes");
+
+ SubstanceColorScheme activeScheme = colorSchemes
+ .get("Office Silver Active");
+ SubstanceColorScheme enabledScheme = colorSchemes
+ .get("Office Silver Enabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, enabledScheme);
+ defaultSchemeBundle.registerColorScheme(enabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(activeScheme, 0.5f,
+ ComponentState.DISABLED_SELECTED);
+
+ SubstanceColorScheme rolloverScheme = colorSchemes
+ .get("Office Silver Rollover");
+ SubstanceColorScheme rolloverSelectedScheme = colorSchemes
+ .get("Office Silver Rollover Selected");
+ SubstanceColorScheme selectedScheme = colorSchemes
+ .get("Office Silver Selected");
+ SubstanceColorScheme pressedScheme = colorSchemes
+ .get("Office Silver Pressed");
+ SubstanceColorScheme pressedSelectedScheme = colorSchemes
+ .get("Office Silver Pressed Selected");
+
+ // register state-specific color schemes on rollovers and selections
+ defaultSchemeBundle.registerColorScheme(rolloverScheme,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(rolloverSelectedScheme,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerColorScheme(selectedScheme,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerColorScheme(pressedScheme,
+ ComponentState.PRESSED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(pressedSelectedScheme,
+ ComponentState.PRESSED_SELECTED);
+
+ // register state-specific highlight color schemes on rollover and
+ // selections
+ defaultSchemeBundle.registerHighlightColorScheme(rolloverScheme, 0.8f,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(selectedScheme, 0.8f,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(
+ rolloverSelectedScheme, 0.8f, ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(selectedScheme, 0.8f,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ // borders and marks
+ SubstanceColorScheme borderEnabledScheme = colorSchemes
+ .get("Office Silver Border Enabled");
+ SubstanceColorScheme borderActiveScheme = colorSchemes
+ .get("Office Silver Border Active");
+ SubstanceColorScheme borderRolloverScheme = colorSchemes
+ .get("Office Border Rollover");
+ SubstanceColorScheme borderRolloverSelectedScheme = colorSchemes
+ .get("Office Border Rollover Selected");
+ SubstanceColorScheme borderSelectedScheme = colorSchemes
+ .get("Office Border Selected");
+ SubstanceColorScheme borderPressedScheme = colorSchemes
+ .get("Office Border Pressed");
+
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED);
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.DISABLED_SELECTED,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(borderActiveScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.DEFAULT);
+ defaultSchemeBundle.registerColorScheme(borderRolloverScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(borderRolloverSelectedScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.ROLLOVER_SELECTED, ComponentState.ARMED,
+ ComponentState.ROLLOVER_ARMED);
+ defaultSchemeBundle.registerColorScheme(borderSelectedScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.SELECTED);
+ defaultSchemeBundle.registerColorScheme(borderPressedScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.PRESSED_SELECTED,
+ ComponentState.PRESSED_UNSELECTED);
+
+ SubstanceColorScheme markEnabledScheme = colorSchemes
+ .get("Office Silver Mark Enabled");
+ defaultSchemeBundle.registerColorScheme(markEnabledScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState.ENABLED);
+ defaultSchemeBundle.registerColorScheme(markEnabledScheme,
+ ColorSchemeAssociationKind.MARK,
+ ComponentState.DISABLED_SELECTED,
+ ComponentState.DISABLED_UNSELECTED);
+
+ registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ // tabs and tab borders
+ SubstanceColorScheme tabSelectedScheme = colorSchemes
+ .get("Office Silver Tab Selected");
+ SubstanceColorScheme tabRolloverScheme = colorSchemes
+ .get("Office Silver Tab Rollover");
+ defaultSchemeBundle.registerColorScheme(tabSelectedScheme,
+ ColorSchemeAssociationKind.TAB, ComponentState.SELECTED,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.PRESSED_SELECTED,
+ ComponentState.PRESSED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(tabRolloverScheme,
+ ColorSchemeAssociationKind.TAB,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(borderEnabledScheme,
+ ColorSchemeAssociationKind.TAB_BORDER, ComponentState.SELECTED,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(rolloverSelectedScheme,
+ ColorSchemeAssociationKind.TAB_BORDER,
+ ComponentState.ROLLOVER_SELECTED);
+
+ // separator
+ SubstanceColorScheme separatorScheme = colorSchemes
+ .get("Office Silver Separator");
+ defaultSchemeBundle.registerColorScheme(separatorScheme,
+ ColorSchemeAssociationKind.SEPARATOR);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.watermarkScheme = colorSchemes.get("Office Silver Watermark");
+
+ SubstanceColorScheme generalWatermarkScheme = colorSchemes
+ .get("Office Silver Header Watermark");
+
+ this.registerAsDecorationArea(generalWatermarkScheme,
+ DecorationAreaType.FOOTER, DecorationAreaType.HEADER,
+ DecorationAreaType.TOOLBAR);
+
+ SubstanceColorScheme titleWatermarkScheme = colorSchemes
+ .get("Office Silver Title Watermark");
+
+ this.registerAsDecorationArea(titleWatermarkScheme,
+ DecorationAreaType.GENERAL,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE);
+
+ setSelectedTabFadeStart(0.6);
+ setSelectedTabFadeEnd(0.9);
+
+ this.addOverlayPainter(new BottomLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ Color fg = scheme.getForegroundColor();
+ return new Color(fg.getRed(), fg.getGreen(), fg
+ .getBlue(), 72);
+ }
+ }), DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.watermark = null;
+
+ this.fillPainter = new FractionBasedFillPainter("Office Silver 2007",
+ new float[] { 0.0f, 0.49999f, 0.5f, 1.0f },
+ new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.ULTRALIGHT,
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.ULTRADARK,
+ ColorSchemeSingleColorQuery.EXTRALIGHT });
+
+ FractionBasedBorderPainter outerBorderPainter = new FractionBasedBorderPainter(
+ "Office Silver 2007 Outer", new float[] { 0.0f, 0.5f, 1.0f },
+ new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.ULTRADARK,
+ ColorSchemeSingleColorQuery.MID });
+ SubstanceBorderPainter innerBorderPainter = new DelegateFractionBasedBorderPainter(
+ "Office Silver 2007 Inner", outerBorderPainter, new int[] {
+ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF },
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.tint(0.8f);
+ }
+ });
+ this.borderPainter = new CompositeBorderPainter("Office Silver 2007",
+ outerBorderPainter, innerBorderPainter);
+
+ this.decorationPainter = new FractionBasedDecorationPainter(
+ "Office Silver 2007", new float[] { 0.0f, 0.2499999f, 0.25f,
+ 0.3f, 0.7f, 1.0f }, new ColorSchemeSingleColorQuery[] {
+ ColorSchemeSingleColorQuery.ULTRALIGHT,
+ ColorSchemeSingleColorQuery.EXTRALIGHT,
+ ColorSchemeSingleColorQuery.DARK,
+ ColorSchemeSingleColorQuery.MID,
+ ColorSchemeSingleColorQuery.LIGHT,
+ ColorSchemeSingleColorQuery.ULTRALIGHT });
+
+ this.highlightPainter = new ClassicHighlightPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/RavenSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/RavenSkin.java
new file mode 100755
index 0000000..9e78fac
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/RavenSkin.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.DarkMetallicColorScheme;
+import org.pushingpixels.substance.api.colorscheme.EbonyColorScheme;
+import org.pushingpixels.substance.api.painter.border.GlassBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.GlassFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.api.watermark.SubstanceCrosshatchWatermark;
+
+/**
+ * <code>Raven</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class RavenSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Raven";
+
+ /**
+ * Creates a new <code>Raven</code> skin.
+ */
+ public RavenSkin() {
+ SubstanceSkin.ColorSchemes schemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/graphite.colorschemes");
+
+ SubstanceColorScheme activeScheme = new EbonyColorScheme();
+ SubstanceColorScheme enabledScheme = new DarkMetallicColorScheme();
+ SubstanceColorScheme disabledScheme = schemes.get("Raven Disabled");
+
+ SubstanceColorScheme selectedDisabledScheme = schemes
+ .get("Raven Selected Disabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+
+ // highlight fill scheme + custom alpha for rollover unselected state
+ SubstanceColorScheme highlightScheme = schemes
+ .get("Graphite Highlight");
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme, 0.6f,
+ ComponentState.ROLLOVER_UNSELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme, 0.8f,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme, 1.0f,
+ ComponentState.ROLLOVER_SELECTED);
+ defaultSchemeBundle.registerHighlightColorScheme(highlightScheme,
+ 0.75f, ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ // highlight border scheme
+ defaultSchemeBundle.registerColorScheme(new EbonyColorScheme(),
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER, ComponentState
+ .getActiveStates());
+
+ // text highlight scheme
+ SubstanceColorScheme textHighlightScheme = schemes
+ .get("Graphite Text Highlight");
+ defaultSchemeBundle.registerColorScheme(textHighlightScheme,
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ ComponentState.SELECTED, ComponentState.ROLLOVER_SELECTED);
+
+ defaultSchemeBundle.registerColorScheme(highlightScheme,
+ ComponentState.ARMED, ComponentState.ROLLOVER_ARMED);
+
+ SubstanceColorScheme highlightMarkScheme = schemes
+ .get("Raven Highlight Mark");
+ defaultSchemeBundle.registerColorScheme(highlightMarkScheme,
+ ColorSchemeAssociationKind.HIGHLIGHT_MARK, ComponentState
+ .getActiveStates());
+
+ defaultSchemeBundle.registerColorScheme(disabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+ defaultSchemeBundle.registerColorScheme(selectedDisabledScheme, 0.65f,
+ ComponentState.DISABLED_SELECTED);
+
+ SubstanceColorScheme tabHighlightScheme = schemes
+ .get("Graphite Tab Highlight");
+ defaultSchemeBundle.registerColorScheme(highlightScheme,
+ ComponentState.SELECTED);
+ defaultSchemeBundle.registerColorScheme(tabHighlightScheme,
+ ColorSchemeAssociationKind.TAB, ComponentState.SELECTED);
+ defaultSchemeBundle.registerColorScheme(activeScheme,
+ ColorSchemeAssociationKind.BORDER, ComponentState.SELECTED,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.ROLLOVER_UNSELECTED);
+
+ SubstanceColorScheme selectedMarkScheme = schemes
+ .get("Raven Selected Mark");
+ defaultSchemeBundle.registerColorScheme(selectedMarkScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState.SELECTED,
+ ComponentState.ROLLOVER_SELECTED,
+ ComponentState.DISABLED_SELECTED);
+ defaultSchemeBundle.registerColorScheme(selectedMarkScheme,
+ ColorSchemeAssociationKind.MARK,
+ ComponentState.ROLLOVER_UNSELECTED);
+
+ defaultSchemeBundle.registerColorScheme(activeScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.DISABLED_SELECTED);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerAsDecorationArea(enabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER, DecorationAreaType.FOOTER,
+ DecorationAreaType.GENERAL, DecorationAreaType.TOOLBAR);
+
+ this.registerAsDecorationArea(disabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE,
+ DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE);
+
+ this.watermarkScheme = activeScheme.shade(0.4);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.watermark = new SubstanceCrosshatchWatermark();
+ this.fillPainter = new GlassFillPainter();
+ this.decorationPainter = new ArcDecorationPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+ this.borderPainter = new GlassBorderPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SaharaSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SaharaSkin.java
new file mode 100755
index 0000000..6f3474c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SaharaSkin.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.DesertSandColorScheme;
+import org.pushingpixels.substance.api.colorscheme.LightGrayColorScheme;
+import org.pushingpixels.substance.api.colorscheme.MetallicColorScheme;
+import org.pushingpixels.substance.api.colorscheme.OliveColorScheme;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ClassicDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.ClassicFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Sahara</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SaharaSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Sahara";
+
+ /**
+ * Creates a new <code>Sahara</code> skin.
+ */
+ public SaharaSkin() {
+ SubstanceColorScheme activeScheme = new DesertSandColorScheme();
+ SubstanceColorScheme enabledScheme = new MetallicColorScheme();
+ SubstanceColorScheme disabledScheme = new LightGrayColorScheme();
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, disabledScheme);
+ defaultSchemeBundle.registerHighlightColorScheme(new OliveColorScheme()
+ .tint(0.2).named("Sahara Highlight"));
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerAsDecorationArea(activeScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE);
+
+ this.registerAsDecorationArea(enabledScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE,
+ DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE);
+
+ SubstanceSkin.ColorSchemes kitchenSinkSchemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/kitchen-sink.colorschemes");
+ this.registerAsDecorationArea(kitchenSinkSchemes
+ .get("LightGray General Watermark"),
+ DecorationAreaType.GENERAL, DecorationAreaType.HEADER);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new ClassicFillPainter();
+ this.borderPainter = new ClassicBorderPainter();
+ this.decorationPainter = new ClassicDecorationPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SkinChangeListener.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SkinChangeListener.java
new file mode 100644
index 0000000..34ac304
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SkinChangeListener.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+
+/**
+ * The listener interface for receiving events on changing the skin in
+ * <b>Substance </b> look and feel. This class is part of officially supported
+ * API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SkinChangeListener {
+ /**
+ * Invoked when the current <b>Substance</b> skin is changed.
+ *
+ * @see SubstanceLookAndFeel#setSkin(SubstanceSkin)
+ * @see SubstanceLookAndFeel#setSkin(String)
+ */
+ public void skinChanged();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SkinInfo.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SkinInfo.java
new file mode 100644
index 0000000..49606bb
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SkinInfo.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.internal.utils.TraitInfoImpl;
+
+/**
+ * Information on a single skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SkinInfo extends TraitInfoImpl {
+ /**
+ * Simple constructor.
+ *
+ * @param skinDisplayName
+ * Display name of <code>this</code> skin.
+ * @param skinClassName
+ * Class name of <code>this</code> skin.
+ */
+ public SkinInfo(String skinDisplayName, String skinClassName) {
+ super(skinDisplayName, skinClassName);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof SkinInfo) {
+ return this.getDisplayName().equals(
+ ((SkinInfo) obj).getDisplayName());
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return this.getDisplayName().hashCode();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceAutumnLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceAutumnLookAndFeel.java
new file mode 100644
index 0000000..ffd2822
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceAutumnLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Autumn</code> skin from
+ * {@link AutumnSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.SubstanceAutumnLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceAutumnLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceAutumnLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public class SubstanceAutumnLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Autumn</code> look-and-feel.
+ */
+ public SubstanceAutumnLookAndFeel() {
+ super(new AutumnSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceBusinessBlackSteelLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceBusinessBlackSteelLookAndFeel.java
new file mode 100644
index 0000000..8527789
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceBusinessBlackSteelLookAndFeel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Business Black Steel</code> skin
+ * from {@link BusinessBlackSteelSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>-Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceBusinessBlackSteelLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceBusinessBlackSteelLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceBusinessBlackSteelLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.3
+ */
+public class SubstanceBusinessBlackSteelLookAndFeel extends
+ SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Business Black Steel</code> look-and-feel.
+ */
+ public SubstanceBusinessBlackSteelLookAndFeel() {
+ super(new BusinessBlackSteelSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceBusinessBlueSteelLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceBusinessBlueSteelLookAndFeel.java
new file mode 100644
index 0000000..ea02187
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceBusinessBlueSteelLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Business Blue Steel</code> skin
+ * from {@link BusinessBlueSteelSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>-Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceBusinessBlueSteelLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceBusinessBlueSteelLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceBusinessBlueSteelLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.3
+ */
+public class SubstanceBusinessBlueSteelLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Business Blue Steel</code> look-and-feel.
+ */
+ public SubstanceBusinessBlueSteelLookAndFeel() {
+ super(new BusinessBlueSteelSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceBusinessLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceBusinessLookAndFeel.java
new file mode 100644
index 0000000..073a5ff
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceBusinessLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Business</code> skin from
+ * {@link BusinessSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.SubstanceBusinessLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceBusinessLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceBusinessLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class SubstanceBusinessLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Business</code> look-and-feel.
+ */
+ public SubstanceBusinessLookAndFeel() {
+ super(new BusinessSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceCeruleanLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceCeruleanLookAndFeel.java
new file mode 100755
index 0000000..d8d6152
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceCeruleanLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Cerulean</code> skin from
+ * {@link org.pushingpixels.substance.api.skin.CeruleanSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceCeruleanLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceCeruleanLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceCeruleanLookAndFeel());</li>
+ * </ul>
+ *
+ * @since version 7.0
+ */
+public class SubstanceCeruleanLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Cerulean</code> look-and-feel.
+ */
+ public SubstanceCeruleanLookAndFeel() {
+ super(new CeruleanSkin());
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceChallengerDeepLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceChallengerDeepLookAndFeel.java
new file mode 100644
index 0000000..b2ede4d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceChallengerDeepLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Challenger Deep</code> skin from
+ * {@link ChallengerDeepSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>-Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceChallengerDeepLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceChallengerDeepLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceChallengerDeepLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.2
+ */
+public class SubstanceChallengerDeepLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Challenger Deep</code> look-and-feel.
+ */
+ public SubstanceChallengerDeepLookAndFeel() {
+ super(new ChallengerDeepSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceCremeCoffeeLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceCremeCoffeeLookAndFeel.java
new file mode 100644
index 0000000..9860794
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceCremeCoffeeLookAndFeel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Creme Coffee</code> skin from
+ * {@link CremeCoffeeSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceCremeCoffeeLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceCremeCoffeeLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceCremeCoffeeLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class SubstanceCremeCoffeeLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Creme Coffee</code> look-and-feel.
+ */
+ public SubstanceCremeCoffeeLookAndFeel() {
+ super(new CremeCoffeeSkin());
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceCremeLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceCremeLookAndFeel.java
new file mode 100644
index 0000000..80678a7
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceCremeLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Creme</code> skin from
+ * {@link CremeSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>-Dswing.defaultlaf=org.pushingpixels.substance.api.skin.SubstanceCremeLookAndFeel
+ * </li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceCremeLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceCremeLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class SubstanceCremeLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Creme</code> look-and-feel.
+ */
+ public SubstanceCremeLookAndFeel() {
+ super(new CremeSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceDustCoffeeLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceDustCoffeeLookAndFeel.java
new file mode 100644
index 0000000..d9b6a64
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceDustCoffeeLookAndFeel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Dust Coffee</code> skin from
+ * {@link DustSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceDustCoffeeLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceDustCoffeeLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceDustCoffeeLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.2
+ */
+public class SubstanceDustCoffeeLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Dust Coffee</code> look-and-feel.
+ */
+ public SubstanceDustCoffeeLookAndFeel() {
+ super(new DustCoffeeSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceDustLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceDustLookAndFeel.java
new file mode 100644
index 0000000..1aae751
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceDustLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Dust</code> skin from
+ * {@link DustSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.SubstanceDustLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceDustLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceDustLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.2
+ */
+public class SubstanceDustLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Dust</code> look-and-feel.
+ */
+ public SubstanceDustLookAndFeel() {
+ super(new DustSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceEmeraldDuskLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceEmeraldDuskLookAndFeel.java
new file mode 100644
index 0000000..444cb26
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceEmeraldDuskLookAndFeel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Emerald Dusk</code> skin from
+ * {@link EmeraldDuskSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceEmeraldDuskLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceEmeraldDuskLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceEmeraldDuskLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.2
+ */
+public class SubstanceEmeraldDuskLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Emerald Dusk</code> look-and-feel.
+ */
+ public SubstanceEmeraldDuskLookAndFeel() {
+ super(new EmeraldDuskSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceGeminiLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceGeminiLookAndFeel.java
new file mode 100644
index 0000000..fc0a267
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceGeminiLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Gemini</code> skin from
+ * {@link GeminiSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.SubstanceGeminiLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceGeminiLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceGeminiLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.3
+ */
+public class SubstanceGeminiLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Gemini</code> look-and-feel.
+ */
+ public SubstanceGeminiLookAndFeel() {
+ super(new GeminiSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceGraphiteAquaLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceGraphiteAquaLookAndFeel.java
new file mode 100644
index 0000000..b5d88da
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceGraphiteAquaLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Raven Graphite</code> skin from
+ * {@link GraphiteAquaSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>-Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceGraphiteAquaLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceGraphiteAquaLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceGraphiteAquaLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.3
+ */
+public class SubstanceGraphiteAquaLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Raven Graphite Aqua</code> look-and-feel.
+ */
+ public SubstanceGraphiteAquaLookAndFeel() {
+ super(new GraphiteAquaSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceGraphiteGlassLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceGraphiteGlassLookAndFeel.java
new file mode 100644
index 0000000..90683f6
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceGraphiteGlassLookAndFeel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Raven Graphite Glass</code> skin
+ * from {@link GraphiteGlassSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>-Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceRavenGraphiteGlassLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceRavenGraphiteGlassLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceRavenGraphiteGlassLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.3
+ */
+public class SubstanceGraphiteGlassLookAndFeel extends
+ SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Raven Graphite Glass</code> look-and-feel.
+ */
+ public SubstanceGraphiteGlassLookAndFeel() {
+ super(new GraphiteGlassSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceGraphiteLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceGraphiteLookAndFeel.java
new file mode 100644
index 0000000..7337b09
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceGraphiteLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Graphite</code> skin from
+ * {@link GraphiteSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>-Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceRavenGraphiteLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceRavenGraphiteLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceRavenGraphiteLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.3
+ */
+public class SubstanceGraphiteLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Graphite</code> look-and-feel.
+ */
+ public SubstanceGraphiteLookAndFeel() {
+ super(new GraphiteSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceMagellanLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceMagellanLookAndFeel.java
new file mode 100644
index 0000000..819fd42
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceMagellanLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Magellan</code> skin from
+ * {@link MagellanSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.SubstanceMagellanLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceMagellanLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceMagellanLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.3
+ */
+public class SubstanceMagellanLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Magellan</code> look-and-feel.
+ */
+ public SubstanceMagellanLookAndFeel() {
+ super(new MagellanSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceMarinerLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceMarinerLookAndFeel.java
new file mode 100644
index 0000000..3e8971e
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceMarinerLookAndFeel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Mariner</code> skin from
+ * {@link MarinerSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceMarinerLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceMarinerLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceMarinerLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 6.1
+ */
+public class SubstanceMarinerLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Mariner</code> look-and-feel.
+ */
+ public SubstanceMarinerLookAndFeel() {
+ super(new MarinerSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceMistAquaLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceMistAquaLookAndFeel.java
new file mode 100644
index 0000000..bb93062
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceMistAquaLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Mist Aqua</code> skin from
+ * {@link MistAquaSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.SubstanceMistAquaLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceMistAquaLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceMistAquaLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public class SubstanceMistAquaLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Mist Aqua</code> look-and-feel.
+ */
+ public SubstanceMistAquaLookAndFeel() {
+ super(new MistAquaSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceMistSilverLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceMistSilverLookAndFeel.java
new file mode 100644
index 0000000..6a26343
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceMistSilverLookAndFeel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Mist Silver</code> skin from
+ * {@link MistSilverSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceMistSilverLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceMistSilverLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceMistSilverLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public class SubstanceMistSilverLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Mist Silver</code> look-and-feel.
+ */
+ public SubstanceMistSilverLookAndFeel() {
+ super(new MistSilverSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceModerateLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceModerateLookAndFeel.java
new file mode 100644
index 0000000..8cdac00
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceModerateLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Moderate</code> skin from
+ * {@link ModerateSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.SubstanceModerateLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceModerateLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceModerateLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class SubstanceModerateLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Moderate</code> look-and-feel.
+ */
+ public SubstanceModerateLookAndFeel() {
+ super(new ModerateSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceNebulaBrickWallLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceNebulaBrickWallLookAndFeel.java
new file mode 100644
index 0000000..57f4400
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceNebulaBrickWallLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Nebula Brick Wall</code> skin
+ * from {@link NebulaBrickWallSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>-Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceNebulaBrickWallLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceNebulaBrickWallLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceNebulaBrickWallLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public class SubstanceNebulaBrickWallLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Nebula Brick Wall</code> look-and-feel.
+ */
+ public SubstanceNebulaBrickWallLookAndFeel() {
+ super(new NebulaBrickWallSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceNebulaLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceNebulaLookAndFeel.java
new file mode 100644
index 0000000..f538256
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceNebulaLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Nebula</code> skin from
+ * {@link NebulaSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.SubstanceNebulaLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceNebulaLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceNebulaLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 4.0
+ */
+public class SubstanceNebulaLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Nebula</code> look-and-feel.
+ */
+ public SubstanceNebulaLookAndFeel() {
+ super(new NebulaSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceOfficeBlack2007LookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceOfficeBlack2007LookAndFeel.java
new file mode 100644
index 0000000..8c85995
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceOfficeBlack2007LookAndFeel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Office Black 2007</code> skin
+ * from {@link OfficeBlack2007Skin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>-Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceOfficeBlack2007LookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceOfficeBlack2007LookAndFeel" );
+ * </li>
+ * <li>UIManager.setLookAndFeel(new SubstanceOfficeBlack2007LookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 6.1
+ */
+public class SubstanceOfficeBlack2007LookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Office Black 2007</code> look-and-feel.
+ */
+ public SubstanceOfficeBlack2007LookAndFeel() {
+ super(new OfficeBlack2007Skin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceOfficeBlue2007LookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceOfficeBlue2007LookAndFeel.java
new file mode 100644
index 0000000..df9fb89
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceOfficeBlue2007LookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Office Blue 2007</code> skin
+ * from {@link OfficeBlue2007Skin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>-Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceOfficeBlue2007LookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceOfficeBlue2007LookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceOfficeBlue2007LookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class SubstanceOfficeBlue2007LookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Office Blue 2007</code> look-and-feel.
+ */
+ public SubstanceOfficeBlue2007LookAndFeel() {
+ super(new OfficeBlue2007Skin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceOfficeSilver2007LookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceOfficeSilver2007LookAndFeel.java
new file mode 100644
index 0000000..30526d5
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceOfficeSilver2007LookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Office Silver 2007</code> skin
+ * from {@link OfficeSilver2007Skin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>-Dswing.defaultlaf=org.pushingpixels.substance.api.skin.
+ * SubstanceOfficeSilver2007LookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceOfficeSilver2007LookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceOfficeSilver2007LookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class SubstanceOfficeSilver2007LookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Office Silver 2007</code> look-and-feel.
+ */
+ public SubstanceOfficeSilver2007LookAndFeel() {
+ super(new OfficeSilver2007Skin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceRavenLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceRavenLookAndFeel.java
new file mode 100644
index 0000000..b892fca
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceRavenLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Raven</code> skin from
+ * {@link RavenSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>-Dswing.defaultlaf=org.pushingpixels.substance.api.skin.SubstanceRavenLookAndFeel
+ * </li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceRavenLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceRavenLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class SubstanceRavenLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Raven</code> look-and-feel.
+ */
+ public SubstanceRavenLookAndFeel() {
+ super(new RavenSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceSaharaLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceSaharaLookAndFeel.java
new file mode 100644
index 0000000..8c9a4bb
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceSaharaLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Sahara</code> skin from
+ * {@link SaharaSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.SubstanceSaharaLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceSaharaLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceSaharaLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 3.1
+ */
+public class SubstanceSaharaLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Sahara</code> look-and-feel.
+ */
+ public SubstanceSaharaLookAndFeel() {
+ super(new SaharaSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceTwilightLookAndFeel.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceTwilightLookAndFeel.java
new file mode 100644
index 0000000..0c06058
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/SubstanceTwilightLookAndFeel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+/**
+ * Standalone look-and-feel that uses the <code>Twilight</code> skin from
+ * {@link TwilightSkin}. You can set this look-and-feel by:
+ * <ul>
+ * <li>
+ * -Dswing.defaultlaf=org.pushingpixels.substance.api.skin.SubstanceTwilightLookAndFeel</li>
+ * <li>UIManager.setLookAndFeel(
+ * "org.pushingpixels.substance.api.skin.SubstanceTwilightLookAndFeel" );</li>
+ * <li>UIManager.setLookAndFeel(new SubstanceTwilightLookAndFeel());</li>
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.2
+ */
+public class SubstanceTwilightLookAndFeel extends SubstanceLookAndFeel {
+ /**
+ * Creates a new <code>Twilight</code> look-and-feel.
+ */
+ public SubstanceTwilightLookAndFeel() {
+ super(new TwilightSkin());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/skin/TwilightSkin.java b/substance/src/main/java/org/pushingpixels/substance/api/skin/TwilightSkin.java
new file mode 100755
index 0000000..f8f90c3
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/skin/TwilightSkin.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.skin;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.*;
+import org.pushingpixels.substance.api.painter.decoration.MatteDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.StandardFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.*;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+
+/**
+ * <code>Twilight</code> skin. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @since version 5.2
+ */
+public class TwilightSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static final String NAME = "Twilight";
+
+ /**
+ * Overlay painter to paint a dark line along the bottom edge of the
+ * toolbars.
+ */
+ private BottomLineOverlayPainter toolbarBottomLineOverlayPainter;
+
+ /**
+ * Overlay painter to paint a light line along the top edge of the toolbars.
+ */
+ private TopLineOverlayPainter toolbarTopLineOverlayPainter;
+
+ /**
+ * Overlay painter to paint a bezel line along the top edge of the footer.
+ */
+ private TopBezelOverlayPainter footerTopBezelOverlayPainter;
+
+ /**
+ * Creates a new <code>Twilight</code> skin.
+ */
+ public TwilightSkin() {
+ SubstanceSkin.ColorSchemes schemes = SubstanceSkin
+ .getColorSchemes("org/pushingpixels/substance/api/skin/twilight.colorschemes");
+ SubstanceColorScheme activeScheme = schemes.get("Twilight Active");
+ SubstanceColorScheme enabledScheme = schemes.get("Twilight Enabled");
+
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, enabledScheme);
+ defaultSchemeBundle.registerColorScheme(enabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+
+ // borders
+ SubstanceColorScheme borderDisabledSelectedScheme = schemes
+ .get("Twilight Selected Disabled Border");
+ SubstanceColorScheme borderScheme = schemes.get("Twilight Border");
+ defaultSchemeBundle.registerColorScheme(borderDisabledSelectedScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.DISABLED_SELECTED);
+ defaultSchemeBundle.registerColorScheme(borderScheme,
+ ColorSchemeAssociationKind.BORDER);
+
+ // marks
+ SubstanceColorScheme markActiveScheme = schemes
+ .get("Twilight Mark Active");
+ defaultSchemeBundle.registerColorScheme(markActiveScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState
+ .getActiveStates());
+
+ // separators
+ SubstanceColorScheme separatorScheme = schemes
+ .get("Twilight Separator");
+ defaultSchemeBundle.registerColorScheme(separatorScheme,
+ ColorSchemeAssociationKind.SEPARATOR);
+
+ SubstanceColorScheme watermarkScheme = schemes
+ .get("Twilight Watermark");
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ watermarkScheme, DecorationAreaType.NONE);
+
+ SubstanceColorSchemeBundle decorationsSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, enabledScheme);
+ decorationsSchemeBundle.registerColorScheme(enabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+
+ // borders
+ decorationsSchemeBundle.registerColorScheme(
+ borderDisabledSelectedScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.DISABLED_SELECTED);
+ decorationsSchemeBundle.registerColorScheme(borderScheme,
+ ColorSchemeAssociationKind.BORDER);
+
+ // marks
+ decorationsSchemeBundle.registerColorScheme(markActiveScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState
+ .getActiveStates());
+
+ // separators
+ SubstanceColorScheme separatorDecorationsScheme = schemes
+ .get("Twilight Decorations Separator");
+ decorationsSchemeBundle.registerColorScheme(separatorDecorationsScheme,
+ ColorSchemeAssociationKind.SEPARATOR);
+
+ SubstanceColorScheme decorationsWatermarkScheme = schemes
+ .get("Twilight Decorations Watermark");
+
+ this.registerDecorationAreaSchemeBundle(decorationsSchemeBundle,
+ decorationsWatermarkScheme, DecorationAreaType.TOOLBAR,
+ DecorationAreaType.GENERAL, DecorationAreaType.FOOTER);
+
+ SubstanceColorSchemeBundle headerSchemeBundle = new SubstanceColorSchemeBundle(
+ activeScheme, enabledScheme, enabledScheme);
+ headerSchemeBundle.registerColorScheme(enabledScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED);
+
+ // borders
+ SubstanceColorScheme headerBorderScheme = schemes
+ .get("Twilight Header Border");
+ headerSchemeBundle.registerColorScheme(borderDisabledSelectedScheme,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.DISABLED_SELECTED);
+ headerSchemeBundle.registerColorScheme(headerBorderScheme,
+ ColorSchemeAssociationKind.BORDER);
+ // marks
+ headerSchemeBundle.registerColorScheme(markActiveScheme,
+ ColorSchemeAssociationKind.MARK, ComponentState
+ .getActiveStates());
+
+ headerSchemeBundle.registerHighlightColorScheme(activeScheme, 0.7f,
+ ComponentState.ROLLOVER_UNSELECTED,
+ ComponentState.ROLLOVER_ARMED, ComponentState.ARMED);
+ headerSchemeBundle.registerHighlightColorScheme(activeScheme, 0.8f,
+ ComponentState.SELECTED);
+ headerSchemeBundle.registerHighlightColorScheme(activeScheme, 1.0f,
+ ComponentState.ROLLOVER_SELECTED);
+
+ SubstanceColorScheme headerWatermarkScheme = schemes
+ .get("Twilight Header Watermark");
+
+ this.registerDecorationAreaSchemeBundle(headerSchemeBundle,
+ headerWatermarkScheme, DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ setSelectedTabFadeStart(0.2);
+ setSelectedTabFadeEnd(0.9);
+
+ // Add overlay painters to paint drop shadows along the bottom
+ // edges of toolbars and footers
+ this.addOverlayPainter(BottomShadowOverlayPainter.getInstance(),
+ DecorationAreaType.TOOLBAR);
+ this.addOverlayPainter(BottomShadowOverlayPainter.getInstance(),
+ DecorationAreaType.FOOTER);
+
+ // add an overlay painter to paint a dark line along the bottom
+ // edge of toolbars
+ this.toolbarBottomLineOverlayPainter = new BottomLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getUltraDarkColor().darker();
+ }
+ });
+ this.addOverlayPainter(this.toolbarBottomLineOverlayPainter,
+ DecorationAreaType.TOOLBAR);
+
+ // add an overlay painter to paint a dark line along the bottom
+ // edge of toolbars
+ this.toolbarTopLineOverlayPainter = new TopLineOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ Color fg = scheme.getForegroundColor();
+ return new Color(fg.getRed(), fg.getGreen(), fg
+ .getBlue(), 32);
+ }
+ });
+ this.addOverlayPainter(this.toolbarTopLineOverlayPainter,
+ DecorationAreaType.TOOLBAR);
+
+ // add an overlay painter to paint a bezel line along the top
+ // edge of footer
+ this.footerTopBezelOverlayPainter = new TopBezelOverlayPainter(
+ new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ return scheme.getUltraDarkColor().darker();
+ }
+ }, new ColorSchemeSingleColorQuery() {
+ @Override
+ public Color query(SubstanceColorScheme scheme) {
+ Color fg = scheme.getForegroundColor();
+ return new Color(fg.getRed(), fg.getGreen(), fg
+ .getBlue(), 32);
+ }
+ });
+ this.addOverlayPainter(this.footerTopBezelOverlayPainter,
+ DecorationAreaType.FOOTER);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.watermark = null;
+ this.fillPainter = new StandardFillPainter();
+ this.decorationPainter = new MatteDecorationPainter();
+ this.highlightPainter = new ClassicHighlightPainter();
+ this.borderPainter = new CompositeBorderPainter("Twilight",
+ new ClassicBorderPainter(), new DelegateBorderPainter(
+ "Twilight Inner", new ClassicBorderPainter(),
+ 0x40FFFFFF, 0x20FFFFFF, 0x00FFFFFF,
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return scheme.tint(0.3);
+ }
+ }));
+ }
+
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/tabbed/BaseTabCloseListener.java b/substance/src/main/java/org/pushingpixels/substance/api/tabbed/BaseTabCloseListener.java
new file mode 100644
index 0000000..16c1f2c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/tabbed/BaseTabCloseListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.tabbed;
+
+/**
+ * Base interface for the tab close listeners. This class is part of officially
+ * supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface BaseTabCloseListener {
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/tabbed/MultipleTabCloseListener.java b/substance/src/main/java/org/pushingpixels/substance/api/tabbed/MultipleTabCloseListener.java
new file mode 100644
index 0000000..d44a8db
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/tabbed/MultipleTabCloseListener.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.tabbed;
+
+import java.awt.Component;
+import java.util.Set;
+
+import javax.swing.JTabbedPane;
+
+/**
+ * Listener on multiple tab closing. This class is part of officially supported
+ * API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface MultipleTabCloseListener extends BaseTabCloseListener {
+ /**
+ * Called when tabs are about to be closed.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabComponents
+ * Tab components to be closed.
+ */
+ public void tabsClosing(JTabbedPane tabbedPane, Set<Component> tabComponents);
+
+ /**
+ * Called when tabs are closed.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabComponents
+ * Tab components closed.
+ */
+ public void tabsClosed(JTabbedPane tabbedPane, Set<Component> tabComponents);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/tabbed/TabCloseCallback.java b/substance/src/main/java/org/pushingpixels/substance/api/tabbed/TabCloseCallback.java
new file mode 100644
index 0000000..8b875e1
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/tabbed/TabCloseCallback.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.tabbed;
+
+import java.awt.event.MouseEvent;
+
+import javax.swing.JTabbedPane;
+
+import org.pushingpixels.substance.api.SubstanceConstants.TabCloseKind;
+
+/**
+ * Callback for registering app-specific behaviour on tab close buttons. This
+ * class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface TabCloseCallback {
+ /**
+ * Invoked when the tab area (not close button) is clicked.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Index of the tab under the click.
+ * @param mouseEvent
+ * Mouse event.
+ * @return Tab close kind.
+ */
+ public TabCloseKind onAreaClick(JTabbedPane tabbedPane, int tabIndex,
+ MouseEvent mouseEvent);
+
+ /**
+ * Invoked when the tab close button is clicked.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Index of the tab under the click.
+ * @param mouseEvent
+ * Mouse event.
+ * @return Tab close kind.
+ */
+ public TabCloseKind onCloseButtonClick(JTabbedPane tabbedPane,
+ int tabIndex, MouseEvent mouseEvent);
+
+ /**
+ * Returns the tooltip for the tab area (not close button).
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Index of the tab under the mouse.
+ * @return Tooltip for the tab area.
+ */
+ public String getAreaTooltip(JTabbedPane tabbedPane, int tabIndex);
+
+ /**
+ * Returns the tooltip for the tab close button.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Index of the tab under the mouse.
+ * @return Tooltip for the tab close button.
+ */
+ public String getCloseButtonTooltip(JTabbedPane tabbedPane, int tabIndex);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/tabbed/TabCloseListener.java b/substance/src/main/java/org/pushingpixels/substance/api/tabbed/TabCloseListener.java
new file mode 100644
index 0000000..8b329c1
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/tabbed/TabCloseListener.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.tabbed;
+
+import java.awt.Component;
+
+import javax.swing.JTabbedPane;
+
+/**
+ * Listener on tab closing. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface TabCloseListener extends BaseTabCloseListener {
+ /**
+ * Called when a tab is about to be closed.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabComponent
+ * Tab component to be closed.
+ */
+ public void tabClosing(JTabbedPane tabbedPane, Component tabComponent);
+
+ /**
+ * Called when a tab is closed.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabComponent
+ * Tab component closed.
+ */
+ public void tabClosed(JTabbedPane tabbedPane, Component tabComponent);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/tabbed/VetoableMultipleTabCloseListener.java b/substance/src/main/java/org/pushingpixels/substance/api/tabbed/VetoableMultipleTabCloseListener.java
new file mode 100644
index 0000000..e60bba1
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/tabbed/VetoableMultipleTabCloseListener.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.tabbed;
+
+import java.awt.Component;
+import java.util.Set;
+
+import javax.swing.JTabbedPane;
+
+/**
+ * Vetoable listener on tabs closing. This class is part of officially supported
+ * API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface VetoableMultipleTabCloseListener extends
+ MultipleTabCloseListener {
+ /**
+ * Called when tabs are about to be closed. Can veto the tabs closing.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabComponents
+ * Tab components to be closed.
+ * @return <code>true</code> if the corresponding tabs shouldn't be closed,
+ * <code>false</code> otherwise.
+ */
+ public boolean vetoTabsClosing(JTabbedPane tabbedPane,
+ Set<Component> tabComponents);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/tabbed/VetoableTabCloseListener.java b/substance/src/main/java/org/pushingpixels/substance/api/tabbed/VetoableTabCloseListener.java
new file mode 100644
index 0000000..7971867
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/tabbed/VetoableTabCloseListener.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.tabbed;
+
+import java.awt.Component;
+
+import javax.swing.JTabbedPane;
+
+/**
+ * Vetoable listener on tab closing. This class is part of officially supported
+ * API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface VetoableTabCloseListener extends TabCloseListener {
+ /**
+ * Called when a tab is about to be closed. Can veto the tab closing.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabComponent
+ * Tab component to be closed.
+ * @return <code>true</code> if the corresponding tab shouldn't be closed,
+ * <code>false</code> otherwise.
+ */
+ public boolean vetoTabClosing(JTabbedPane tabbedPane, Component tabComponent);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/trait/SubstanceTrait.java b/substance/src/main/java/org/pushingpixels/substance/api/trait/SubstanceTrait.java
new file mode 100644
index 0000000..d4fb848
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/trait/SubstanceTrait.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.trait;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
+
+/**
+ * Base interface for Substance traits, like {@link SubstanceColorScheme},
+ * {@link SubstanceWatermark} etc.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SubstanceTrait {
+ /**
+ * Returns the display name of <code>this</code> trait. This method is part
+ * of officially supported API.
+ *
+ * @return The display name of <code>this</code> trait.
+ */
+ public String getDisplayName();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/trait/SubstanceTraitInfo.java b/substance/src/main/java/org/pushingpixels/substance/api/trait/SubstanceTraitInfo.java
new file mode 100644
index 0000000..5ff2369
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/trait/SubstanceTraitInfo.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.trait;
+
+/**
+ * Base interface for Substance traits info, like {@link org.pushingpixels.substance.api.skin.SkinInfo}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SubstanceTraitInfo {
+ /**
+ * Returns the display name of the associated trait. This method is part of
+ * officially supported API.
+ *
+ * @return The display name of the associated trait.
+ */
+ public String getDisplayName();
+
+ /**
+ * Returns the class name of the associated trait.
+ *
+ * @return The class name of the associated trait. This method is part of
+ * officially supported API.
+ */
+ public String getClassName();
+
+ /**
+ * Returns indication whether the associated trait is default.
+ *
+ * @return <code>true</code> if the associated trait is default,
+ * <code>false</code> otherwise.
+ */
+ public boolean isDefault();
+
+ /**
+ * Sets indication whether the associated trait is default.
+ *
+ * @param isDefault
+ * New indication whether the associated trait is default.
+ */
+ public void setDefault(boolean isDefault);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceCrosshatchWatermark.java b/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceCrosshatchWatermark.java
new file mode 100644
index 0000000..614a841
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceCrosshatchWatermark.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.watermark;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Simple implementation of {@link SubstanceWatermark}, drawing cross hatches as
+ * watermark. This implementation is inspired by Office 12 background. This
+ * class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @author Chris Hall
+ */
+public class SubstanceCrosshatchWatermark implements SubstanceWatermark {
+ /**
+ * Watermark image (screen-sized).
+ */
+ private static Image watermarkImage = null;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.watermark.SubstanceWatermark#drawWatermarkImage(java
+ * .awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void drawWatermarkImage(Graphics graphics, Component c, int x,
+ int y, int width, int height) {
+ if (!c.isShowing())
+ return;
+ int dx = c.getLocationOnScreen().x;
+ int dy = c.getLocationOnScreen().y;
+ graphics.drawImage(SubstanceCrosshatchWatermark.watermarkImage, x, y, x
+ + width, y + height, x + dx, y + dy, x + dx + width, y + dy
+ + height, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.watermark.SubstanceWatermark#updateWatermarkImage
+ * (org.pushingpixels.substance.skin.SubstanceSkin)
+ */
+ @Override
+ public boolean updateWatermarkImage(SubstanceSkin skin) {
+ // fix by Chris for bug 67 - support for multiple screens
+ Rectangle virtualBounds = new Rectangle();
+ GraphicsEnvironment ge = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice[] gds = ge.getScreenDevices();
+ for (GraphicsDevice gd : gds) {
+ GraphicsConfiguration gc = gd.getDefaultConfiguration();
+ virtualBounds = virtualBounds.union(gc.getBounds());
+ }
+
+ int screenWidth = virtualBounds.width;
+ int screenHeight = virtualBounds.height;
+ SubstanceCrosshatchWatermark.watermarkImage = SubstanceCoreUtilities
+ .getBlankImage(screenWidth, screenHeight);
+
+ Graphics2D graphics = (Graphics2D) SubstanceCrosshatchWatermark.watermarkImage
+ .getGraphics().create();
+ boolean status = this.drawWatermarkImage(skin, graphics, 0, 0,
+ screenWidth, screenHeight, false);
+ graphics.dispose();
+ return status;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.watermark.SubstanceWatermark#previewWatermark
+ * (java.awt.Graphics, org.pushingpixels.substance.api.SubstanceSkin, int, int, int,
+ * int)
+ */
+ @Override
+ public void previewWatermark(Graphics g, SubstanceSkin skin, int x, int y,
+ int width, int height) {
+ this
+ .drawWatermarkImage(skin, (Graphics2D) g, x, y, width, height,
+ true);
+ }
+
+ /**
+ * Draws the specified portion of the watermark image.
+ *
+ * @param skin
+ * Skin to use for painting the watermark.
+ * @param graphics
+ * Graphic context.
+ * @param x
+ * the <i>x</i> coordinate of the watermark to be drawn.
+ * @param y
+ * The <i>y</i> coordinate of the watermark to be drawn.
+ * @param width
+ * The width of the watermark to be drawn.
+ * @param height
+ * The height of the watermark to be drawn.
+ * @param isPreview
+ * Indication whether the result is a preview image.
+ * @return Indication whether the draw succeeded.
+ */
+ private boolean drawWatermarkImage(SubstanceSkin skin, Graphics2D graphics,
+ int x, int y, int width, int height, boolean isPreview) {
+ Color stampColorDark = null;
+ Color stampColorAll = null;
+ Color stampColorLight = null;
+ SubstanceColorScheme scheme = skin.getWatermarkColorScheme();
+ if (isPreview) {
+ stampColorDark = scheme.isDark() ? Color.white : Color.black;
+ stampColorAll = Color.lightGray;
+ stampColorLight = scheme.isDark() ? Color.black : Color.white;
+ } else {
+ stampColorDark = scheme.getWatermarkDarkColor();
+ stampColorAll = scheme.getWatermarkStampColor();
+ stampColorLight = scheme.getWatermarkLightColor();
+ }
+
+ graphics.setColor(stampColorAll);
+ graphics.fillRect(0, 0, width, height);
+
+ BufferedImage tile = SubstanceCoreUtilities.getBlankImage(4, 4);
+ tile.setRGB(0, 0, stampColorDark.getRGB());
+ tile.setRGB(2, 2, stampColorDark.getRGB());
+ tile.setRGB(0, 1, stampColorLight.getRGB());
+ tile.setRGB(2, 3, stampColorLight.getRGB());
+
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+ 0.4f));
+ for (int row = y; row < (y + height); row += 4) {
+ for (int col = x; col < (x + width); col += 4) {
+ g2d.drawImage(tile, col, row, null);
+ }
+ }
+ g2d.dispose();
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.trait.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return SubstanceCrosshatchWatermark.getName();
+ }
+
+ /**
+ * Returns the name of all watermarks of <code>this</code> class.
+ *
+ * @return The name of all watermarks of <code>this</code> class.
+ */
+ public static String getName() {
+ return "Crosshatch";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.watermark.SubstanceWatermark#dispose()
+ */
+ @Override
+ public void dispose() {
+ watermarkImage = null;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceImageWatermark.java b/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceImageWatermark.java
new file mode 100644
index 0000000..06a158d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceImageWatermark.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.watermark;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.imageio.IIOException;
+import javax.imageio.ImageIO;
+import javax.swing.JComponent;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.SubstanceConstants.ImageWatermarkKind;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Implementation of {@link SubstanceWatermark}, drawing specified image as
+ * watermark. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @author Chris Hall
+ * @author Mark Haag
+ */
+public class SubstanceImageWatermark implements SubstanceWatermark {
+ /**
+ * Watermark image (screen-sized).
+ */
+ protected Image watermarkImage = null;
+
+ /**
+ * Watermark image kind.
+ */
+ private ImageWatermarkKind kind = ImageWatermarkKind.SCREEN_CENTER_SCALE;
+
+ /**
+ * Watermark image opacity.
+ */
+ private float opacity = 0.2f;
+
+ /**
+ * The original image (as read from the disk / HTTP connection).
+ */
+ protected BufferedImage origImage;
+
+ /**
+ * The original image location.
+ */
+ protected String origImageLocation;
+
+ /**
+ * Creates an instance with specified image.
+ *
+ * @param imageLocation
+ * Image location. Can point to a local file or HTTP URL (needs
+ * to start with <code>http</code> in the later case).
+ */
+ public SubstanceImageWatermark(String imageLocation) {
+ if (imageLocation != null) {
+ try {
+ if (imageLocation.startsWith("http")) {
+ URL url = new URL(imageLocation);
+ BufferedImage tempImage = ImageIO.read(url);
+ this.origImage = SubstanceCoreUtilities
+ .createCompatibleImage(tempImage);
+ } else {
+ try {
+ this.origImage = SubstanceCoreUtilities
+ .createCompatibleImage(ImageIO.read(new File(
+ imageLocation)));
+ } catch (IIOException iioe) {
+ this.origImage = SubstanceCoreUtilities
+ .createCompatibleImage(ImageIO
+ .read(SubstanceImageWatermark.class
+ .getClassLoader().getResource(
+ imageLocation)));
+ }
+ }
+ } catch (Exception exc) {
+ // ignore - probably specified incorrect file
+ // or file is not image
+ exc.printStackTrace();
+ }
+ }
+ this.origImageLocation = imageLocation;
+ }
+
+ /**
+ * Creates an instance from the specified input stream.
+ *
+ * @param inputStream
+ * Input stream.
+ */
+ public SubstanceImageWatermark(InputStream inputStream) {
+ if (inputStream != null) {
+ try {
+ BufferedImage tempImage = ImageIO.read(inputStream);
+ this.origImage = SubstanceCoreUtilities
+ .createCompatibleImage(tempImage);
+ } catch (Exception exc) {
+ // ignore - probably specified incorrect input stream
+ // or stream doesn't contain an image
+ exc.printStackTrace();
+ }
+ }
+ this.origImageLocation = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.watermark.SubstanceWatermark#drawWatermarkImage(java
+ * .awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void drawWatermarkImage(Graphics graphics, Component c, int x,
+ int y, int width, int height) {
+ if (!c.isShowing())
+ return;
+ int dx = 0;
+ int dy = 0;
+
+ Component topParent = null;
+ switch (getKind()) {
+ case SCREEN_CENTER_SCALE:
+ case SCREEN_TILE:
+ dx = c.getLocationOnScreen().x;
+ dy = c.getLocationOnScreen().y;
+ break;
+ case APP_ANCHOR:
+ case APP_TILE:
+ if (c instanceof JComponent) {
+ topParent = ((JComponent) c).getTopLevelAncestor();
+ } else {
+ Component comp = c;
+ while (comp.getParent() != null) {
+ comp = comp.getParent();
+ }
+ topParent = comp;
+ }
+ dx = c.getLocationOnScreen().x - topParent.getLocationOnScreen().x;
+ dy = c.getLocationOnScreen().y - topParent.getLocationOnScreen().y;
+ break;
+ case APP_CENTER:
+ if (c instanceof JComponent) {
+ topParent = ((JComponent) c).getTopLevelAncestor();
+ } else {
+ Component comp = c;
+ while (comp.getParent() != null) {
+ comp = comp.getParent();
+ }
+ topParent = comp;
+ }
+ dx = c.getLocationOnScreen().x - topParent.getLocationOnScreen().x;
+ dy = c.getLocationOnScreen().y - topParent.getLocationOnScreen().y;
+ dx -= (topParent.getWidth() / 2 - this.origImage.getWidth() / 2);
+ dy -= (topParent.getHeight() / 2 - this.origImage.getHeight() / 2);
+ }
+
+ graphics.drawImage(watermarkImage, x, y, x + width, y + height, x + dx,
+ y + dy, x + dx + width, y + dy + height, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.watermark.SubstanceWatermark#previewWatermark
+ * (java.awt.Graphics, org.pushingpixels.substance.api.SubstanceSkin, int, int, int,
+ * int)
+ */
+ @Override
+ public void previewWatermark(Graphics g, SubstanceSkin skin, int x, int y,
+ int width, int height) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.watermark.SubstanceWatermark#updateWatermarkImage
+ * (org.pushingpixels.substance.skin.SubstanceSkin)
+ */
+ @Override
+ public boolean updateWatermarkImage(SubstanceSkin skin) {
+ if (this.origImage == null) {
+ return false;
+ }
+
+ // fix by Chris for bug 67 - support for multiple screens
+ Rectangle virtualBounds = new Rectangle();
+ GraphicsEnvironment ge = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice[] gds = ge.getScreenDevices();
+ for (GraphicsDevice gd : gds) {
+ GraphicsConfiguration gc = gd.getDefaultConfiguration();
+ virtualBounds = virtualBounds.union(gc.getBounds());
+ }
+
+ int screenWidth = virtualBounds.width;
+ int screenHeight = virtualBounds.height;
+
+ int origImageWidth = this.origImage.getWidth();
+ int origImageHeight = this.origImage.getHeight();
+
+ if (getKind() == ImageWatermarkKind.SCREEN_CENTER_SCALE) {
+ watermarkImage = SubstanceCoreUtilities.getBlankImage(screenWidth,
+ screenHeight);
+
+ Graphics2D graphics = (Graphics2D) watermarkImage.getGraphics()
+ .create();
+
+ Composite comp = AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, opacity);
+ graphics.setComposite(comp);
+
+ // decide if need to scale or center
+ boolean isWidthFits = (origImageWidth <= screenWidth);
+ boolean isHeightFits = (origImageHeight <= screenHeight);
+
+ // see of need to scale or simply center
+ if (isWidthFits && isHeightFits) {
+ graphics.drawImage(this.origImage,
+ (screenWidth - origImageWidth) / 2,
+ (screenHeight - origImageHeight) / 2, null);
+ graphics.dispose();
+ return true;
+ }
+ if (isWidthFits) {
+ // height doesn't fit
+ double scaleFact = (double) screenHeight
+ / (double) origImageHeight;
+ int dx = (int) (screenWidth - scaleFact * origImageWidth) / 2;
+ // The following line is Mark Haag's to make the
+ // scaling less jaggy
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ graphics.drawImage(this.origImage, dx, 0, screenWidth - dx,
+ screenHeight, 0, 0, origImageWidth, origImageHeight,
+ null);
+ graphics.dispose();
+ return true;
+ }
+ if (isHeightFits) {
+ // width doesn't fit
+ double scaleFact = (double) screenWidth
+ / (double) origImageWidth;
+ int dy = (int) (screenHeight - scaleFact * origImageHeight) / 2;
+ // The following line is Mark Haag's to make the
+ // scaling less jaggy
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ graphics.drawImage(this.origImage, 0, dy, screenWidth,
+ screenHeight - dy, 0, 0, origImageWidth,
+ origImageHeight, null);
+ graphics.dispose();
+ return true;
+ }
+ // none fits
+ double scaleFactY = (double) screenHeight
+ / (double) origImageHeight;
+ double scaleFactX = (double) screenWidth / (double) origImageWidth;
+ double scaleFact = Math.min(scaleFactX, scaleFactY);
+ int dx = Math.max(0, (int) (screenWidth - scaleFact
+ * origImageWidth) / 2);
+ int dy = Math.max(0, (int) (screenHeight - scaleFact
+ * origImageHeight) / 2);
+ // The following line is Mark Haag's to make the
+ // scaling less jaggy
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ graphics.drawImage(this.origImage, dx, dy, screenWidth - dx,
+ screenHeight - dy, 0, 0, origImageWidth, origImageHeight,
+ null);
+ graphics.dispose();
+ return true;
+ }
+
+ if ((getKind() == ImageWatermarkKind.SCREEN_TILE)
+ || (getKind() == ImageWatermarkKind.APP_TILE)) {
+ watermarkImage = SubstanceCoreUtilities.getBlankImage(screenWidth,
+ screenHeight);
+
+ Graphics2D graphics = (Graphics2D) watermarkImage.getGraphics()
+ .create();
+
+ Composite comp = AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, opacity);
+ graphics.setComposite(comp);
+
+ int replicateX = 1 + screenWidth / origImageWidth;
+ int replicateY = 1 + screenHeight / origImageHeight;
+
+ for (int i = 0; i < replicateX; i++) {
+ for (int j = 0; j < replicateY; j++) {
+ graphics.drawImage(this.origImage, i * origImageWidth, j
+ * origImageHeight, null);
+ }
+ }
+ graphics.dispose();
+ return true;
+ }
+
+ if ((getKind() == ImageWatermarkKind.APP_ANCHOR)
+ || (getKind() == ImageWatermarkKind.APP_CENTER)) {
+ watermarkImage = SubstanceCoreUtilities.getBlankImage(
+ origImageWidth, origImageHeight);
+
+ Graphics2D graphics = (Graphics2D) watermarkImage.getGraphics()
+ .create();
+
+ Composite comp = AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, opacity);
+ graphics.setComposite(comp);
+
+ graphics.drawImage(this.origImage, 0, 0, null);
+ graphics.dispose();
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.trait.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return "Image";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.watermark.SubstanceWatermark#dispose()
+ */
+ @Override
+ public void dispose() {
+ watermarkImage = null;
+ }
+
+ /**
+ * Returns the location of the original image.
+ *
+ * @return The location of the original image.
+ */
+ public String getOrigImageLocation() {
+ return this.origImageLocation;
+ }
+
+ /**
+ * Sets image watermark kind.
+ *
+ * @param kind
+ * Image watermark kind.
+ */
+ public void setKind(ImageWatermarkKind kind) {
+ if (kind == null) {
+ throw new IllegalArgumentException(
+ "Can't pass null to SubstanceImageWatermark.setKind()");
+ }
+ this.kind = kind;
+ this.updateWatermarkImage(SubstanceLookAndFeel.getCurrentSkin(null));
+ }
+
+ /**
+ * Returns the image watermark kind.
+ *
+ * @return Image watermark kind.
+ */
+ public ImageWatermarkKind getKind() {
+ return this.kind;
+ }
+
+ /**
+ * Returns the image watermark opacity.
+ *
+ * @return Image watermark opacity.
+ */
+ public float getOpacity() {
+ return this.opacity;
+ }
+
+ /**
+ * Sets image watermark opacity.
+ *
+ * @param opacity
+ * Image watermark opacity.
+ * @throws IllegalArgumentException
+ * if the argument is not in 0.0-1.0 range.
+ */
+ public void setOpacity(float opacity) {
+ if ((opacity < 0.0f) || (opacity > 1.0f)) {
+ throw new IllegalArgumentException(
+ "SubstanceImageWatermark.setOpacity() can get value in 0.0-1.0 range, was passed value "
+ + opacity);
+ }
+ this.opacity = opacity;
+ this.updateWatermarkImage(SubstanceLookAndFeel.getCurrentSkin(null));
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceNullWatermark.java b/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceNullWatermark.java
new file mode 100644
index 0000000..90e3f09
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceNullWatermark.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.watermark;
+
+import java.awt.*;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Implementation of {@link SubstanceWatermark} that paints a translucent fill
+ * (with no pattern). This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @author Chris Hall
+ */
+public class SubstanceNullWatermark implements SubstanceWatermark {
+ /**
+ * Simple constructor.
+ */
+ public SubstanceNullWatermark() {
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.watermark.SubstanceWatermark#drawWatermarkImage
+ * (java .awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void drawWatermarkImage(Graphics graphics, Component c, int x,
+ int y, int width, int height) {
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(c);
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.setComposite(LafWidgetUtilities
+ .getAlphaComposite(c, 0.2f, graphics));
+ g2d.setColor(skin.getWatermarkColorScheme().getWatermarkLightColor());
+ g2d.fillRect(x, y, width, height);
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.watermark.SubstanceWatermark#previewWatermark
+ * (java.awt.Graphics, org.pushingpixels.substance.api.SubstanceSkin, int,
+ * int, int, int)
+ */
+ @Override
+ public void previewWatermark(Graphics g, SubstanceSkin skin, int x, int y,
+ int width, int height) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.watermark.SubstanceWatermark#updateWatermarkImage
+ * (org.pushingpixels.substance.skin.SubstanceSkin)
+ */
+ @Override
+ public boolean updateWatermarkImage(SubstanceSkin skin) {
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.trait.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return "Null";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.watermark.SubstanceWatermark#dispose()
+ */
+ @Override
+ public void dispose() {
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceStripeWatermark.java b/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceStripeWatermark.java
new file mode 100644
index 0000000..a9a2dd8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceStripeWatermark.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.watermark;
+
+import java.awt.*;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Simple implementation of
+ * {@link org.pushingpixels.substance.api.watermark.SubstanceWatermark}, drawing darker
+ * even lines as watermark. This is the default watermark implementation. This
+ * class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ * @author Chris Hall
+ */
+public class SubstanceStripeWatermark implements SubstanceWatermark {
+ /**
+ * Watermark image (screen-sized).
+ */
+ private static Image watermarkImage = null;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.watermark.SubstanceWatermark#drawWatermarkImage(java
+ * .awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void drawWatermarkImage(Graphics graphics, Component c, int x,
+ int y, int width, int height) {
+ if (!c.isShowing())
+ return;
+ // System.err.println(System.currentTimeMillis() + ":"
+ // + c.getClass().getName() + ":" + x + ":" + y + ":" + width
+ // + ":" + height);
+ int dx = c.getLocationOnScreen().x;
+ int dy = c.getLocationOnScreen().y;
+ graphics.drawImage(SubstanceStripeWatermark.watermarkImage, x, y, x
+ + width, y + height, x + dx, y + dy, x + dx + width, y + dy
+ + height, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.watermark.SubstanceWatermark#updateWatermarkImage
+ * (org.pushingpixels.substance.skin.SubstanceSkin)
+ */
+ @Override
+ public boolean updateWatermarkImage(SubstanceSkin skin) {
+ // fix by Chris for bug 67 - support for multiple screens
+ Rectangle virtualBounds = new Rectangle();
+ GraphicsEnvironment ge = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice[] gds = ge.getScreenDevices();
+ for (GraphicsDevice gd : gds) {
+ GraphicsConfiguration gc = gd.getDefaultConfiguration();
+ virtualBounds = virtualBounds.union(gc.getBounds());
+ }
+
+ int screenWidth = virtualBounds.width;
+ int screenHeight = virtualBounds.height;
+ SubstanceStripeWatermark.watermarkImage = SubstanceCoreUtilities
+ .getBlankImage(screenWidth, screenHeight);
+
+ Graphics2D graphics = (Graphics2D) SubstanceStripeWatermark.watermarkImage
+ .getGraphics().create();
+
+ boolean status = this.drawWatermarkImage(skin, graphics, 0, 0,
+ screenWidth, screenHeight, false);
+ graphics.dispose();
+ return status;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.api.watermark.SubstanceWatermark#previewWatermark
+ * (java.awt.Graphics, org.pushingpixels.substance.api.SubstanceSkin, int, int, int,
+ * int)
+ */
+ @Override
+ public void previewWatermark(Graphics g, SubstanceSkin skin, int x, int y,
+ int width, int height) {
+ this
+ .drawWatermarkImage(skin, (Graphics2D) g, x, y, width, height,
+ true);
+ }
+
+ /**
+ * Draws the specified portion of the watermark image.
+ *
+ * @param skin
+ * Skin to use for painting the watermark.
+ * @param graphics
+ * Graphic context.
+ * @param x
+ * the <i>x</i> coordinate of the watermark to be drawn.
+ * @param y
+ * The <i>y</i> coordinate of the watermark to be drawn.
+ * @param width
+ * The width of the watermark to be drawn.
+ * @param height
+ * The height of the watermark to be drawn.
+ * @param isPreview
+ * Indication whether the result is a preview image.
+ * @return Indication whether the draw succeeded.
+ */
+ private boolean drawWatermarkImage(SubstanceSkin skin, Graphics2D graphics,
+ int x, int y, int width, int height, boolean isPreview) {
+ Color stampColor = null;
+ SubstanceColorScheme scheme = skin.getWatermarkColorScheme();
+ if (isPreview)
+ stampColor = scheme.isDark() ? Color.lightGray : Color.darkGray;
+ else {
+ stampColor = scheme.getWatermarkStampColor();
+ }
+
+ graphics.setColor(stampColor);
+ for (int row = y; row < (y + height); row += 2) {
+ graphics.drawLine(x, row, x + width, row);
+ }
+ if (isPreview) {
+ graphics.setColor(Color.gray);
+ for (int row = y + 1; row < (y + height); row += 2) {
+ graphics.drawLine(x, row, x + width, row);
+ }
+ }
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.api.trait.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return "Stripes";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.watermark.SubstanceWatermark#dispose()
+ */
+ @Override
+ public void dispose() {
+ watermarkImage = null;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceWatermark.java b/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceWatermark.java
new file mode 100644
index 0000000..ccc795f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/api/watermark/SubstanceWatermark.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.api.watermark;
+
+import java.awt.Component;
+import java.awt.Graphics;
+
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.trait.SubstanceTrait;
+
+/**
+ * Interface for watermarks. This class is part of officially supported API.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SubstanceWatermark extends SubstanceTrait {
+ /**
+ * Draws the watermark on the specified graphics context in the specified
+ * region.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param c
+ * Component that is painted.
+ * @param x
+ * Left X of the region.
+ * @param y
+ * Top Y of the region.
+ * @param width
+ * Region width.
+ * @param height
+ * Region height.
+ */
+ public void drawWatermarkImage(Graphics graphics, Component c, int x,
+ int y, int width, int height);
+
+ /**
+ * Updates the current watermark image.
+ *
+ * @param skin
+ * Skin for the watermark.
+ * @return <code>true</code> if the watermark has been updated successfully,
+ * <code>false</code> otherwise.
+ */
+ public boolean updateWatermarkImage(SubstanceSkin skin);
+
+ /**
+ * Draws the preview of the watermark image.
+ *
+ * @param g
+ * Graphic context.
+ * @param skin
+ * Optional skin to use for the preview. Can be ignored by the
+ * implementation.
+ * @param x
+ * the <i>x</i> coordinate of the watermark to be drawn.
+ * @param y
+ * The <i>y</i> coordinate of the watermark to be drawn.
+ * @param width
+ * The width of the watermark to be drawn.
+ * @param height
+ * The height of the watermark to be drawn.
+ */
+ public void previewWatermark(Graphics g, SubstanceSkin skin, int x, int y,
+ int width, int height);
+
+ /**
+ * Disposes the memory associated with <code>this</code> watermark.
+ */
+ public void dispose();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/animation/IconGlowTracker.java b/substance/src/main/java/org/pushingpixels/substance/internal/animation/IconGlowTracker.java
new file mode 100644
index 0000000..c59c1c2
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/animation/IconGlowTracker.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.animation;
+
+import java.awt.Component;
+
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.RepeatBehavior;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.swing.SwingRepaintCallback;
+
+public class IconGlowTracker {
+ private Timeline iconGlowTimeline;
+
+ private Component component;
+
+ public IconGlowTracker(Component component) {
+ this.component = component;
+
+ this.iconGlowTimeline = new Timeline(this.component);
+ AnimationConfigurationManager.getInstance().configureTimeline(
+ this.iconGlowTimeline);
+ this.iconGlowTimeline.setDuration(10 * this.iconGlowTimeline
+ .getDuration());
+ this.iconGlowTimeline.addCallback(new SwingRepaintCallback(component));
+ this.iconGlowTimeline.setName("Icon glow");
+ }
+
+ public void play() {
+ this.iconGlowTimeline.playLoop(RepeatBehavior.REVERSE);
+ }
+
+ public void play(int fullLoopCount) {
+ this.iconGlowTimeline.playLoop(fullLoopCount, RepeatBehavior.REVERSE);
+ }
+
+ public boolean isPlaying() {
+ return (this.iconGlowTimeline.getState() != TimelineState.IDLE);
+ }
+
+ public void cancel() {
+ if (this.iconGlowTimeline.getState() != TimelineState.IDLE) {
+ this.iconGlowTimeline.cancelAtCycleBreak();
+ }
+ }
+
+ public float getIconGlowPosition() {
+ return this.iconGlowTimeline.getTimelinePosition();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/animation/ModificationAwareUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/animation/ModificationAwareUI.java
new file mode 100644
index 0000000..51f416d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/animation/ModificationAwareUI.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.animation;
+
+import org.pushingpixels.trident.Timeline;
+
+public interface ModificationAwareUI {
+ public Timeline getModificationTimeline();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/animation/RootPaneDefaultButtonTracker.java b/substance/src/main/java/org/pushingpixels/substance/internal/animation/RootPaneDefaultButtonTracker.java
new file mode 100755
index 0000000..7105214
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/animation/RootPaneDefaultButtonTracker.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.animation;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+import javax.swing.*;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.RepeatBehavior;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
+
+/**
+ * Tracker for pulsating (default and focused) <code>JButton</code>s. This class
+ * is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RootPaneDefaultButtonTracker extends
+ UIThreadTimelineCallbackAdapter {
+ /**
+ * Map (with weakly-referenced keys) of all trackers. For each default
+ * button which has not been claimed by GC, we have a tracker (with
+ * associated <code>Timer</code>).
+ */
+ private static WeakHashMap<JButton, RootPaneDefaultButtonTracker> trackers = new WeakHashMap<JButton, RootPaneDefaultButtonTracker>();
+
+ /**
+ * Waek reference to the associated button.
+ */
+ private WeakReference<JButton> buttonRef;
+
+ /**
+ * The associated timeline.
+ */
+ private Timeline timeline;
+
+ /**
+ * Simple constructor.
+ *
+ * @param jbutton
+ */
+ private RootPaneDefaultButtonTracker(JButton jbutton) {
+ // Create weak reference.
+ this.buttonRef = new WeakReference<JButton>(jbutton);
+ // Create coalesced timer.
+ this.timeline = new Timeline(this);
+ this.timeline.addCallback(this);
+ // Store event handler and initial cycle count.
+ RootPaneDefaultButtonTracker.trackers.put(jbutton, this);
+ }
+
+ /**
+ * Recursively checks whether the specified component or one of its inner
+ * components has focus.
+ *
+ * @param component
+ * Component to check.
+ * @return <code>true</code> if the specified component or one of its inner
+ * components has focus, <code>false</code> otherwise.
+ */
+ private static boolean isInFocusedWindow(Component component) {
+ if (component == null) {
+ return false;
+ }
+
+ // check if the component itself is focus owner
+ if (component.isFocusOwner()) {
+ return true;
+ }
+
+ // recursively find if has focus owner component
+ if (component instanceof Container) {
+ for (Component comp : ((Container) component).getComponents()) {
+ if (isInFocusedWindow(comp)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Recursively checks whether the specified component has visible glass
+ * pane.
+ *
+ * @param component
+ * Component to check.
+ * @return <code>true</code> if the specified component has visible glass
+ * pane, <code>false</code> otherwise.
+ */
+ private static boolean hasGlassPane(Component component) {
+ if (component == null) {
+ return false;
+ }
+ // check if the component has visible glass pane
+ Component glassPane = null;
+ if (component instanceof JDialog) {
+ glassPane = ((JDialog) component).getGlassPane();
+ }
+ if (component instanceof JFrame) {
+ glassPane = ((JFrame) component).getGlassPane();
+ }
+ if ((glassPane != null) && (glassPane.isVisible())) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ this.onTimelineEvent();
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction, float timelinePosition) {
+ this.onTimelineEvent();
+ }
+
+ void onTimelineEvent() {
+ // get the button and check if it wasn't GC'd
+ JButton jButton = this.buttonRef.get();
+ if (jButton == null) {
+ return;
+ }
+
+ if (!jButton.isDisplayable()) {
+ timeline.abort();
+ return;
+ }
+
+ if (RootPaneDefaultButtonTracker.hasGlassPane(jButton
+ .getTopLevelAncestor()))
+ return;
+ if (!isPulsating(jButton)) {
+ // has since lost its default status
+ RootPaneDefaultButtonTracker tracker = trackers.get(jButton);
+ tracker.stopTimer();
+ tracker.buttonRef.clear();
+ trackers.remove(jButton);
+ } else {
+ if (!RootPaneDefaultButtonTracker.isInFocusedWindow(jButton
+ .getTopLevelAncestor())) {
+ // && (!isAttentionDrawingCloseButton(jButton))) {
+ // no focus in button window - will restore original (not
+ // animated) painting
+ RootPaneDefaultButtonTracker.update(jButton);
+ } else {
+ // check if it's enabled
+ if (!jButton.isEnabled()) {
+ if (timeline.getState() != TimelineState.SUSPENDED) {
+ timeline.suspend();
+ }
+ } else {
+ if (timeline.getState() == TimelineState.SUSPENDED) {
+ timeline.resume();
+ }
+ }
+ // if (jButton.isEnabled()) {
+ // // increment cycle count for pulsating buttons.
+ // long oldCycle = cycles.get(jButton);
+ // if (oldCycle == 20) {
+ // oldCycle = isAttentionDrawingCloseButton(jButton) ? -80
+ // : 0;
+ // }
+ // cycles.put(jButton, oldCycle + 1);
+ // } else {
+ // // revert to 0 if it's not enabled
+ // if (cycles.get(jButton) != 0) {
+ // cycles.put(jButton, (long) 0);
+ // }
+ // }
+ }
+ }
+ // maybe LAF has changed
+ if (SubstanceLookAndFeel.isCurrentLookAndFeel())
+ jButton.repaint();
+ }
+
+ /**
+ * Starts the associated timer.
+ */
+ private void startTimer() {
+ this.timeline.playLoop(RepeatBehavior.REVERSE);
+ }
+
+ /**
+ * Stops the associated timer.
+ */
+ private void stopTimer() {
+ this.timeline.cancel();
+ }
+
+ /**
+ * Returns the status of the associated timer.
+ *
+ * @return <code>true</code> is the associated timer is running,
+ * <code>false</code> otherwise.
+ */
+ private boolean isRunning() {
+ TimelineState state = this.timeline.getState();
+ return ((state == TimelineState.PLAYING_FORWARD) || (state == TimelineState.PLAYING_REVERSE));
+ }
+
+ /**
+ * Updates the state of the specified button which must be a default button
+ * in some window. The button state is determined based on focus ownership.
+ *
+ * @param jButton
+ * Button.
+ */
+ public static void update(JButton jButton) {
+ if (jButton == null)
+ return;
+
+ boolean hasFocus = RootPaneDefaultButtonTracker
+ .isInFocusedWindow(jButton.getTopLevelAncestor());
+ RootPaneDefaultButtonTracker tracker = trackers.get(jButton);
+ if (!hasFocus) {
+ // remove
+ if (tracker == null) {
+ return;
+ }
+ // if (cycles.get(jButton) == 0) {
+ // return;
+ // }
+ // cycles.put(jButton, (long) 0);
+ // System.out.println("r::" + trackers.size());
+ } else {
+ // add
+ if (tracker != null) {
+ tracker.startTimer();
+ return;
+ }
+ tracker = new RootPaneDefaultButtonTracker(jButton);
+ tracker.startTimer();
+ trackers.put(jButton, tracker);
+ // long initialCycle = isAttentionDrawingCloseButton(jButton) ? -80
+ // : 0;
+ // cycles.put(jButton, initialCycle);
+ // System.out.println("a::" + trackers.size());
+ }
+ }
+
+ /**
+ * Retrieves the current cycle count for the specified button.
+ *
+ * @param jButton
+ * Button.
+ * @return Current cycle count for the specified button.
+ */
+ public static float getTimelinePosition(JButton jButton) {
+ RootPaneDefaultButtonTracker tracker = trackers.get(jButton);
+ if (tracker == null)
+ return 0;
+ return tracker.timeline.getTimelinePosition();
+ // Long cycleCount = cycles.get(jButton);
+ // if (cycleCount == null) {
+ // return 0;
+ // }
+ // long result = cycleCount.longValue();
+ // if (result < 0)
+ // result = 0;
+ // return result;
+ }
+
+ /**
+ * Retrieves the animation state for the specified button.
+ *
+ * @param jButton
+ * Button.
+ * @return <code>true</code> if the specified button is being animated,
+ * <code>false</code> otherwise.
+ */
+ public static boolean isAnimating(JButton jButton) {
+ RootPaneDefaultButtonTracker tracker = trackers.get(jButton);
+ if (tracker == null) {
+ return false;
+ }
+ return tracker.isRunning();
+ }
+
+ /**
+ * Returns memory usage.
+ *
+ * @return Memory usage string.
+ */
+ static String getMemoryUsage() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("PulseTracker: \n");
+ sb.append("\t" + trackers.size() + " trackers");
+ // + cycles.size();
+ // + " cycles");
+ return sb.toString();
+ }
+
+ // /**
+ // * Checks whether the specified button is attention-drawing
+ // * <code>close</code> button of some internal frame, root pane or desktop
+ // * icon.
+ // *
+ // * @param jButton
+ // * Button.
+ // * @return <code>true</code> if the specified button is <code>close</code>
+ // * button of some internal frame, root pane or desktop icon,
+ // * <code>false</code> otherwise.
+ // */
+ // public static boolean isAttentionDrawingCloseButton(JButton jButton) {
+ // if (SubstanceCoreUtilities.isTitleCloseButton(jButton)) {
+ // // check if have windowModified property
+ // Component comp = jButton;
+ // boolean isWindowModified = false;
+ // while (comp != null) {
+ // if (comp instanceof JInternalFrame) {
+ // JInternalFrame jif = (JInternalFrame) comp;
+ // isWindowModified = Boolean.TRUE
+ // .equals(jif
+ // .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
+ // break;
+ // }
+ // if (comp instanceof JRootPane) {
+ // JRootPane jrp = (JRootPane) comp;
+ // isWindowModified = Boolean.TRUE
+ // .equals(jrp
+ // .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
+ // break;
+ // }
+ // if (comp instanceof JDesktopIcon) {
+ // JDesktopIcon jdi = (JDesktopIcon) comp;
+ // JInternalFrame jif = jdi.getInternalFrame();
+ // isWindowModified = Boolean.TRUE
+ // .equals(jif
+ // .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
+ // break;
+ // }
+ // comp = comp.getParent();
+ // }
+ // if (isWindowModified) {
+ // return true;
+ // }
+ // }
+ // return false;
+ // }
+
+ /**
+ * Checks whether the specified button is pulsating.
+ *
+ * @param jButton
+ * Button.
+ * @return <code>true</code> if the specified button is pulsating,
+ * <code>false</code> otherwise.
+ */
+ public static boolean isPulsating(JButton jButton) {
+ // default button pulsates.
+ boolean isDefault = jButton.isDefaultButton();
+ if (isDefault) {
+ return true;
+ }
+ return false;
+ // attention-drawing close button pulsates.
+ // return isAttentionDrawingCloseButton(jButton);
+ }
+
+ /**
+ * Stops all timers.
+ */
+ public static void stopAllTimers() {
+ for (RootPaneDefaultButtonTracker tracker : trackers.values()) {
+ tracker.stopTimer();
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/animation/StateTransitionEvent.java b/substance/src/main/java/org/pushingpixels/substance/internal/animation/StateTransitionEvent.java
new file mode 100644
index 0000000..583cf04
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/animation/StateTransitionEvent.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.animation;
+
+import java.util.EventObject;
+
+import org.pushingpixels.trident.Timeline.TimelineState;
+
+public class StateTransitionEvent extends EventObject {
+ private TimelineState newState;
+
+ private TimelineState oldState;
+
+ public StateTransitionEvent(Object source, TimelineState oldState,
+ TimelineState newState) {
+ super(source);
+ this.oldState = oldState;
+ this.newState = newState;
+ }
+
+ public TimelineState getOldState() {
+ return oldState;
+ }
+
+ public TimelineState getNewState() {
+ return newState;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/animation/StateTransitionListener.java b/substance/src/main/java/org/pushingpixels/substance/internal/animation/StateTransitionListener.java
new file mode 100644
index 0000000..dcb833b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/animation/StateTransitionListener.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.animation;
+
+import java.util.EventListener;
+
+public interface StateTransitionListener extends EventListener {
+ public void onModelStateTransition(StateTransitionEvent stateTransitionEvent);
+
+ public void onFocusStateTransition(StateTransitionEvent stateTransitionEvent);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/animation/StateTransitionMultiTracker.java b/substance/src/main/java/org/pushingpixels/substance/internal/animation/StateTransitionMultiTracker.java
new file mode 100644
index 0000000..5fc0862
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/animation/StateTransitionMultiTracker.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.animation;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public final class StateTransitionMultiTracker<T> {
+ private Map<Comparable<T>, StateTransitionTracker> trackerMap;
+
+ private boolean isInCleaning;
+
+ public StateTransitionMultiTracker() {
+ this.trackerMap = new HashMap<Comparable<T>, StateTransitionTracker>();
+ }
+
+ public synchronized void clear() {
+ this.isInCleaning = true;
+ for (StateTransitionTracker tracker : this.trackerMap.values()) {
+ tracker.endTransition();
+ }
+ this.trackerMap.clear();
+ this.isInCleaning = false;
+ }
+
+ public synchronized int size() {
+ return this.trackerMap.size();
+ }
+
+ public synchronized StateTransitionTracker getTracker(Comparable<T> id) {
+ return this.trackerMap.get(id);
+ }
+
+ public synchronized void addTracker(final Comparable<T> id,
+ final StateTransitionTracker tracker) {
+ this.trackerMap.put(id, tracker);
+
+ // System.out.println("Storing tracker @" + tracker.hashCode() + " for "
+ // + tracker.component.getClass().getSimpleName() + ":" + id);
+
+ StateTransitionListener listener = new StateTransitionListener() {
+ @Override
+ public void onModelStateTransition(
+ StateTransitionEvent stateTransitionEvent) {
+ if (isInCleaning)
+ return;
+
+ if (!tracker.hasRunningTimelines()) {
+ // System.out.println("Removing tracker for " + id);
+ trackerMap.remove(id);
+ tracker.unregisterModelListeners();
+ tracker.removeStateTransitionListener(this);
+ }
+ }
+
+ @Override
+ public void onFocusStateTransition(
+ StateTransitionEvent stateTransitionEvent) {
+ if (isInCleaning)
+ return;
+
+ if (!tracker.hasRunningTimelines()) {
+ // System.out.println("Removing tracker for " + id);
+ trackerMap.remove(id);
+ tracker.unregisterModelListeners();
+ tracker.removeStateTransitionListener(this);
+ }
+ }
+ };
+ tracker.addStateTransitionListener(listener);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/animation/StateTransitionTracker.java b/substance/src/main/java/org/pushingpixels/substance/internal/animation/StateTransitionTracker.java
new file mode 100644
index 0000000..26316a1
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/animation/StateTransitionTracker.java
@@ -0,0 +1,780 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.animation;
+
+import java.awt.Container;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.tree.TreeCellRenderer;
+
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.renderers.SubstanceRenderer;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.RepeatBehavior;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.TimelineCallback;
+import org.pushingpixels.trident.callback.TimelineCallbackAdapter;
+import org.pushingpixels.trident.swing.SwingRepaintCallback;
+
+public class StateTransitionTracker {
+ public static interface RepaintCallback {
+ public TimelineCallback getRepaintCallback();
+ }
+
+ JComponent component;
+
+ private ButtonModel model;
+
+ private ChangeListener modelChangeListener;
+
+ private Timeline transitionTimeline;
+
+ private float transitionPosition;
+
+ /**
+ * Listener on the focus gain and loss.
+ */
+ private FocusListener focusListener;
+
+ private Timeline focusTimeline;
+
+ private Timeline focusLoopTimeline;
+
+ private IconGlowTracker iconGlowTracker;
+
+ private StateTransitionTracker.RepaintCallback repaintCallback;
+
+ private boolean isAutoTrackingModelChanges;
+
+ private EventListenerList eventListenerList;
+
+ private String name;
+
+ private ModelStateInfo modelStateInfo;
+
+ public static class StateContributionInfo {
+ float start;
+
+ float end;
+
+ float curr;
+
+ public StateContributionInfo(float start, float end) {
+ this.start = start;
+ this.end = end;
+ this.curr = start;
+ }
+
+ public float getContribution() {
+ return curr;
+ }
+
+ void updateContribution(float timelinePosition) {
+ this.curr = this.start + timelinePosition * (this.end - this.start);
+ }
+ }
+
+ public static class ModelStateInfo {
+ private Map<ComponentState, StateContributionInfo> stateContributionMap;
+
+ private Map<ComponentState, StateContributionInfo> stateNoSelectionContributionMap;
+
+ ComponentState currState;
+
+ ComponentState currStateNoSelection;
+
+ float activeStrength;
+
+ public ModelStateInfo() {
+ this.stateContributionMap = new HashMap<ComponentState, StateContributionInfo>();
+ this.stateNoSelectionContributionMap = new HashMap<ComponentState, StateContributionInfo>();
+ this.activeStrength = 0.0f;
+ }
+
+ public ComponentState getCurrModelState() {
+ return currState;
+ }
+
+ public ComponentState getCurrModelStateNoSelection() {
+ return currStateNoSelection;
+ }
+
+ public Map<ComponentState, StateContributionInfo> getStateContributionMap() {
+ return this.stateContributionMap;
+ }
+
+ public Map<ComponentState, StateContributionInfo> getStateNoSelectionContributionMap() {
+ return this.stateNoSelectionContributionMap;
+ }
+
+ void sync() {
+ this.activeStrength = 0.0f;
+ for (Map.Entry<ComponentState, StateContributionInfo> activeEntry : this.stateContributionMap
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState.isActive()) {
+ this.activeStrength += activeEntry.getValue()
+ .getContribution();
+ }
+ }
+ }
+
+ float getActiveStrength() {
+ return this.activeStrength;
+ }
+
+ void clear() {
+ if ((SubstanceCoreUtilities.reallyPrintThreadingExceptions() || SubstanceCoreUtilities.reallyThrowThreadingExceptions())
+ && !SwingUtilities.isEventDispatchThread())
+ {
+ UiThreadingViolationException uiThreadingViolationError = new UiThreadingViolationException(
+ "State tracking must be done on Event Dispatch Thread");
+ if (SubstanceCoreUtilities.reallyPrintThreadingExceptions()) {
+ uiThreadingViolationError.printStackTrace(System.err);
+ }
+ if (SubstanceCoreUtilities.reallyThrowThreadingExceptions()) {
+ throw uiThreadingViolationError;
+ }
+ }
+ this.stateContributionMap.clear();
+ this.stateContributionMap.put(this.currState,
+ new StateContributionInfo(1.0f, 1.0f));
+ this.stateNoSelectionContributionMap.clear();
+ this.stateNoSelectionContributionMap.put(this.currStateNoSelection,
+ new StateContributionInfo(1.0f, 1.0f));
+ this.sync();
+ }
+ }
+
+ public StateTransitionTracker(final JComponent component, ButtonModel model) {
+ this.component = component;
+ this.model = model;
+
+ this.modelStateInfo = new ModelStateInfo();
+ this.modelStateInfo.currState = ComponentState.getState(model,
+ component);
+ this.modelStateInfo.currStateNoSelection = ComponentState.getState(
+ model, component, true);
+ this.modelStateInfo.clear();
+
+ this.repaintCallback = new StateTransitionTracker.RepaintCallback() {
+ @Override
+ public SwingRepaintCallback getRepaintCallback() {
+ return new SwingRepaintCallback(component);
+ }
+ };
+ this.isAutoTrackingModelChanges = true;
+ this.eventListenerList = new EventListenerList();
+
+ this.focusTimeline = new Timeline(this.component);
+ AnimationConfigurationManager.getInstance().configureTimeline(
+ this.focusTimeline);
+ this.focusTimeline.addCallback(this.repaintCallback
+ .getRepaintCallback());
+ // notify listeners on focus state transition
+ this.focusTimeline.addCallback(new TimelineCallbackAdapter() {
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ fireFocusStateTransitionEvent(oldState, newState);
+ }
+ });
+
+ this.focusLoopTimeline = new Timeline(this.component);
+ AnimationConfigurationManager.getInstance().configureTimeline(
+ this.focusLoopTimeline);
+ this.focusLoopTimeline.addCallback(this.repaintCallback
+ .getRepaintCallback());
+
+ this.iconGlowTracker = new IconGlowTracker(this.component);
+
+ this.name = "";
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setRepaintCallback(
+ StateTransitionTracker.RepaintCallback repaintCallback) {
+ this.repaintCallback = repaintCallback;
+ }
+
+ public void registerFocusListeners() {
+ this.focusListener = new FocusListener() {
+ @Override
+ public void focusGained(FocusEvent e) {
+ setFocusState(true);
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ setFocusState(false);
+ }
+ };
+ this.component.addFocusListener(this.focusListener);
+ }
+
+ public void registerModelListeners() {
+ this.modelChangeListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (isAutoTrackingModelChanges)
+ onModelStateChanged();
+ }
+ };
+ this.model.addChangeListener(this.modelChangeListener);
+ }
+
+ public void unregisterFocusListeners() {
+ this.component.removeFocusListener(this.focusListener);
+ this.focusListener = null;
+ }
+
+ public void unregisterModelListeners() {
+ this.model.removeChangeListener(this.modelChangeListener);
+ this.modelChangeListener = null;
+ }
+
+ public void setTransitionPosition(float transitionPosition) {
+ this.transitionPosition = transitionPosition;
+ }
+
+ public void setModel(ButtonModel model) {
+ this.model.removeChangeListener(this.modelChangeListener);
+ if (this.transitionTimeline != null) {
+ this.transitionTimeline.abort();
+ this.transitionPosition = 0.0f;
+ }
+
+ this.modelStateInfo.currState = ComponentState.getState(model,
+ component);
+ this.modelStateInfo.currStateNoSelection = ComponentState.getState(
+ model, component, true);
+ this.modelStateInfo.clear();
+
+ this.model = model;
+ this.model.addChangeListener(this.modelChangeListener);
+
+ this.component.repaint();
+ }
+
+ public ButtonModel getModel() {
+ return model;
+ }
+
+ public void turnOffModelChangeTracking() {
+ this.isAutoTrackingModelChanges = false;
+ }
+
+ public void onModelStateChanged() {
+ this.isAutoTrackingModelChanges = true;
+
+ ComponentState newState = ComponentState.getState(this.model,
+ this.component);
+ ComponentState newStateNoSelection = ComponentState.getState(
+ this.model, this.component, true);
+
+ boolean isInRenderer = this.component.getClass().isAnnotationPresent(
+ SubstanceRenderer.class);
+ if (!isInRenderer) {
+ Container parent = this.component.getParent();
+ while (parent != null) {
+ if (CellRendererPane.class.isInstance(parent)
+ || ListCellRenderer.class.isInstance(parent)
+ || TreeCellRenderer.class.isInstance(parent)
+ || TableCellRenderer.class.isInstance(parent)) {
+ isInRenderer = true;
+ break;
+ }
+ parent = parent.getParent();
+ }
+ }
+
+ if (isInRenderer || (this.component.getParent() == null)) {
+ // no animations on renderers and parentless components
+ this.modelStateInfo.currState = newState;
+ this.modelStateInfo.currStateNoSelection = newStateNoSelection;
+ this.modelStateInfo.clear();
+ return;
+ }
+
+ // System.out.println("State changed from " + currState + " to "
+ // + newState);
+
+ // if (this.component instanceof JMenuItem) {
+ // System.out.println(((JMenuItem) this.component).getText());
+ // System.out.println("\tCURR:" + this.modelStateInfo.currState
+ // + ", NEW:" + newState);
+ // }
+
+ if (this.modelStateInfo.currState == newState)
+ return;
+
+ if (this.transitionTimeline != null) {
+ this.transitionTimeline.abort();
+ }
+ this.transitionTimeline = new Timeline(this);
+ this.transitionTimeline.setName("Model transitions");
+ this.transitionTimeline.addCallback(this.repaintCallback
+ .getRepaintCallback());
+ AnimationConfigurationManager.getInstance().configureTimeline(
+ this.transitionTimeline);
+ if (!this.modelStateInfo.currState
+ .isFacetActive(ComponentStateFacet.SELECTION)
+ && newState.isFacetActive(ComponentStateFacet.SELECTION)) {
+ // special handling for transition from non-selected to
+ // selected state - make it twice faster
+ this.transitionTimeline.setDuration(this.transitionTimeline
+ .getDuration() / 2);
+ }
+ long fullDuration = this.transitionTimeline.getDuration();
+ if (this.modelStateInfo.stateContributionMap.containsKey(newState)) {
+ // Going to a state that is already partially active. The
+ // new timeline is going to be shorter. The new state will go to
+ // 1.0f, hence the transition position begins from its current
+ // contribution.
+ this.transitionPosition = this.modelStateInfo.stateContributionMap
+ .get(newState).getContribution();
+ this.transitionTimeline.addPropertyToInterpolate(
+ "transitionPosition", this.transitionPosition, 1.0f);
+ this.transitionTimeline
+ .setDuration((long) (fullDuration * (1.0f - this.transitionPosition)));
+ // if ((this.component instanceof JMenuItem)
+ // && "Check enabled unselected"
+ // .equals(((JMenuItem) this.component).getText())) {
+ // System.out.println("*******************************");
+ // System.out.println("Timeline will run from "
+ // + this.transitionPosition + " with state " + newState);
+ // for (Map.Entry<ComponentState, StateContributionInfo> existing :
+ // this.modelStateInfo.stateContributionMap
+ // .entrySet()) {
+ // System.out.println("\t" + existing.getKey() + " in ["
+ // + existing.getValue().start + ":"
+ // + existing.getValue().end + "]:"
+ // + existing.getValue().curr);
+ // }
+ // System.out.println("*******************************");
+ // }
+ } else {
+ this.transitionPosition = 0.0f;
+ this.transitionTimeline.addPropertyToInterpolate(
+ "transitionPosition", 0.0f, 1.0f);
+ // if ((this.component instanceof JMenuItem)
+ // && "Check enabled unselected"
+ // .equals(((JMenuItem) this.component).getText())) {
+ // System.out.println("Timeline will run fully");
+ // }
+ }
+
+ Map<ComponentState, StateContributionInfo> newContributionMap = new HashMap<ComponentState, StateContributionInfo>();
+ if (this.modelStateInfo.stateContributionMap.containsKey(newState)) {
+ // 1. the new state goes from current value to 1.0
+ // 2. the rest go from current value to 0.0
+ for (Map.Entry<ComponentState, StateContributionInfo> existing : this.modelStateInfo.stateContributionMap
+ .entrySet()) {
+ StateContributionInfo currRange = existing.getValue();
+ ComponentState state = existing.getKey();
+ float newEnd = (state == newState) ? 1.0f : 0.0f;
+ newContributionMap.put(state, new StateContributionInfo(
+ currRange.curr, newEnd));
+ }
+ } else {
+ // 1. all existing states go from current value to 0.0
+ // 2. the new state goes from 0.0 to 1.0
+ for (Map.Entry<ComponentState, StateContributionInfo> existing : this.modelStateInfo.stateContributionMap
+ .entrySet()) {
+ StateContributionInfo currRange = existing.getValue();
+ ComponentState state = existing.getKey();
+ newContributionMap.put(state, new StateContributionInfo(
+ currRange.curr, 0.0f));
+ }
+ newContributionMap.put(newState, new StateContributionInfo(0.0f,
+ 1.0f));
+ }
+ this.modelStateInfo.stateContributionMap = newContributionMap;
+
+ Map<ComponentState, StateContributionInfo> newNoSelectionContributionMap = new HashMap<ComponentState, StateContributionInfo>();
+ if (this.modelStateInfo.stateNoSelectionContributionMap
+ .containsKey(newStateNoSelection)) {
+ // 1. the new state goes from current value to 1.0
+ // 2. the rest go from current value to 0.0
+ for (Map.Entry<ComponentState, StateContributionInfo> existing : this.modelStateInfo.stateNoSelectionContributionMap
+ .entrySet()) {
+ StateContributionInfo currRange = existing.getValue();
+ ComponentState state = existing.getKey();
+ float newEnd = (state == newStateNoSelection) ? 1.0f : 0.0f;
+ newNoSelectionContributionMap.put(state,
+ new StateContributionInfo(currRange.curr, newEnd));
+ }
+ } else {
+ // 1. all existing states go from current value to 0.0
+ // 2. the new state goes from 0.0 to 1.0
+ for (Map.Entry<ComponentState, StateContributionInfo> existing : this.modelStateInfo.stateNoSelectionContributionMap
+ .entrySet()) {
+ StateContributionInfo currRange = existing.getValue();
+ ComponentState state = existing.getKey();
+ newNoSelectionContributionMap.put(state,
+ new StateContributionInfo(currRange.curr, 0.0f));
+ }
+ newNoSelectionContributionMap.put(newStateNoSelection,
+ new StateContributionInfo(0.0f, 1.0f));
+ }
+ this.modelStateInfo.stateNoSelectionContributionMap = newNoSelectionContributionMap;
+
+ this.modelStateInfo.sync();
+
+ // if (this.component instanceof SubstanceScrollButton) {
+ // System.out.println("New contribution map for "
+ // + this.transitionTimeline.getDuration() + "ms");
+ // for (Map.Entry<ComponentState, StateContributionInfo> existing :
+ // this.modelStateInfo.stateContributionMap
+ // .entrySet()) {
+ // System.out.println("\t" + existing.getKey() + " in ["
+ // + existing.getValue().start + ":"
+ // + existing.getValue().end + "]");
+ // }
+ // }
+
+ this.transitionTimeline.addCallback(new TimelineCallbackAdapter() {
+ @Override
+ public void onTimelineStateChanged(final TimelineState oldState,
+ final TimelineState newState, final float durationFraction,
+ final float timelinePosition) {
+ if (newState == TimelineState.DONE) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ modelStateInfo.clear();
+ // repaint after the model state info has
+ // been cleared
+ repaintCallback.getRepaintCallback()
+ .onTimelineStateChanged(oldState, newState,
+ durationFraction, timelinePosition);
+ }
+ });
+ }
+ }
+ });
+ // notify listeners on model state transition
+ this.transitionTimeline.addCallback(new TimelineCallbackAdapter() {
+ @Override
+ public void onTimelineStateChanged(final TimelineState oldState,
+ final TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ fireModelStateTransitionEvent(oldState, newState);
+ }
+ });
+ }
+ });
+ // Add fix for issue 297 - menu items partially covered by lightweight
+ // popups (such as tooltips).
+ this.transitionTimeline.addCallback(new TimelineCallbackAdapter() {
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (component instanceof JMenuItem) {
+ if (SubstanceCoreUtilities
+ .isCoveredByLightweightPopups(component)) {
+ component
+ .putClientProperty(
+ SubstanceCoreUtilities.IS_COVERED_BY_LIGHTWEIGHT_POPUPS,
+ Boolean.TRUE);
+ } else {
+ component
+ .putClientProperty(
+ SubstanceCoreUtilities.IS_COVERED_BY_LIGHTWEIGHT_POPUPS,
+ null);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (component instanceof JMenuItem) {
+ if (SubstanceCoreUtilities
+ .isCoveredByLightweightPopups(component)) {
+ component
+ .putClientProperty(
+ SubstanceCoreUtilities.IS_COVERED_BY_LIGHTWEIGHT_POPUPS,
+ Boolean.TRUE);
+ } else {
+ component
+ .putClientProperty(
+ SubstanceCoreUtilities.IS_COVERED_BY_LIGHTWEIGHT_POPUPS,
+ null);
+ }
+ }
+ }
+ });
+ }
+ });
+
+ this.transitionTimeline.addCallback(new TimelineCallbackAdapter() {
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ updateActiveStates(timelinePosition);
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ updateActiveStates(timelinePosition);
+ }
+
+ private void updateActiveStates(final float timelinePosition) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ for (StateContributionInfo pair : modelStateInfo.stateContributionMap
+ .values()) {
+ pair.updateContribution(timelinePosition);
+ }
+ for (StateContributionInfo pair : modelStateInfo.stateNoSelectionContributionMap
+ .values()) {
+ pair.updateContribution(timelinePosition);
+ }
+ modelStateInfo.sync();
+ }
+ });
+ }
+
+ });
+
+ this.modelStateInfo.currState = newState;
+ this.modelStateInfo.currStateNoSelection = newStateNoSelection;
+
+ // if (this.component instanceof JMenuItem) {
+ // System.out.println("Running timeline on "
+ // + ((JMenuItem) this.component).getText() + " for "
+ // + this.transitionTimeline.getDuration());
+ // }
+ this.transitionTimeline.play();
+
+ // track icon glowing
+ if (AnimationConfigurationManager.getInstance().isAnimationAllowed(
+ AnimationFacet.ICON_GLOW, this.component)) {
+ boolean wasRollover = false;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : this.modelStateInfo.stateContributionMap
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == this.modelStateInfo.currState)
+ continue;
+
+ if (activeState.isFacetActive(ComponentStateFacet.ROLLOVER)) {
+ wasRollover = true;
+ break;
+ }
+ }
+ boolean isRollover = this.modelStateInfo.currState
+ .isFacetActive(ComponentStateFacet.ROLLOVER);
+ if (wasRollover && !isRollover) {
+ this.iconGlowTracker.cancel();
+ }
+ if (!wasRollover && isRollover) {
+ this.iconGlowTracker.play();
+ }
+ }
+ }
+
+ public float getFocusStrength(boolean hasFocus) {
+ if (this.focusTimeline == null)
+ return 0.0f;
+
+ TimelineState focusTimelineState = this.focusTimeline.getState();
+ if ((focusTimelineState == TimelineState.READY)
+ || (focusTimelineState == TimelineState.PLAYING_FORWARD)
+ || (focusTimelineState == TimelineState.PLAYING_REVERSE)) {
+ return this.focusTimeline.getTimelinePosition();
+ }
+ return hasFocus ? 1.0f : 0.0f;
+ }
+
+ public float getFocusLoopPosition() {
+ if (this.focusLoopTimeline == null)
+ return 0.0f;
+
+ return this.focusLoopTimeline.getTimelinePosition();
+ }
+
+ public float getIconGlowPosition() {
+ return this.iconGlowTracker.getIconGlowPosition();
+ }
+
+ public float getFacetStrength(ComponentStateFacet stateFacet) {
+ float result = 0.0f;
+ for (Map.Entry<ComponentState, StateContributionInfo> activeEntry : this.modelStateInfo.stateContributionMap
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState.isFacetActive(stateFacet)) {
+ result += activeEntry.getValue().getContribution();
+ }
+ }
+ return result;
+ }
+
+ public float getActiveStrength() {
+ return this.modelStateInfo.getActiveStrength();
+ }
+
+ public void addStateTransitionListener(
+ StateTransitionListener stateTransitionListener) {
+ // System.out.println("Adding state listener to @" + this.hashCode());
+ this.eventListenerList.add(StateTransitionListener.class,
+ stateTransitionListener);
+ }
+
+ public void removeStateTransitionListener(
+ StateTransitionListener stateTransitionListener) {
+ // System.out.println("Removing state listener from @" +
+ // this.hashCode());
+ this.eventListenerList.remove(StateTransitionListener.class,
+ stateTransitionListener);
+ }
+
+ private void fireModelStateTransitionEvent(TimelineState oldState,
+ TimelineState newState) {
+ // System.out.println("Fired state event from " + oldState + " to "
+ // + newState + " on @" + this.hashCode());
+ if (this.eventListenerList.getListenerCount() == 0)
+ return;
+
+ StateTransitionListener[] listeners = this.eventListenerList
+ .getListeners(StateTransitionListener.class);
+ if ((listeners == null) || (listeners.length == 0))
+ return;
+
+ StateTransitionEvent event = new StateTransitionEvent(this, oldState,
+ newState);
+ for (StateTransitionListener listener : listeners) {
+ listener.onModelStateTransition(event);
+ }
+ }
+
+ private void fireFocusStateTransitionEvent(TimelineState oldState,
+ TimelineState newState) {
+ if (this.eventListenerList.getListenerCount() == 0)
+ return;
+
+ StateTransitionListener[] listeners = this.eventListenerList
+ .getListeners(StateTransitionListener.class);
+ if ((listeners == null) || (listeners.length == 0))
+ return;
+
+ StateTransitionEvent event = new StateTransitionEvent(this, oldState,
+ newState);
+ for (StateTransitionListener listener : listeners) {
+ listener.onFocusStateTransition(event);
+ }
+ }
+
+ public void endTransition() {
+ if (this.transitionTimeline != null)
+ this.transitionTimeline.end();
+ }
+
+ public void setFocusState(boolean hasFocus) {
+ if (hasFocus) {
+ this.focusTimeline.play();
+ if (AnimationConfigurationManager.getInstance().isAnimationAllowed(
+ AnimationFacet.FOCUS_LOOP_ANIMATION, this.component)) {
+ this.focusLoopTimeline.playLoop(RepeatBehavior.LOOP);
+ }
+ } else {
+ this.focusTimeline.playReverse();
+ if (AnimationConfigurationManager.getInstance().isAnimationAllowed(
+ AnimationFacet.FOCUS_LOOP_ANIMATION, this.component)) {
+ this.focusLoopTimeline.cancel();
+ }
+ }
+ }
+
+ public boolean hasRunningTimelines() {
+ if (this.focusTimeline != null) {
+ TimelineState focusTimelineState = this.focusTimeline.getState();
+ if (focusTimelineState != TimelineState.IDLE)
+ return true;
+ }
+ if (this.focusLoopTimeline != null) {
+ TimelineState focusLoopTimelineState = this.focusLoopTimeline
+ .getState();
+ if (focusLoopTimelineState != TimelineState.IDLE)
+ return true;
+ }
+ if (this.iconGlowTracker.isPlaying()) {
+ return true;
+ }
+ if (this.transitionTimeline != null) {
+ TimelineState modelTransitionTimelineState = this.transitionTimeline
+ .getState();
+ if (modelTransitionTimelineState != TimelineState.IDLE)
+ return true;
+ }
+
+ return false;
+ }
+
+ public IconGlowTracker getIconGlowTracker() {
+ return iconGlowTracker;
+ }
+
+ public ModelStateInfo getModelStateInfo() {
+ return modelStateInfo;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/animation/TransitionAwareUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/animation/TransitionAwareUI.java
new file mode 100644
index 0000000..0110b1f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/animation/TransitionAwareUI.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.animation;
+
+import java.awt.event.MouseEvent;
+
+
+/**
+ * General interface for UIs that wish to provide transition effects on one of
+ * their components. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface TransitionAwareUI {
+ /**
+ * Checks whether the mouse position of the specified event lies inside the
+ * area of the component designated for transition effects.
+ *
+ * @param me
+ * Mouse event.
+ * @return <code>true</code> if the mouse position of the specified event
+ * lies inside the area of the component designated for transition
+ * effects, <code>false</code> otherwise.
+ */
+ public boolean isInside(MouseEvent me);
+
+ /**
+ * Returns the model for tracking the transitions.
+ *
+ * @return Model for tracking the transitions.
+ */
+ public StateTransitionTracker getTransitionTracker();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/BlendBiColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/BlendBiColorScheme.java
new file mode 100644
index 0000000..95e6ae0
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/BlendBiColorScheme.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.colorscheme;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.BaseColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Blended color scheme.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BlendBiColorScheme extends BaseColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private Color mainUltraLightColor;
+
+ /**
+ * The main extra-light color.
+ */
+ private Color mainExtraLightColor;
+
+ /**
+ * The main light color.
+ */
+ private Color mainLightColor;
+
+ /**
+ * The main medium color.
+ */
+ private Color mainMidColor;
+
+ /**
+ * The main dark color.
+ */
+ private Color mainDarkColor;
+
+ /**
+ * The main ultra-dark color.
+ */
+ private Color mainUltraDarkColor;
+
+ /**
+ * The foreground color.
+ */
+ private Color foregroundColor;
+
+ /**
+ * The main original color scheme.
+ */
+ private SubstanceColorScheme firstScheme;
+
+ /**
+ * The secondary original color scheme.
+ */
+ private SubstanceColorScheme secondScheme;
+
+ /**
+ * Likeness to the first scheme. Values close to 0.0 will create scheme that
+ * closely matches the second original scheme. Values close to 1.0 will
+ * create scheme that closely matches the second original scheme.
+ */
+ private double firstSchemeLikeness;
+
+ /**
+ * Creates a new blended color scheme.
+ *
+ * @param firstScheme
+ * The first original color scheme.
+ * @param secondScheme
+ * The second original color scheme.
+ * @param firstSchemeLikeness
+ * Likeness to the first scheme. Values close to 0.0 will create
+ * scheme that closely matches the second original scheme. Values
+ * close to 1.0 will create scheme that closely matches the
+ * second original scheme.
+ */
+ public BlendBiColorScheme(SubstanceColorScheme firstScheme,
+ SubstanceColorScheme secondScheme, double firstSchemeLikeness) {
+ super("Blended " + firstScheme.getDisplayName() + " & "
+ + secondScheme.getDisplayName() + " " + firstSchemeLikeness,
+ firstScheme.isDark());
+ this.firstScheme = firstScheme;
+ this.secondScheme = secondScheme;
+ this.firstSchemeLikeness = firstSchemeLikeness;
+ this.foregroundColor = new Color(SubstanceColorUtilities
+ .getInterpolatedRGB(firstScheme.getForegroundColor(),
+ secondScheme.getForegroundColor(), firstSchemeLikeness));
+ this.mainUltraDarkColor = new Color(SubstanceColorUtilities
+ .getInterpolatedRGB(firstScheme.getUltraDarkColor(),
+ secondScheme.getUltraDarkColor(), firstSchemeLikeness));
+ this.mainDarkColor = new Color(SubstanceColorUtilities
+ .getInterpolatedRGB(firstScheme.getDarkColor(), secondScheme
+ .getDarkColor(), firstSchemeLikeness));
+ this.mainMidColor = new Color(SubstanceColorUtilities
+ .getInterpolatedRGB(firstScheme.getMidColor(), secondScheme
+ .getMidColor(), firstSchemeLikeness));
+ this.mainLightColor = new Color(SubstanceColorUtilities
+ .getInterpolatedRGB(firstScheme.getLightColor(), secondScheme
+ .getLightColor(), firstSchemeLikeness));
+ this.mainExtraLightColor = new Color(SubstanceColorUtilities
+ .getInterpolatedRGB(firstScheme.getExtraLightColor(),
+ secondScheme.getExtraLightColor(), firstSchemeLikeness));
+ this.mainUltraLightColor = new Color(SubstanceColorUtilities
+ .getInterpolatedRGB(firstScheme.getUltraLightColor(),
+ secondScheme.getUltraLightColor(), firstSchemeLikeness));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return this.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return this.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return this.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return this.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return this.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return this.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return this.mainUltraDarkColor;
+ }
+
+ /**
+ * Returns the likeness to the first scheme.
+ *
+ * @return Likeness to the first scheme
+ */
+ public double getFirstSchemeLikeness() {
+ return this.firstSchemeLikeness;
+ }
+
+ /**
+ * Returns the main original color scheme.
+ *
+ * @return The main original color scheme.
+ */
+ public SubstanceColorScheme getFirstScheme() {
+ return this.firstScheme;
+ }
+
+ /**
+ * Returns the secondary original color scheme.
+ *
+ * @return The secondary original color scheme.
+ */
+ public SubstanceColorScheme getSecondScheme() {
+ return this.secondScheme;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/HueShiftColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/HueShiftColorScheme.java
new file mode 100644
index 0000000..8a2b97c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/HueShiftColorScheme.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.colorscheme;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.BaseColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Hue-shifted color scheme. A hue-shifted color scheme is a color scheme that
+ * is hue-shifted in HSB space.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class HueShiftColorScheme extends BaseColorScheme {
+ /**
+ * Hue-shift factor.
+ */
+ private double hueShiftFactor;
+
+ /**
+ * The main ultra-light color.
+ */
+ private Color mainUltraLightColor;
+
+ /**
+ * The main extra-light color.
+ */
+ private Color mainExtraLightColor;
+
+ /**
+ * The main light color.
+ */
+ private Color mainLightColor;
+
+ /**
+ * The main medium color.
+ */
+ private Color mainMidColor;
+
+ /**
+ * The main dark color.
+ */
+ private Color mainDarkColor;
+
+ /**
+ * The main ultra-dark color.
+ */
+ private Color mainUltraDarkColor;
+
+ /**
+ * The foreground color.
+ */
+ private Color foregroundColor;
+
+ /**
+ * The original color scheme.
+ */
+ private SubstanceColorScheme origScheme;
+
+ /**
+ * Creates a new hue-shifted color scheme.
+ *
+ * @param origScheme
+ * The original color scheme.
+ * @param hueShiftFactor
+ * Shift factor. Should be in -1.0-1.0 range.
+ */
+ public HueShiftColorScheme(SubstanceColorScheme origScheme,
+ double hueShiftFactor) {
+ super("Hue-shift " + origScheme.getDisplayName() + " "
+ + (int) (100 * hueShiftFactor) + "%", getResolver(origScheme));
+ this.hueShiftFactor = hueShiftFactor;
+ this.origScheme = origScheme;
+ this.foregroundColor = SubstanceColorUtilities.getHueShiftedColor(
+ origScheme.getForegroundColor(), this.hueShiftFactor / 2.0);
+ this.mainUltraDarkColor = SubstanceColorUtilities.getHueShiftedColor(
+ origScheme.getUltraDarkColor(), this.hueShiftFactor);
+ this.mainDarkColor = SubstanceColorUtilities.getHueShiftedColor(
+ origScheme.getDarkColor(), this.hueShiftFactor);
+ this.mainMidColor = SubstanceColorUtilities.getHueShiftedColor(
+ origScheme.getMidColor(), this.hueShiftFactor);
+ this.mainLightColor = SubstanceColorUtilities.getHueShiftedColor(
+ origScheme.getLightColor(), this.hueShiftFactor);
+ this.mainExtraLightColor = SubstanceColorUtilities.getHueShiftedColor(
+ origScheme.getExtraLightColor(), this.hueShiftFactor);
+ this.mainUltraLightColor = SubstanceColorUtilities.getHueShiftedColor(
+ origScheme.getUltraLightColor(), this.hueShiftFactor);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return this.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return this.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return this.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return this.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return this.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return this.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return this.mainUltraDarkColor;
+ }
+
+ /**
+ * Returns the original color scheme.
+ *
+ * @return The original color scheme.
+ */
+ public SubstanceColorScheme getOrigScheme() {
+ return this.origScheme;
+ }
+
+ /**
+ * Returns the hue-shift factor.
+ *
+ * @return Hue-shift factor.
+ */
+ public double getHueShiftFactor() {
+ return this.hueShiftFactor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/InvertedColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/InvertedColorScheme.java
new file mode 100644
index 0000000..d8c86ab
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/InvertedColorScheme.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.colorscheme;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.BaseColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Implementation of inverted color scheme. Inverted color scheme is based on
+ * some original color scheme, switching the dark colors by light colors and
+ * inverting the foreground color.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class InvertedColorScheme extends BaseColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private Color mainUltraLightColor;
+
+ /**
+ * The main extra-light color.
+ */
+ private Color mainExtraLightColor;
+
+ /**
+ * The main light color.
+ */
+ private Color mainLightColor;
+
+ /**
+ * The main medium color.
+ */
+ private Color mainMidColor;
+
+ /**
+ * The main dark color.
+ */
+ private Color mainDarkColor;
+
+ /**
+ * The main ultra-dark color.
+ */
+ private Color mainUltraDarkColor;
+
+ /**
+ * The foreground color.
+ */
+ private Color foregroundColor;
+
+ /**
+ * The original color scheme.
+ */
+ private SubstanceColorScheme origScheme;
+
+ /**
+ * Creates a new inverted scheme.
+ *
+ * @param origScheme
+ * The original color scheme.
+ */
+ public InvertedColorScheme(SubstanceColorScheme origScheme) {
+ super("Inverted " + origScheme.getDisplayName(), getResolver(origScheme).invert());
+ this.origScheme = origScheme;
+ this.foregroundColor = SubstanceColorUtilities.invertColor(origScheme
+ .getForegroundColor());
+ this.mainUltraDarkColor = SubstanceColorUtilities
+ .invertColor(origScheme.getUltraLightColor());
+ this.mainDarkColor = SubstanceColorUtilities.invertColor(origScheme
+ .getExtraLightColor());
+ this.mainMidColor = SubstanceColorUtilities.invertColor(origScheme
+ .getLightColor());
+ this.mainLightColor = SubstanceColorUtilities.invertColor(origScheme
+ .getMidColor());
+ this.mainExtraLightColor = SubstanceColorUtilities
+ .invertColor(origScheme.getDarkColor());
+ this.mainUltraLightColor = SubstanceColorUtilities
+ .invertColor(origScheme.getUltraDarkColor());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return this.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return this.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return this.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return this.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return this.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return this.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return this.mainUltraDarkColor;
+ }
+
+ /**
+ * Returns the original color scheme.
+ *
+ * @return The original color scheme.
+ */
+ public SubstanceColorScheme getOrigScheme() {
+ return this.origScheme;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/NegatedColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/NegatedColorScheme.java
new file mode 100644
index 0000000..3156d0b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/NegatedColorScheme.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.colorscheme;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.BaseColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Implementation of negated color scheme. Negated color scheme is based on some
+ * original color scheme, negating all the colors.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class NegatedColorScheme extends BaseColorScheme {
+ /**
+ * The main ultra-light color.
+ */
+ private Color mainUltraLightColor;
+
+ /**
+ * The main extra-light color.
+ */
+ private Color mainExtraLightColor;
+
+ /**
+ * The main light color.
+ */
+ private Color mainLightColor;
+
+ /**
+ * The main medium color.
+ */
+ private Color mainMidColor;
+
+ /**
+ * The main dark color.
+ */
+ private Color mainDarkColor;
+
+ /**
+ * The main ultra-dark color.
+ */
+ private Color mainUltraDarkColor;
+
+ /**
+ * The foreground color.
+ */
+ private Color foregroundColor;
+
+ /**
+ * The original color scheme.
+ */
+ private SubstanceColorScheme origScheme;
+
+ /**
+ * Creates a new inverted scheme.
+ *
+ * @param origScheme
+ * The original color scheme.
+ */
+ public NegatedColorScheme(SubstanceColorScheme origScheme) {
+ super("Negated " + origScheme.getDisplayName(), getResolver(origScheme).invert());
+ this.origScheme = origScheme;
+
+ this.foregroundColor = SubstanceColorUtilities.invertColor(origScheme
+ .getForegroundColor());
+
+ this.mainUltraDarkColor = SubstanceColorUtilities
+ .invertColor(origScheme.getUltraDarkColor());
+ this.mainDarkColor = SubstanceColorUtilities.invertColor(origScheme
+ .getDarkColor());
+ this.mainMidColor = SubstanceColorUtilities.invertColor(origScheme
+ .getMidColor());
+ this.mainLightColor = SubstanceColorUtilities.invertColor(origScheme
+ .getLightColor());
+ this.mainExtraLightColor = SubstanceColorUtilities
+ .invertColor(origScheme.getExtraLightColor());
+ this.mainUltraLightColor = SubstanceColorUtilities
+ .invertColor(origScheme.getUltraLightColor());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return this.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return this.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return this.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return this.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return this.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return this.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return this.mainUltraDarkColor;
+ }
+
+ /**
+ * Returns the original color scheme.
+ *
+ * @return The original color scheme.
+ */
+ public SubstanceColorScheme getOrigScheme() {
+ return this.origScheme;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/SaturatedColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/SaturatedColorScheme.java
new file mode 100644
index 0000000..edcf009
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/SaturatedColorScheme.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.colorscheme;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.BaseColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Saturated color scheme. A saturated color scheme is a color scheme that is
+ * saturated / desaturated (using HSV).
+ *
+ * @author Kirill Grouchnikov
+ * @see ShiftColorScheme
+ */
+public class SaturatedColorScheme extends BaseColorScheme {
+ /**
+ * Shift factor.
+ */
+ private double saturationFactor;
+
+ /**
+ * The main ultra-light color.
+ */
+ private Color mainUltraLightColor;
+
+ /**
+ * The main extra-light color.
+ */
+ private Color mainExtraLightColor;
+
+ /**
+ * The main light color.
+ */
+ private Color mainLightColor;
+
+ /**
+ * The main medium color.
+ */
+ private Color mainMidColor;
+
+ /**
+ * The main dark color.
+ */
+ private Color mainDarkColor;
+
+ /**
+ * The main ultra-dark color.
+ */
+ private Color mainUltraDarkColor;
+
+ /**
+ * The foreground color.
+ */
+ private Color foregroundColor;
+
+ /**
+ * The original color scheme.
+ */
+ private SubstanceColorScheme origScheme;
+
+ /**
+ * Creates a new saturated color scheme.
+ *
+ * @param origScheme
+ * The original color scheme.
+ * @param saturationFactor
+ * Saturation factor. Should be in -1.0..1.0 range.
+ */
+ public SaturatedColorScheme(SubstanceColorScheme origScheme,
+ double saturationFactor) {
+ super("Saturated (" + (int) (100 * saturationFactor) + "%) "
+ + origScheme.getDisplayName(), getResolver(origScheme));
+ this.saturationFactor = saturationFactor;
+ this.origScheme = origScheme;
+ this.foregroundColor = origScheme.getForegroundColor();
+ this.mainUltraDarkColor = SubstanceColorUtilities.getSaturatedColor(
+ origScheme.getUltraDarkColor(), saturationFactor);
+ this.mainDarkColor = SubstanceColorUtilities.getSaturatedColor(
+ origScheme.getDarkColor(), saturationFactor);
+ this.mainMidColor = SubstanceColorUtilities.getSaturatedColor(
+ origScheme.getMidColor(), saturationFactor);
+ this.mainLightColor = SubstanceColorUtilities.getSaturatedColor(
+ origScheme.getLightColor(), saturationFactor);
+ this.mainExtraLightColor = SubstanceColorUtilities.getSaturatedColor(
+ origScheme.getExtraLightColor(), saturationFactor);
+ this.mainUltraLightColor = SubstanceColorUtilities.getSaturatedColor(
+ origScheme.getUltraLightColor(), saturationFactor);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return this.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return this.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return this.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return this.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return this.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return this.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return this.mainUltraDarkColor;
+ }
+
+ /**
+ * Returns the original color scheme.
+ *
+ * @return The original color scheme.
+ */
+ public SubstanceColorScheme getOrigScheme() {
+ return this.origScheme;
+ }
+
+ /**
+ * Returns the saturation factor.
+ *
+ * @return Saturation factor.
+ */
+ public double getSaturationFactor() {
+ return this.saturationFactor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/ShadeColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/ShadeColorScheme.java
new file mode 100644
index 0000000..15abd42
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/ShadeColorScheme.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.colorscheme;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+
+/**
+ * Shaded color scheme. A shaded color scheme is a color scheme that is shifted
+ * to <b>black</b> color.
+ *
+ * @author Kirill Grouchnikov
+ * @see ShiftColorScheme
+ */
+public class ShadeColorScheme extends ShiftColorScheme {
+ /**
+ * Creates a new shaded color scheme.
+ *
+ * @param origColorScheme
+ * The original color scheme.
+ * @param shadeFactor
+ * The shade factor. Should be in 0.0-1.0 range.
+ */
+ public ShadeColorScheme(SubstanceColorScheme origColorScheme,
+ double shadeFactor) {
+ super(origColorScheme, Color.black, shadeFactor);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/ShiftColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/ShiftColorScheme.java
new file mode 100644
index 0000000..f21d244
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/ShiftColorScheme.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.colorscheme;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.BaseColorScheme;
+import org.pushingpixels.substance.internal.utils.HashMapKey;
+import org.pushingpixels.substance.internal.utils.LazyResettableHashMap;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Base class for shifted color schemes. A shifted color scheme is based on some
+ * original color scheme, a shift color and a shift factor. All colors of the
+ * original color scheme are shifted towards the shift color based on the shift
+ * factor. The closer the shift factor value is to 1.0, the closer the colors of
+ * the shifted color scheme will be to the shift color.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ShiftColorScheme extends BaseColorScheme {
+ /**
+ * Shift factor for background colors.
+ */
+ private double backgroundShiftFactor;
+
+ /**
+ * Shift factor for foreground colors.
+ */
+ private double foregroundShiftFactor;
+
+ /**
+ * Shift color for background colors.
+ */
+ private Color backgroundShiftColor;
+
+ /**
+ * Shift color for foreground color.
+ */
+ private Color foregroundShiftColor;
+
+ /**
+ * The main ultra-light color.
+ */
+ private Color mainUltraLightColor;
+
+ /**
+ * The main extra-light color.
+ */
+ private Color mainExtraLightColor;
+
+ /**
+ * The main light color.
+ */
+ private Color mainLightColor;
+
+ /**
+ * The main medium color.
+ */
+ private Color mainMidColor;
+
+ /**
+ * The main dark color.
+ */
+ private Color mainDarkColor;
+
+ /**
+ * The main ultra-dark color.
+ */
+ private Color mainUltraDarkColor;
+
+ /**
+ * The foreground color.
+ */
+ private Color foregroundColor;
+
+ /**
+ * The original color scheme.
+ */
+ private SubstanceColorScheme origScheme;
+
+ /**
+ * Cache of shifted schemes.
+ */
+ protected final static LazyResettableHashMap<SubstanceColorScheme> shiftedCache = new LazyResettableHashMap<SubstanceColorScheme>(
+ "ShiftColorScheme.shiftedSchemes");
+
+ /**
+ * Creates a new shifted color scheme.
+ *
+ * @param origScheme
+ * The original color scheme.
+ * @param shiftColor
+ * Shift color for the colors.
+ * @param shiftFactor
+ * Shift factor for the colors. Should be in 0.0-1.0 range.
+ */
+ public ShiftColorScheme(SubstanceColorScheme origScheme, Color shiftColor,
+ double shiftFactor) {
+ this(origScheme, shiftColor, shiftFactor, shiftColor,
+ shiftFactor / 2.0, false);
+ }
+
+ /**
+ * Creates a new shifted color scheme.
+ *
+ * @param origScheme
+ * The original color scheme.
+ * @param backgroundShiftColor
+ * Shift color for the background colors.
+ * @param backgroundShiftFactor
+ * Shift factor for the background colors. Should be in 0.0-1.0
+ * range.
+ * @param foregroundShiftColor
+ * Shift color for the foreground colors.
+ * @param foregroundShiftFactor
+ * Shift factor for the foreground colors. Should be in 0.0-1.0
+ * range.
+ * @param shiftByBrightness
+ * If <code>true</code>, the shift will account for the
+ * brightness of the original color scheme colors.
+ */
+ public ShiftColorScheme(SubstanceColorScheme origScheme,
+ Color backgroundShiftColor, double backgroundShiftFactor,
+ Color foregroundShiftColor, double foregroundShiftFactor,
+ boolean shiftByBrightness) {
+ super("Shift " + origScheme.getDisplayName() + " to backgr ["
+ + backgroundShiftColor + "] "
+ + (int) (100 * backgroundShiftFactor) + "%, foregr ["
+ + foregroundShiftColor + "]"
+ + (int) (100 * foregroundShiftFactor) + "%", getResolver(origScheme));
+ this.backgroundShiftColor = backgroundShiftColor;
+ this.backgroundShiftFactor = backgroundShiftFactor;
+ this.foregroundShiftColor = foregroundShiftColor;
+ this.foregroundShiftFactor = foregroundShiftFactor;
+ this.origScheme = origScheme;
+ this.foregroundColor = (this.foregroundShiftColor != null) ? SubstanceColorUtilities
+ .getInterpolatedColor(this.foregroundShiftColor, origScheme
+ .getForegroundColor(), this.foregroundShiftFactor)
+ : origScheme.getForegroundColor();
+ shiftByBrightness = shiftByBrightness
+ && (this.backgroundShiftColor != null);
+ Color ultraDarkToShiftTo = shiftByBrightness ? SubstanceColorUtilities
+ .deriveByBrightness(this.backgroundShiftColor, origScheme
+ .getUltraDarkColor()) : this.backgroundShiftColor;
+ this.mainUltraDarkColor = (this.backgroundShiftColor != null) ? SubstanceColorUtilities
+ .getInterpolatedColor(ultraDarkToShiftTo, origScheme
+ .getUltraDarkColor(), this.backgroundShiftFactor)
+ : origScheme.getUltraDarkColor();
+ Color darkToShiftTo = shiftByBrightness ? SubstanceColorUtilities
+ .deriveByBrightness(this.backgroundShiftColor, origScheme
+ .getDarkColor()) : this.backgroundShiftColor;
+ this.mainDarkColor = (this.backgroundShiftColor != null) ? SubstanceColorUtilities
+ .getInterpolatedColor(darkToShiftTo, origScheme.getDarkColor(),
+ this.backgroundShiftFactor)
+ : origScheme.getDarkColor();
+ Color midToShiftTo = shiftByBrightness ? SubstanceColorUtilities
+ .deriveByBrightness(this.backgroundShiftColor, origScheme
+ .getMidColor()) : this.backgroundShiftColor;
+ this.mainMidColor = (this.backgroundShiftColor != null) ? SubstanceColorUtilities
+ .getInterpolatedColor(midToShiftTo, origScheme.getMidColor(),
+ this.backgroundShiftFactor)
+ : origScheme.getMidColor();
+ Color lightToShiftTo = shiftByBrightness ? SubstanceColorUtilities
+ .deriveByBrightness(this.backgroundShiftColor, origScheme
+ .getLightColor()) : this.backgroundShiftColor;
+ this.mainLightColor = (this.backgroundShiftColor != null) ? SubstanceColorUtilities
+ .getInterpolatedColor(lightToShiftTo, origScheme
+ .getLightColor(), this.backgroundShiftFactor)
+ : origScheme.getLightColor();
+ Color extraLightToShiftTo = shiftByBrightness ? SubstanceColorUtilities
+ .deriveByBrightness(this.backgroundShiftColor, origScheme
+ .getExtraLightColor()) : this.backgroundShiftColor;
+ this.mainExtraLightColor = (this.backgroundShiftColor != null) ? SubstanceColorUtilities
+ .getInterpolatedColor(extraLightToShiftTo, origScheme
+ .getExtraLightColor(), this.backgroundShiftFactor)
+ : origScheme.getExtraLightColor();
+ Color ultraLightToShiftTo = shiftByBrightness ? SubstanceColorUtilities
+ .deriveByBrightness(this.backgroundShiftColor, origScheme
+ .getUltraLightColor()) : this.backgroundShiftColor;
+ this.mainUltraLightColor = (this.backgroundShiftColor != null) ? SubstanceColorUtilities
+ .getInterpolatedColor(ultraLightToShiftTo, origScheme
+ .getUltraLightColor(), this.backgroundShiftFactor)
+ : origScheme.getUltraLightColor();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return this.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return this.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return this.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return this.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return this.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return this.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return this.mainUltraDarkColor;
+ }
+
+ /**
+ * Returns the original color scheme.
+ *
+ * @return The original color scheme.
+ */
+ public SubstanceColorScheme getOrigScheme() {
+ return this.origScheme;
+ }
+
+ /**
+ * Returns the shift factor.
+ *
+ * @return Shift factor.
+ */
+ public double getShiftFactor() {
+ return this.backgroundShiftFactor;
+ }
+
+ /**
+ * Returns a shifted color scheme. This method is for internal use only.
+ *
+ * @param orig
+ * The original color scheme.
+ * @param backgroundShiftColor
+ * Shift color for the background color scheme colors. May be
+ * <code>null</code> - in this case, the background color scheme
+ * colors will not be shifted.
+ * @param backgroundShiftFactor
+ * Shift factor for the background color scheme colors. If the
+ * shift color for the background color scheme colors is
+ * <code>null</code>, this value is ignored.
+ * @param foregroundShiftColor
+ * Shift color for the foreground color scheme colors. May be
+ * <code>null</code> - in this case, the foreground color scheme
+ * colors will not be shifted.
+ * @param foregroundShiftFactor
+ * Shift factor for the foreground color scheme colors. If the
+ * shift color for the foreground color scheme colors is
+ * <code>null</code>, this value is ignored.
+ * @return Shifted scheme.
+ */
+ public static SubstanceColorScheme getShiftedScheme(
+ SubstanceColorScheme orig, Color backgroundShiftColor,
+ double backgroundShiftFactor, Color foregroundShiftColor,
+ double foregroundShiftFactor) {
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(orig
+ .getDisplayName(), backgroundShiftColor == null ? ""
+ : backgroundShiftColor.getRGB(), backgroundShiftFactor,
+ foregroundShiftColor == null ? "" : foregroundShiftColor
+ .getRGB(), foregroundShiftFactor);
+ SubstanceColorScheme result = shiftedCache.get(key);
+ if (result == null) {
+ result = orig.shift(backgroundShiftColor, backgroundShiftFactor,
+ foregroundShiftColor, foregroundShiftFactor);
+ shiftedCache.put(key, result);
+ }
+ return result;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/TintColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/TintColorScheme.java
new file mode 100644
index 0000000..eee8bb8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/TintColorScheme.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.colorscheme;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+
+/**
+ * Tinted color scheme. A tinted color scheme is a color scheme that is shifted
+ * to <b>white</b> color.
+ *
+ * @author Kirill Grouchnikov
+ * @see ShiftColorScheme
+ */
+public class TintColorScheme extends ShiftColorScheme {
+ /**
+ * Creates a new tinted color scheme.
+ *
+ * @param origColorScheme
+ * The original color scheme.
+ * @param tintFactor
+ * The tint factor. Should be in 0.0-1.0 range.
+ */
+ public TintColorScheme(SubstanceColorScheme origColorScheme,
+ double tintFactor) {
+ super(origColorScheme, Color.white, tintFactor);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/ToneColorScheme.java b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/ToneColorScheme.java
new file mode 100644
index 0000000..59b3b39
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/colorscheme/ToneColorScheme.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.colorscheme;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+
+/**
+ * Toned color scheme. A toned color scheme is a color scheme that is shifted to
+ * <b>gray</b> color.
+ *
+ * @author Kirill Grouchnikov
+ * @see ShiftColorScheme
+ */
+public class ToneColorScheme extends ShiftColorScheme {
+ /**
+ * Creates a new toned color scheme.
+ *
+ * @param origColorScheme
+ * The original color scheme.
+ * @param toneFactor
+ * The tone factor. Should be in 0.0-1.0 range.
+ */
+ public ToneColorScheme(SubstanceColorScheme origColorScheme,
+ double toneFactor) {
+ super(origColorScheme, Color.gray, toneFactor);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/FontSizeHints.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/FontSizeHints.java
new file mode 100644
index 0000000..1709dea
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/FontSizeHints.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2001-2005 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.substance.internal.contrib.jgoodies.looks;
+
+import org.pushingpixels.lafwidget.utils.LookUtils;
+
+/**
+ * Describes font size hints used by the JGoodies Windows look&feel; future
+ * implementations of the Plastic l&f may use the same hints.
+ * <p>
+ *
+ * These hints are only applied if the dialog font is <em>Tahoma</em>, which is
+ * the default font on the majority of Windows desktops. The hints apply a size
+ * delta to increase or decrease the given system font size.
+ * <p>
+ *
+ * NOTE: This is work in progress and will probably change in the next release,
+ * to better reflect the font choice in the J2SE 1.4.".
+ *
+ * @author Karsten Lentzsch
+ *
+ * @see Options#setGlobalFontSizeHints(FontSizeHints)
+ */
+public final class FontSizeHints {
+
+ public static final FontSizeHints LARGE = new FontSizeHints(12, 12, 14, 14);
+ public static final FontSizeHints SYSTEM = new FontSizeHints(11, 11, 14, 14);
+ public static final FontSizeHints MIXED2 = new FontSizeHints(11, 11, 14, 13);
+ public static final FontSizeHints MIXED = new FontSizeHints(11, 11, 14, 12);
+ public static final FontSizeHints SMALL = new FontSizeHints(11, 11, 12, 12);
+ public static final FontSizeHints FIXED = new FontSizeHints(12, 12, 12, 12);
+
+ public static final FontSizeHints DEFAULT = SYSTEM;
+
+ private final int loResMenuFontSize;
+ private final int loResControlFontSize;
+ private final int hiResMenuFontSize;
+ private final int hiResControlFontSize;
+
+ /**
+ * Constructs <code>FontSizeHints</code> for the specified menu and control
+ * fonts, both for low and high resolution environments.
+ *
+ * @param loResMenuFontSize
+ * the size of the menu font in low resolution
+ * @param loResControlFontSize
+ * the size of the control font in low resolution
+ * @param hiResMenuFontSize
+ * the size of the menu font in low resolution
+ * @param hiResControlFontSize
+ * the size of the control font in low resolution
+ */
+ public FontSizeHints(int loResMenuFontSize, int loResControlFontSize,
+ int hiResMenuFontSize, int hiResControlFontSize) {
+ this.loResMenuFontSize = loResMenuFontSize;
+ this.loResControlFontSize = loResControlFontSize;
+ this.hiResMenuFontSize = hiResMenuFontSize;
+ this.hiResControlFontSize = hiResControlFontSize;
+ }
+
+ /**
+ * Returns the low resolution menu font size.
+ *
+ * @return the size of the menu font in low resolution mode
+ */
+ public int loResMenuFontSize() {
+ return loResMenuFontSize;
+ }
+
+ /**
+ * Returns the low resolution control font size.
+ *
+ * @return the size of the control font in low resolution mode
+ */
+ public int loResControlFontSize() {
+ return loResControlFontSize;
+ }
+
+ /**
+ * Returns the high resolution menu font size.
+ *
+ * @return the size of the menu font in high resolution mode
+ */
+ public int hiResMenuFontSize() {
+ return hiResMenuFontSize;
+ }
+
+ /**
+ * Returns the high resolution control font size.
+ *
+ * @return the size of the control font in high resolution mode
+ */
+ public int hiResControlFontSize() {
+ return hiResControlFontSize;
+ }
+
+ /**
+ * Returns the menu font size.
+ *
+ * @return the size of the menu font in the current resolution
+ */
+ public int menuFontSize() {
+ return LookUtils.IS_LOW_RESOLUTION ? loResMenuFontSize
+ : hiResMenuFontSize();
+ }
+
+ /**
+ * Returns the control font size.
+ *
+ * @return the size of the control font in the current resolution
+ */
+ public int controlFontSize() {
+ return LookUtils.IS_LOW_RESOLUTION ? loResControlFontSize
+ : hiResControlFontSize();
+ }
+
+ /**
+ * Returns the delta between the system menu font size and our menu font
+ * size hint.
+ *
+ * @return the delta between the system menu font size and our menu font
+ * size hint
+ */
+ public float menuFontSizeDelta() {
+ return menuFontSize() - SYSTEM.menuFontSize();
+ }
+
+ /**
+ * Returns the delta between system control font size and our control font
+ * size hint.
+ *
+ * @return the delta between the system control font size and our control
+ * font size hint
+ */
+ public float controlFontSizeDelta() {
+ return controlFontSize() - SYSTEM.controlFontSize();
+ }
+
+ /**
+ * Looksup and returns the <code>FontSizeHints</code> for the specified
+ * name.
+ *
+ * @param name
+ * the name of the FontSizeHints object
+ * @return the associated FontSizeHints object
+ */
+ public static FontSizeHints valueOf(String name) {
+ if (name.equalsIgnoreCase("LARGE"))
+ return LARGE;
+ else if (name.equalsIgnoreCase("SYSTEM"))
+ return SYSTEM;
+ else if (name.equalsIgnoreCase("MIXED"))
+ return MIXED;
+ else if (name.equalsIgnoreCase("SMALL"))
+ return SMALL;
+ else if (name.equalsIgnoreCase("FIXED"))
+ return FIXED;
+ else
+ throw new IllegalArgumentException("Unknown font size hints name: "
+ + name);
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/Options.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/Options.java
new file mode 100644
index 0000000..0b807d6
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/Options.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright (c) 2001-2005 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.substance.internal.contrib.jgoodies.looks;
+
+import java.awt.Dimension;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.UIManager;
+
+import org.pushingpixels.lafwidget.utils.LookUtils;
+import org.pushingpixels.substance.internal.contrib.jgoodies.looks.common.ShadowPopup;
+
+/**
+ * Provides access to several optional properties for the JGoodies L&Fs,
+ * either by a key to the <code>UIDefaults</code> table or via a method or both.
+ *
+ * @author Karsten Lentzsch
+ */
+
+public final class Options {
+
+ // Look & Feel Names ****************************************************
+
+ public static final String PLASTIC_NAME = "com.jgoodies.looks.plastic.PlasticLookAndFeel";
+
+ public static final String PLASTIC3D_NAME = "com.jgoodies.looks.plastic.Plastic3DLookAndFeel";
+
+ public static final String PLASTICXP_NAME = "com.jgoodies.looks.plastic.PlasticXPLookAndFeel";
+
+ public static final String JGOODIES_WINDOWS_NAME = "com.jgoodies.looks.windows.WindowsLookAndFeel";
+
+ /**
+ * This outdated constant will be removed in the Looks version 1.4.
+ *
+ * @deprecated Replaced by {@link #JGOODIES_WINDOWS_NAME}.
+ */
+ @Deprecated
+ public static final String EXT_WINDOWS_NAME = JGOODIES_WINDOWS_NAME;
+
+ public static final String DEFAULT_LOOK_NAME = PLASTIC3D_NAME;
+
+ /**
+ * Holds a Map that enables the look&feel replacement mechanism to
+ * replace one look by another. Maps the original class names to their
+ * replacement class names.
+ */
+ private static final Map LAF_REPLACEMENTS;
+ static {
+ LAF_REPLACEMENTS = new HashMap();
+ initializeDefaultReplacements();
+ }
+
+ // Keys for Overriding Font Settings ************************************
+
+ public static final String MENU_FONT_KEY = "jgoodies.menuFont";
+
+ public static final String CONTROL_FONT_KEY = "jgoodies.controlFont";
+
+ public static final String FONT_SIZE_HINTS_KEY = "jgoodies.fontSizeHints";
+
+ public static final String USE_SYSTEM_FONTS_KEY = "swing.useSystemFontSettings";
+
+ public static final String USE_SYSTEM_FONTS_APP_KEY = "Application.useSystemFontSettings";
+
+ // Optional Global User Properties **************************************
+
+ public static final String DEFAULT_ICON_SIZE_KEY = "jgoodies.defaultIconSize";
+
+ public static final String USE_NARROW_BUTTONS_KEY = "jgoodies.useNarrowButtons";
+
+ public static final String TAB_ICONS_ENABLED_KEY = "jgoodies.tabIconsEnabled";
+
+ public static final String POPUP_DROP_SHADOW_ENABLED_KEY = "jgoodies.popupDropShadowEnabled";
+
+ // Optional Client Properties *******************************************
+
+ /**
+ * Hint that the button margin should be narrow.
+ */
+ public static final String IS_NARROW_KEY = "jgoodies.isNarrow";
+
+ /**
+ * Hint that the scroll pane border should be etched.
+ */
+ public static final String IS_ETCHED_KEY = "jgoodies.isEtched";
+
+ /**
+ * Hint for the style: Single or Both, see <code>HeaderStyle</code>.
+ */
+ public static final String HEADER_STYLE_KEY = "jgoodies.headerStyle";
+
+ /**
+ * Hint that the menu items in the menu have no icons.
+ */
+ public static final String NO_ICONS_KEY = "jgoodies.noIcons";
+
+ /**
+ * A client property key for JTrees. Used with the angled and none style
+ * values.
+ */
+ public static final String TREE_LINE_STYLE_KEY = "JTree.lineStyle";
+
+ /**
+ * A client property value for JTrees that indicates that lines shall be
+ * drawn.
+ */
+ public static final String TREE_LINE_STYLE_ANGLED_VALUE = "Angled";
+
+ /**
+ * A client property value for JTrees that indicates that lines shall be
+ * hidden.
+ */
+ public static final String TREE_LINE_STYLE_NONE_VALUE = "None";
+
+ /**
+ * A client property key for JTabbedPanes that indicates that no content
+ * border shall be painted. Supported by the Plastic look&feel family.
+ * This effect will be achieved also if the EMBEDDED property is true.
+ */
+ public static final String NO_CONTENT_BORDER_KEY = "jgoodies.noContentBorder";
+
+ /**
+ * A client property key for JTabbedPanes that indicates that tabs are
+ * painted with a special embedded appearance. Supported by the Plastic
+ * look&feel family. This effect will be achieved also if the EMBEDDED
+ * property is true.
+ */
+ public static final String EMBEDDED_TABS_KEY = "jgoodies.embeddedTabs";
+
+ // System Settings ********************************************************
+
+ /**
+ * Holds the Boolean system property value for the tab icon enablement, or
+ * null, if it has not been set. If this property has been set, we log a
+ * message about the choosen value.
+ *
+ * @see #isTabIconsEnabled()
+ */
+ private static final Boolean TAB_ICONS_ENABLED_SYSTEM_VALUE = LookUtils
+ .getBooleanSystemProperty(TAB_ICONS_ENABLED_KEY,
+ "Icons in tabbed panes");
+
+ /**
+ * Holds the Boolean system property value for the popup drop shadow
+ * enablement, or null, if it has not been set. If this property has been
+ * set, we log a message about the choosen value.
+ * <p>
+ *
+ * This property just set the feature's enablement, not its actual
+ * activation. For example, drop shadows are always inactive on the Mac OS
+ * X, because this platform already provides shadows. The activation is
+ * requested in <code>#isPopupDropShadowActive</code>.
+ *
+ * @see #isPopupDropShadowEnabled()
+ * @see #isPopupDropShadowActive()
+ */
+ private static final Boolean POPUP_DROP_SHADOW_ENABLED_SYSTEM_VALUE = LookUtils
+ .getBooleanSystemProperty(POPUP_DROP_SHADOW_ENABLED_KEY,
+ "Popup drop shadows");
+
+ // Private ****************************************************************
+
+ private static final Dimension DEFAULT_ICON_SIZE = new Dimension(20, 20);
+
+ private Options() {
+ // Override default constructor; prevents instantiation.
+ }
+
+ // Accessing Options ******************************************************
+
+ /**
+ * Returns whether a hint is set in the UIManager that indicates, that a
+ * look&feel may use the native system fonts.
+ *
+ * @return true if the UIManager indicates that system fonts shall be used
+ * @see #setUseSystemFonts(boolean)
+ */
+ public static boolean getUseSystemFonts() {
+ return UIManager.get(USE_SYSTEM_FONTS_APP_KEY).equals(Boolean.TRUE);
+ }
+
+ /**
+ * Sets a value in the UIManager to indicate, that a look&feel may use
+ * the native system fonts.
+ *
+ * @param useSystemFonts
+ * true to enable system fonts in the UIManager
+ * @see #getUseSystemFonts()
+ */
+ public static void setUseSystemFonts(boolean useSystemFonts) {
+ UIManager
+ .put(USE_SYSTEM_FONTS_APP_KEY, useSystemFonts);
+ }
+
+ /**
+ * Returns the default icon size that is used in menus, menu items and
+ * toolbars. Menu items that have no icon set are aligned using the default
+ * icon dimensions.
+ *
+ * @return the dimension of the default icon
+ * @see #setDefaultIconSize(Dimension)
+ */
+ public static Dimension getDefaultIconSize() {
+ Dimension size = UIManager.getDimension(DEFAULT_ICON_SIZE_KEY);
+ return size == null ? DEFAULT_ICON_SIZE : size;
+ }
+
+ /**
+ * Sets the default icon size.
+ *
+ * @param defaultIconSize
+ * the default icon size to set
+ * @see #getDefaultIconSize()
+ */
+ public static void setDefaultIconSize(Dimension defaultIconSize) {
+ UIManager.put(DEFAULT_ICON_SIZE_KEY, defaultIconSize);
+ }
+
+ /**
+ * Returns the global <code>FontSizeHints</code> that can be overriden by a
+ * look-specific setting.
+ *
+ * @return the gobally used FontSizeHints object
+ * @see #setGlobalFontSizeHints(FontSizeHints)
+ */
+ public static FontSizeHints getGlobalFontSizeHints() {
+ Object value = UIManager.get(FONT_SIZE_HINTS_KEY);
+ if (value != null)
+ return (FontSizeHints) value;
+
+ String name = LookUtils.getSystemProperty(FONT_SIZE_HINTS_KEY, "");
+ try {
+ return FontSizeHints.valueOf(name);
+ } catch (IllegalArgumentException e) {
+ return FontSizeHints.DEFAULT;
+ }
+ }
+
+ /**
+ * Sets the global <code>FontSizeHints</code>.
+ *
+ * @param hints
+ * the FontSizeHints object to be used globally
+ * @see #getGlobalFontSizeHints()
+ */
+ public static void setGlobalFontSizeHints(FontSizeHints hints) {
+ UIManager.put(FONT_SIZE_HINTS_KEY, hints);
+ }
+
+ /**
+ * Checks and answers if we shall use narrow button margins of 4 pixels.
+ * Sun's L&F implementations use a much wider button margin of 14
+ * pixels, which leads to good button minimum width in the typical case.
+ * <p>
+ *
+ * Using narrow button margins can potentially cause compatibility issues,
+ * so this feature must be switched on programmatically.
+ * <p>
+ *
+ * If you use narrow margin, you should take care of minimum button width,
+ * either by the layout management or appropriate ButtonUI minimum widths.
+ *
+ * @return true if all buttons shall use narrow margins
+ * @see #setUseNarrowButtons(boolean)
+ */
+ public static boolean getUseNarrowButtons() {
+ return UIManager.getBoolean(USE_NARROW_BUTTONS_KEY);
+ }
+
+ /**
+ * Sets if we use narrow or standard button margins.
+ *
+ * @param b
+ * true to use narrow button margins globally
+ * @see #getUseNarrowButtons()
+ */
+ public static void setUseNarrowButtons(boolean b) {
+ UIManager.put(USE_NARROW_BUTTONS_KEY, b);
+ }
+
+ /**
+ * Checks and answers if we shall use icons in JTabbedPanes. By default, tab
+ * icons are enabled. If the user has set a system property, we log a
+ * message about the choosen style.
+ *
+ * @return true if icons in tabbed panes are enabled, false if disabled
+ * @see #setTabIconsEnabled(boolean)
+ */
+ public static boolean isTabIconsEnabled() {
+ return TAB_ICONS_ENABLED_SYSTEM_VALUE == null ? !Boolean.FALSE
+ .equals(UIManager.get(TAB_ICONS_ENABLED_KEY))
+ : TAB_ICONS_ENABLED_SYSTEM_VALUE;
+ }
+
+ /**
+ * Enables or disables the use of icons in JTabbedPanes.
+ *
+ * @param b
+ * true to enable icons in tabbed panes, false to disable them
+ * @see #isTabIconsEnabled()
+ */
+ public static void setTabIconsEnabled(boolean b) {
+ UIManager.put(TAB_ICONS_ENABLED_KEY, b);
+ }
+
+ /**
+ * Checks and answers whether popup drop shadows are active. This feature
+ * shall be inactive with toolkits that use native drop shadows, such as
+ * Aqua on the Mac OS X. It is also inactive if the ShadowPopup cannot
+ * snapshot the desktop background (due to security and AWT exceptions).
+ * Otherwise the feature's enablement state is returned.
+ * <p>
+ *
+ * Currently only the Mac OS X is detected as platform where the toolkit
+ * uses native drop shadows.
+ *
+ * @return true if drop shadows are active, false if inactive
+ *
+ * @see #isPopupDropShadowEnabled()
+ * @see #setPopupDropShadowEnabled(boolean)
+ */
+ public static boolean isPopupDropShadowActive() {
+ return !LookUtils.getToolkitUsesNativeDropShadows()
+ && ShadowPopup.canSnapshot() && isPopupDropShadowEnabled();
+ }
+
+ /**
+ * Checks and answers whether the optional drop shadows for PopupMenus are
+ * enabled or disabled.
+ *
+ * @return true if drop shadows are enabled, false if disabled
+ *
+ * @see #isPopupDropShadowActive()
+ * @see #setPopupDropShadowEnabled(boolean)
+ */
+ public static boolean isPopupDropShadowEnabled() {
+ if (POPUP_DROP_SHADOW_ENABLED_SYSTEM_VALUE != null)
+ return POPUP_DROP_SHADOW_ENABLED_SYSTEM_VALUE;
+
+ Object value = UIManager.get(POPUP_DROP_SHADOW_ENABLED_KEY);
+ return value == null ? isPopupDropShadowEnabledDefault() : Boolean.TRUE
+ .equals(value);
+ }
+
+ /**
+ * Enables or disables drop shadows in PopupMenus. Note that drop shadows
+ * are always inactive on platforms that provide native drop shadows such as
+ * the Mac OS X.
+ * <p>
+ *
+ * It is recommended to enable this feature only on platforms that
+ * accelerate translucency and snapshots with the hardware.
+ *
+ * @param b
+ * true to enable drop shadows, false to disable them
+ *
+ * @see #isPopupDropShadowActive()
+ * @see #isPopupDropShadowEnabled()
+ */
+ public static void setPopupDropShadowEnabled(boolean b) {
+ UIManager.put(POPUP_DROP_SHADOW_ENABLED_KEY, b);
+ }
+
+ /**
+ * Checks and answers whether popup drop shadows are enabled or disabled by
+ * default. True for modern Windows platforms: Windows 98/ME/2000/XP.
+ * <p>
+ *
+ * TODO: Consider enabling popup drop shadows on Linux by default.
+ * <p>
+ *
+ * TODO: Consider moving the default to the individual L&F's component
+ * defaults initialization. For example Plastic and Plastic3D may disable
+ * this feature by default, while PlasticXP enables it by default.
+ *
+ * @return false
+ */
+ private static boolean isPopupDropShadowEnabledDefault() {
+ return LookUtils.IS_OS_WINDOWS_MODERN;
+ }
+
+ // Look And Feel Replacements *******************************************
+
+ /**
+ * Puts a replacement name for a given <code>LookAndFeel</code> class name
+ * in the list of all look and feel replacements.
+ *
+ * @param original
+ * the name of the look-and-feel to replace
+ * @param replacement
+ * the name of the replacement look-and-feel
+ * @see #removeLookAndFeelReplacement(String)
+ * @see #getReplacementClassNameFor(String)
+ */
+ public static void putLookAndFeelReplacement(String original,
+ String replacement) {
+ LAF_REPLACEMENTS.put(original, replacement);
+ }
+
+ /**
+ * Removes a replacement name for a given <code>LookAndFeel</code> class
+ * name from the list of all look and feel replacements.
+ *
+ * @param original
+ * the name of the look-and-feel that has been replaced
+ * @see #putLookAndFeelReplacement(String, String)
+ * @see #getReplacementClassNameFor(String)
+ */
+ public static void removeLookAndFeelReplacement(String original) {
+ LAF_REPLACEMENTS.remove(original);
+ }
+
+ /**
+ * Initializes some default class name replacements, that replace Sun's Java
+ * look and feel, and Sun's Windows look and feel by the appropriate
+ * JGoodies replacements.
+ *
+ * @see #putLookAndFeelReplacement(String, String)
+ * @see #removeLookAndFeelReplacement(String)
+ * @see #getReplacementClassNameFor(String)
+ */
+ public static void initializeDefaultReplacements() {
+ putLookAndFeelReplacement("javax.swing.plaf.metal.MetalLookAndFeel",
+ PLASTIC3D_NAME);
+ putLookAndFeelReplacement(
+ "com.sun.java.swing.plaf.windows.WindowsLookAndFeel",
+ JGOODIES_WINDOWS_NAME);
+ }
+
+ /**
+ * Returns the class name that can be used to replace the specified
+ * <code>LookAndFeel</code> class name.
+ *
+ * @param className
+ * the name of the look-and-feel class
+ * @return the name of the suggested replacement class
+ * @see #putLookAndFeelReplacement(String, String)
+ * @see #removeLookAndFeelReplacement(String)
+ * @see #initializeDefaultReplacements()
+ */
+ public static String getReplacementClassNameFor(String className) {
+ String replacement = (String) LAF_REPLACEMENTS.get(className);
+ return replacement == null ? className : replacement;
+ }
+
+ /**
+ * Returns the class name for a cross-platform <code>LookAndFeel</code>.
+ *
+ * @return the name of a cross platform look-and-feel class
+ * @see #getSystemLookAndFeelClassName()
+ */
+ public static String getCrossPlatformLookAndFeelClassName() {
+ return PLASTIC3D_NAME;
+ }
+
+ /**
+ * Returns the class name for a system specific <code>LookAndFeel</code>.
+ *
+ * @return the name of the system look-and-feel class
+ * @see #getCrossPlatformLookAndFeelClassName()
+ */
+ public static String getSystemLookAndFeelClassName() {
+ String osName = System.getProperty("os.name");
+ if (osName.startsWith("Windows"))
+ return Options.JGOODIES_WINDOWS_NAME;
+ else if (osName.startsWith("Mac"))
+ return UIManager.getSystemLookAndFeelClassName();
+ else
+ return getCrossPlatformLookAndFeelClassName();
+ }
+
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/common/ShadowPopup.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/common/ShadowPopup.java
new file mode 100644
index 0000000..eceb463
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/common/ShadowPopup.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2007-2011 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.substance.internal.contrib.jgoodies.looks.common;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+
+/**
+ * Does all the magic for getting popups with drop shadows.
+ * It adds the drop shadow border to the Popup,
+ * in {@code #show} it snapshots the screen background as needed,
+ * and in {@code #hide} it cleans up all changes made before.
+ *
+* @author Andrej Golovnin
+ * @author Karsten Lentzsch
+ *
+ * @see com.jgoodies.looks.common.ShadowPopupBorder
+ * @see com.jgoodies.looks.common.ShadowPopupFactory
+ */
+public final class ShadowPopup extends Popup {
+
+ /**
+ * Max number of items to store in the cache.
+ */
+ private static final int MAX_CACHE_SIZE = 5;
+
+ /**
+ * The cache to use for ShadowPopups.
+ */
+ private static List cache;
+
+ /**
+ * The singleton instance used to draw all borders.
+ */
+ private static final Border SHADOW_BORDER = ShadowPopupBorder.getInstance();
+
+ /**
+ * The size of the drop shadow.
+ */
+ private static final int SHADOW_SIZE = 5;
+
+ /**
+ * Indicates whether we can make snapshots from screen or not.
+ */
+ private static boolean canSnapshot = true;
+
+ /**
+ * The component mouse coordinates are relative to, may be null.
+ */
+ private Component owner;
+
+ /**
+ * The contents of the popup.
+ */
+ private Component contents;
+
+ /**
+ * The desired x and y location of the popup.
+ */
+ private int x, y;
+
+ /**
+ * The real popup. The #show() and #hide() methods will delegate
+ * all calls to these popup.
+ */
+ private Popup popup;
+
+ /**
+ * The border of the contents' parent replaced by SHADOW_BORDER.
+ */
+ private Border oldBorder;
+
+ /**
+ * The old value of the opaque property of the contents' parent.
+ */
+ private boolean oldOpaque;
+
+ /**
+ * The heavy weight container of the popup contents, may be null.
+ */
+ private Container heavyWeightContainer;
+
+ /**
+ * Returns a previously used {@code ShadowPopup}, or a new one
+ * if none of the popups have been recycled.
+ */
+ static Popup getInstance(Component owner, Component contents, int x,
+ int y, Popup delegate) {
+ ShadowPopup result;
+ synchronized (ShadowPopup.class) {
+ if (cache == null) {
+ cache = new ArrayList(MAX_CACHE_SIZE);
+ }
+ if (cache.size() > 0) {
+ result = (ShadowPopup) cache.remove(0);
+ } else {
+ result = new ShadowPopup();
+ }
+ }
+ result.reset(owner, contents, x, y, delegate);
+ return result;
+ }
+
+ /**
+ * Recycles the ShadowPopup.
+ */
+ private static void recycle(ShadowPopup popup) {
+ synchronized (ShadowPopup.class) {
+ if (cache.size() < MAX_CACHE_SIZE) {
+ cache.add(popup);
+ }
+ }
+ }
+
+ public static boolean canSnapshot() {
+ return canSnapshot;
+ }
+
+ /**
+ * Hides and disposes of the {@code Popup}. Once a {@code Popup}
+ * has been disposed you should no longer invoke methods on it. A
+ * {@code dispose}d {@code Popup} may be reclaimed and later used
+ * based on the {@code PopupFactory}. As such, if you invoke methods
+ * on a {@code disposed} {@code Popup}, indeterminate
+ * behavior will result.<p>
+ *
+ * In addition to the superclass behavior, we reset the stored
+ * horizontal and vertical drop shadows - if any.
+ */
+ @Override
+ public void hide() {
+ if (contents == null) {
+ return;
+ }
+
+ JComponent parent = (JComponent) contents.getParent();
+ popup.hide();
+ if ((parent != null) && parent.getBorder() == SHADOW_BORDER) {
+ parent.setBorder(oldBorder);
+ parent.setOpaque(oldOpaque);
+ oldBorder = null;
+ if (heavyWeightContainer != null) {
+ parent.putClientProperty(ShadowPopupFactory.PROP_HORIZONTAL_BACKGROUND, null);
+ parent.putClientProperty(ShadowPopupFactory.PROP_VERTICAL_BACKGROUND, null);
+ heavyWeightContainer = null;
+ }
+ }
+ owner = null;
+ contents = null;
+ popup = null;
+ recycle(this);
+ }
+
+ /**
+ * Makes the {@code Popup} visible. If the popup has a
+ * heavy-weight container, we try to snapshot the background.
+ * If the {@code Popup} is currently visible, it remains visible.
+ */
+ @Override
+ public void show() {
+ if (heavyWeightContainer != null) {
+ snapshot();
+ }
+ popup.show();
+ }
+
+ /**
+ * Reinitializes this ShadowPopup using the given parameters.
+ *
+ * @param owner component mouse coordinates are relative to, may be null
+ * @param contents the contents of the popup
+ * @param x the desired x location of the popup
+ * @param y the desired y location of the popup
+ * @param popup the popup to wrap
+ */
+ private void reset(Component owner, Component contents, int x, int y,
+ Popup popup) {
+ this.owner = owner;
+ this.contents = contents;
+ this.popup = popup;
+ this.x = x;
+ this.y = y;
+ if (owner instanceof JComboBox) {
+ return;
+ }
+ // Do not install the shadow border when the contents
+ // has a preferred size less than or equal to 0.
+ // We can't use the size, because it is(0, 0) for new popups.
+ Dimension contentsPrefSize = contents.getPreferredSize();
+ if ((contentsPrefSize.width <= 0) || (contentsPrefSize.height <= 0)) {
+ return;
+ }
+ for(Container p = contents.getParent(); p != null; p = p.getParent()) {
+ if ((p instanceof JWindow) || (p instanceof Panel)) {
+ // Workaround for the gray rect problem.
+ p.setBackground(contents.getBackground());
+ heavyWeightContainer = p;
+ break;
+ }
+ }
+ JComponent parent = (JComponent) contents.getParent();
+ oldOpaque = parent.isOpaque();
+ oldBorder = parent.getBorder();
+ parent.setOpaque(false);
+ parent.setBorder(SHADOW_BORDER);
+ // Pack it because we have changed the border.
+ if (heavyWeightContainer != null) {
+ heavyWeightContainer.setSize(
+ heavyWeightContainer.getPreferredSize());
+ } else {
+ parent.setSize(parent.getPreferredSize());
+ }
+ }
+
+ /**
+ * The 'scratch pad' objects used to calculate dirty regions of
+ * the screen snapshots.
+ *
+ * @see #snapshot()
+ */
+ private static final Point POINT = new Point();
+ private static final Rectangle RECT = new Rectangle();
+
+ /**
+ * Snapshots the background. The snapshots are stored as client
+ * properties of the contents' parent. The next time the border is drawn,
+ * this background will be used.<p>
+ *
+ * Uses a robot on the default screen device to capture the screen
+ * region under the drop shadow. Does <em>not</em> use the window's
+ * device, because that may be an outdated device (due to popup reuse)
+ * and the robot's origin seems to be adjusted with the default screen
+ * device.
+ *
+ * @see #show()
+ * @see com.jgoodies.looks.common.ShadowPopupBorder
+ * @see Robot#createScreenCapture(Rectangle)
+ */
+ private void snapshot() {
+ try {
+ Dimension size = heavyWeightContainer.getPreferredSize();
+ int width = size.width;
+ int height = size.height;
+
+ // Avoid unnecessary and illegal screen captures
+ // for degenerated popups.
+ if ((width <= 0) || (height <= SHADOW_SIZE)) {
+ return;
+ }
+
+ Robot robot = new Robot(); // uses the default screen device
+
+ RECT.setBounds(x, y + height - SHADOW_SIZE, width, SHADOW_SIZE);
+ BufferedImage hShadowBg = robot.createScreenCapture(RECT);
+
+ RECT.setBounds(x + width - SHADOW_SIZE, y, SHADOW_SIZE,
+ height - SHADOW_SIZE);
+ BufferedImage vShadowBg = robot.createScreenCapture(RECT);
+
+ JComponent parent = (JComponent) contents.getParent();
+ parent.putClientProperty(ShadowPopupFactory.PROP_HORIZONTAL_BACKGROUND, hShadowBg);
+ parent.putClientProperty(ShadowPopupFactory.PROP_VERTICAL_BACKGROUND, vShadowBg);
+
+ Container layeredPane = getLayeredPane();
+ if (layeredPane == null) {
+ // This could happen if owner is null.
+ return;
+ }
+
+ int layeredPaneWidth = layeredPane.getWidth();
+ int layeredPaneHeight = layeredPane.getHeight();
+
+ POINT.x = x;
+ POINT.y = y;
+ SwingUtilities.convertPointFromScreen(POINT, layeredPane);
+
+ // If needed paint dirty region of the horizontal snapshot.
+ RECT.x = POINT.x;
+ RECT.y = POINT.y + height - SHADOW_SIZE;
+ RECT.width = width;
+ RECT.height = SHADOW_SIZE;
+
+ if ((RECT.x + RECT.width) > layeredPaneWidth) {
+ RECT.width = layeredPaneWidth - RECT.x;
+ }
+ if ((RECT.y + RECT.height) > layeredPaneHeight) {
+ RECT.height = layeredPaneHeight - RECT.y;
+ }
+ if (!RECT.isEmpty()) {
+ Graphics g = hShadowBg.createGraphics();
+ g.translate(-RECT.x, -RECT.y);
+ g.setClip(RECT);
+ if (layeredPane instanceof JComponent) {
+ JComponent c = (JComponent) layeredPane;
+ boolean doubleBuffered = c.isDoubleBuffered();
+ c.setDoubleBuffered(false);
+ c.paintAll(g);
+ c.setDoubleBuffered(doubleBuffered);
+ } else {
+ layeredPane.paintAll(g);
+ }
+ g.dispose();
+ }
+
+ // If needed paint dirty region of the vertical snapshot.
+ RECT.x = POINT.x + width - SHADOW_SIZE;
+ RECT.y = POINT.y;
+ RECT.width = SHADOW_SIZE;
+ RECT.height = height - SHADOW_SIZE;
+
+ if ((RECT.x + RECT.width) > layeredPaneWidth) {
+ RECT.width = layeredPaneWidth - RECT.x;
+ }
+ if ((RECT.y + RECT.height) > layeredPaneHeight) {
+ RECT.height = layeredPaneHeight - RECT.y;
+ }
+ if (!RECT.isEmpty()) {
+ Graphics g = vShadowBg.createGraphics();
+ g.translate(-RECT.x, -RECT.y);
+ g.setClip(RECT);
+ if (layeredPane instanceof JComponent) {
+ JComponent c = (JComponent) layeredPane;
+ boolean doubleBuffered = c.isDoubleBuffered();
+ c.setDoubleBuffered(false);
+ c.paintAll(g);
+ c.setDoubleBuffered(doubleBuffered);
+ } else {
+ layeredPane.paintAll(g);
+ }
+ g.dispose();
+ }
+ } catch (AWTException e) {
+ canSnapshot = false;
+ } catch (SecurityException e) {
+ canSnapshot = false;
+ }
+ }
+
+ /**
+ * @return the top level layered pane which contains the owner.
+ */
+ private Container getLayeredPane() {
+ // The code below is copied from PopupFactory#LightWeightPopup#show()
+ Container parent = null;
+ if (owner != null) {
+ parent = owner instanceof Container
+ ? (Container) owner
+ : owner.getParent();
+ }
+ // Try to find a JLayeredPane and Window to add
+ for (Container p = parent; p != null; p = p.getParent()) {
+ if (p instanceof JRootPane) {
+ if (p.getParent() instanceof JInternalFrame) {
+ continue;
+ }
+ parent = ((JRootPane) p).getLayeredPane();
+ // Continue, so that if there is a higher JRootPane, we'll
+ // pick it up.
+ } else if (p instanceof Window) {
+ if (parent == null) {
+ parent = p;
+ }
+ break;
+ } else if (p instanceof JApplet) {
+ // Painting code stops at Applets, we don't want
+ // to add to a Component above an Applet otherwise
+ // you'll never see it painted.
+ break;
+ }
+ }
+ return parent;
+ }
+
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/common/ShadowPopupBorder.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/common/ShadowPopupBorder.java
new file mode 100644
index 0000000..a6cc56c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/common/ShadowPopupBorder.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2001-2011 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.substance.internal.contrib.jgoodies.looks.common;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.Insets;
+
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+import javax.swing.border.AbstractBorder;
+
+/**
+ * A border with a drop shadow intended to be used as the outer border
+ * of popups. Can paint the screen background if used with heavy-weight
+ * popup windows.
+ *
+ * @author Stefan Matthias Aust
+ * @author Karsten Lentzsch
+ * @author Andrej Golovnin
+ *
+ * @see ShadowPopup
+ * @see ShadowPopupFactory
+ */
+final class ShadowPopupBorder extends AbstractBorder {
+
+ /**
+ * The drop shadow needs 5 pixels at the bottom and the right hand side.
+ */
+ private static final int SHADOW_SIZE = 5;
+
+ /**
+ * The singleton instance used to draw all borders.
+ */
+ private static ShadowPopupBorder instance = new ShadowPopupBorder();
+
+ /**
+ * The drop shadow is created from a PNG image with 8 bit alpha channel.
+ */
+ private static Image shadow
+ = new ImageIcon(ShadowPopupBorder.class.getResource("shadow.png")).getImage();
+
+
+ // Instance Creation *****************************************************
+
+ /**
+ * Returns the singleton instance used to draw all borders.
+ */
+ public static ShadowPopupBorder getInstance() {
+ return instance;
+ }
+
+
+ /**
+ * Paints the border for the specified component with the specified
+ * position and size.
+ */
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+ // fake drop shadow effect in case of heavy weight popups
+ JComponent popup = (JComponent) c;
+ Image hShadowBg = (Image) popup.getClientProperty(ShadowPopupFactory.PROP_HORIZONTAL_BACKGROUND);
+ if (hShadowBg != null) {
+ g.drawImage(hShadowBg, x, y + height - 5, c);
+ }
+ Image vShadowBg = (Image) popup.getClientProperty(ShadowPopupFactory.PROP_VERTICAL_BACKGROUND);
+ if (vShadowBg != null) {
+ g.drawImage(vShadowBg, x + width - 5, y, c);
+ }
+
+ // draw drop shadow
+ g.drawImage(shadow, x + 5, y + height - 5, x + 10, y + height, 0, 6, 5, 11, null, c);
+ g.drawImage(shadow, x + 10, y + height - 5, x + width - 5, y + height, 5, 6, 6, 11, null, c);
+ g.drawImage(shadow, x + width - 5, y + 5, x + width, y + 10, 6, 0, 11, 5, null, c);
+ g.drawImage(shadow, x + width - 5, y + 10, x + width, y + height - 5, 6, 5, 11, 6, null, c);
+ g.drawImage(shadow, x + width - 5, y + height - 5, x + width, y + height, 6, 6, 11, 11, null, c);
+ }
+
+
+ /**
+ * Returns the insets of the border.
+ */
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return new Insets(0, 0, SHADOW_SIZE, SHADOW_SIZE);
+ }
+
+
+ /**
+ * Reinitializes the insets parameter with this Border's current Insets.
+ * @param c the component for which this border insets value applies
+ * @param insets the object to be reinitialized
+ * @return the {@code insets} object
+ */
+ @Override
+ public Insets getBorderInsets(Component c, Insets insets) {
+ insets.left = insets.top = 0;
+ insets.right = insets.bottom = SHADOW_SIZE;
+ return insets;
+ }
+
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/common/ShadowPopupFactory.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/common/ShadowPopupFactory.java
new file mode 100644
index 0000000..a99f330
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/jgoodies/looks/common/ShadowPopupFactory.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2005-2011 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.substance.internal.contrib.jgoodies.looks.common;
+
+import java.awt.Component;
+
+import javax.swing.LookAndFeel;
+import javax.swing.Popup;
+import javax.swing.PopupFactory;
+
+import org.pushingpixels.lafwidget.utils.LookUtils;
+import org.pushingpixels.substance.internal.contrib.jgoodies.looks.Options;
+
+/**
+ * The JGoodies Looks implementation of {@code PopupFactory}.
+ * Adds a drop shadow border to all popups except ComboBox popups.
+ * It is installed by the JGoodies Plastic L&F, as well as by
+ * the JGoodies Windows L&F during the Look&Feel initialization,
+ * see {@link com.jgoodies.looks.plastic.PlasticLookAndFeel#initialize} and
+ * {@link com.jgoodies.looks.windows.WindowsLookAndFeel#initialize}.<p>
+ *
+ * This factory shall not be used on platforms that provide native drop shadows,
+ * such as the Mac OS X. Therefore the invocation of the {@link #install()}
+ * method will have no effect on such platforms.<p>
+ *
+ * <strong>Note:</strong> To be used in a sandbox environment, this PopupFactory
+ * requires two AWT permissions: {@code createRobot} and
+ * {@code readDisplayPixels}. The reason for it is, that in the case of
+ * the heavy weight popups this PopupFactory uses a Robot to snapshot
+ * the screen background to simulate the drop shadow effect.
+ *
+ * @author Andrej Golovnin
+ * @author Karsten Lentzsch
+ *
+ * @see java.awt.AWTPermission
+ * @see java.awt.Robot
+ * @see javax.swing.Popup
+ * @see LookAndFeel#initialize
+ * @see LookAndFeel#uninitialize
+ */
+public final class ShadowPopupFactory extends PopupFactory {
+
+ /**
+ * In the case of heavy weight popups, snapshots of the screen background
+ * will be stored as client properties of the popup contents' parent.
+ * These snapshots will be used by the popup border to simulate the drop
+ * shadow effect. The two following constants define the names of
+ * these client properties.
+ *
+ * @see com.jgoodies.looks.common.ShadowPopupBorder
+ */
+ static final String PROP_HORIZONTAL_BACKGROUND = "jgoodies.hShadowBg";
+ static final String PROP_VERTICAL_BACKGROUND = "jgoodies.vShadowBg";
+
+ /**
+ * The PopupFactory used before this PopupFactory has been installed
+ * in {@code #install}. Used to restored the original state
+ * in {@code #uninstall}.
+ */
+ private final PopupFactory storedFactory;
+
+
+ // Instance Creation ******************************************************
+
+ private ShadowPopupFactory(PopupFactory storedFactory) {
+ this.storedFactory = storedFactory;
+ }
+
+
+ // API ********************************************************************
+
+ /**
+ * Installs the ShadowPopupFactory as the shared popup factory
+ * on non-Mac platforms. Also stores the previously set factory,
+ * so that it can be restored in {@code #uninstall}.<p>
+ *
+ * In some Mac Java environments the popup factory throws
+ * a NullPointerException when we call {@code #getPopup}.<p>
+ *
+ * TODO: The Mac case shows that we may have problems replacing
+ * non PopupFactory instances. Therefore we should consider
+ * replacing only instances of PopupFactory.
+ *
+ * @see #uninstall()
+ */
+ public static void install() {
+ if (LookUtils.IS_OS_MAC) {
+ return;
+ }
+
+ PopupFactory factory = PopupFactory.getSharedInstance();
+ if (factory instanceof ShadowPopupFactory) {
+ return;
+ }
+
+ PopupFactory.setSharedInstance(new ShadowPopupFactory(factory));
+ }
+
+ /**
+ * Uninstalls the ShadowPopupFactory and restores the original
+ * popup factory as the new shared popup factory.
+ *
+ * @see #install()
+ */
+ public static void uninstall() {
+ PopupFactory factory = PopupFactory.getSharedInstance();
+ if (!(factory instanceof ShadowPopupFactory)) {
+ return;
+ }
+
+ PopupFactory stored = ((ShadowPopupFactory) factory).storedFactory;
+ PopupFactory.setSharedInstance(stored);
+ }
+
+
+ /**
+ * Creates a {@code Popup} for the Component {@code owner}
+ * containing the Component {@code contents}. In addition to
+ * the superclass behavior, we try to return a Popup that has a drop shadow,
+ * if popup drop shadows are active - as returned by
+ * {@code Options#isPopupDropShadowActive}.<p>
+ *
+ * {@code owner} is used to determine which {@code Window} the new
+ * {@code Popup} will parent the {@code Component} the
+ * {@code Popup} creates to. A null {@code owner} implies there
+ * is no valid parent. {@code x} and
+ * {@code y} specify the preferred initial location to place
+ * the {@code Popup} at. Based on screen size, or other paramaters,
+ * the {@code Popup} may not display at {@code x} and
+ * {@code y}.<p>
+ *
+ * We invoke the super {@code #getPopup}, not the one in the
+ * stored factory, because the popup type is set in this instance,
+ * not in the stored one.
+ *
+ * @param owner Component mouse coordinates are relative to, may be null
+ * @param contents Contents of the Popup
+ * @param x Initial x screen coordinate
+ * @param y Initial y screen coordinate
+ * @return Popup containing Contents
+ * @throws IllegalArgumentException if contents is null
+ *
+ * @see Options#isPopupDropShadowActive()
+ */
+ @Override
+ public Popup getPopup(Component owner, Component contents, int x, int y)
+ throws IllegalArgumentException {
+ Popup popup = super.getPopup(owner, contents, x, y);
+ return Options.isPopupDropShadowActive()
+ ? ShadowPopup.getInstance(owner, contents, x, y, popup)
+ : popup;
+ }
+
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/ButtonStateIcon.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/ButtonStateIcon.java
new file mode 100644
index 0000000..36061d7
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/ButtonStateIcon.java
@@ -0,0 +1,194 @@
+/*
+ * @(#)ButtonStateIcon.java 3.1 2005-12-08
+ *
+ * Copyright (c) 2003-2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+/**
+ * An Icon with different visuals reflecting the state of the AbstractButton
+ * on which it draws on.
+ *
+ * @author Werner Randelshofer
+ * @version 3.1 2005-12-08 Draw pressed state if model is armed, instead of
+ * drawing pressed state if model is armed and pressed.
+ * <br>3.0 2005-10-17 Changed superclass to MultiIcon.
+ * <br>2.0.1 2005-10-02 Used Enabled image for Pressed-Unarmed state.
+ * <br>2.0.1 2005-09-11 generateMissing icons can now deal with only one
+ * provided icon image.
+ * <br>2.0 2005-03-19 Reworked.
+ * <br>1.0 October 5, 2003 Create..
+ */
+public class ButtonStateIcon extends MultiIcon {
+ private final static int E = 0;
+ private final static int EP = 1;
+ private final static int ES = 2;
+ private final static int EPS = 3;
+ private final static int D = 4;
+ private final static int DS = 5;
+ private final static int I = 6;
+ private final static int IS = 7;
+ private final static int DI = 8;
+ private final static int DIS = 9;
+
+ /**
+ * Creates a new instance.
+ * All icons must have the same dimensions.
+ * If an icon is null, an icon is derived for the state from the
+ * other icons.
+ */
+ public ButtonStateIcon(Icon e, Icon ep, Icon es, Icon eps, Icon d, Icon ds) {
+ super(new Icon[] {e, ep, es, eps, d, ds});
+ }
+ /**
+ * Creates a new instance.
+ * All icons must have the same dimensions.
+ *
+ * The array indices are used to represente the following states:
+ * [0] Enabled
+ * [1] Enabled Pressed
+ * [2] Enabled Selected
+ * [3] Enabled Pressed Selected
+ * [4] Disabled
+ * [5] Disabled Selected
+ * [6] Enabled Inactive
+ * [7] Enabled Inactive Selected
+ * [8] Disabled Inactive
+ * [9] Disabled Inactive Selected
+ *
+ * If an array element is null, an icon is derived for the state from the
+ * other icons.
+ */
+ public ButtonStateIcon(Image[] images) {
+ super(images);
+ }
+ /**
+ * Creates a new instance.
+ * All icons must have the same dimensions.
+ * If an icon is null, nothing is drawn for this state.
+ */
+ public ButtonStateIcon(Icon[] icons) {
+ super(icons);
+ }
+
+ /**
+ * Creates a new instance.
+ * The icon representations are created lazily from the image.
+ */
+ public ButtonStateIcon(Image tiledImage, int tileCount, boolean isTiledHorizontally) {
+ super(tiledImage, tileCount, isTiledHorizontally);
+ }
+
+
+ @Override
+ protected Icon getIcon(Component c) {
+ Icon icon;
+ boolean isActive = QuaquaUtilities.isOnActiveWindow(c);
+
+ if (c instanceof AbstractButton) {
+ ButtonModel model = ((AbstractButton) c).getModel();
+ if (isActive) {
+ if (model.isEnabled()) {
+ if (/*model.isPressed() && */model.isArmed()) {
+ if (model.isSelected()) {
+ icon = icons[EPS];
+ } else {
+ icon = icons[EP];
+ }
+ } else if (model.isSelected()) {
+ icon = icons[ES];
+ } else {
+ icon = icons[E];
+ }
+ } else {
+ if (model.isSelected()) {
+ icon = icons[DS];
+ } else {
+ icon = icons[D];
+ }
+ }
+ } else {
+ if (model.isEnabled()) {
+ if (model.isSelected()) {
+ icon = icons[IS];
+ } else {
+ icon = icons[I];
+ }
+ } else {
+ if (model.isSelected()) {
+ icon = icons[DIS];
+ } else {
+ icon = icons[DI];
+ }
+ }
+ }
+ } else {
+ if (isActive) {
+ if (c.isEnabled()) {
+ icon = icons[E];
+ } else {
+ icon = icons[D];
+ }
+ } else {
+ if (c.isEnabled()) {
+ icon = icons[I];
+ } else {
+ icon = icons[DI];
+ }
+ }
+ }
+ return icon;
+ }
+
+ @Override
+ protected void generateMissingIcons() {
+ if (icons.length != 10) {
+ Icon[] helper = icons;
+ icons = new Icon[10];
+ System.arraycopy(helper, 0, icons, 0, Math.min(helper.length, icons.length));
+ }
+
+ if (icons[EP] == null) {
+ icons[EP] = icons[E];
+ }
+ if (icons[ES] == null) {
+ icons[ES] = icons[EP];
+ }
+ if (icons[EPS] == null) {
+ icons[EPS] = icons[EP];
+ }
+ if (icons[D] == null) {
+ icons[D] = icons[E];
+ }
+ if (icons[DS] == null) {
+ icons[DS] = icons[ES];
+ }
+ if (icons[I] == null) {
+ icons[I] = icons[E];
+ }
+ if (icons[IS] == null) {
+ icons[IS] = icons[ES];
+ }
+ if (icons[DI] == null) {
+ icons[DI] = icons[D];
+ }
+ if (icons[DIS] == null) {
+ icons[DIS] = icons[DS];
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/MultiIcon.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/MultiIcon.java
new file mode 100644
index 0000000..576cd8c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/MultiIcon.java
@@ -0,0 +1,130 @@
+/*
+ * @(#)MultiIcon.java 1.0.1 2006-02-14
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+
+/**
+ * An icon which paints one out of multiple icons depending on the state
+ * of the component.
+ * MultiIcon can lazily create the icons from a tiled image.
+ *
+ * @author Werner Randelshofer
+ * @version 1.0.1 2006-02-14 Created tileCount icons from image.
+ * <br>1.0 October 17, 2005 Created.
+ */
+public abstract class MultiIcon implements Icon {
+ /**
+ * The icons from which we choose from.
+ * This variable is null, if we are using a tiled image as our base.
+ */
+ protected Icon[] icons;
+
+ /** Holds the icon pictures in a single image. This variable is used only
+ *until we create the icons array. Then it is set to null.
+ */
+ private Image tiledImage;
+ /**
+ * The number of icons in the tiledImage.
+ */
+ private int tileCount;
+ /**
+ * Whether the tiledImage needs to be tiled horizontaly or vertically
+ * to get the icons out of it.
+ */
+ private boolean isTiledHorizontaly;
+
+
+ /**
+ * Creates a new instance from an array of icons.
+ * All icons must have the same dimensions.
+ * If an icon is null, an icon is derived for the state from the
+ * other icons.
+ */
+ public MultiIcon(Icon[] icons) {
+ this.icons = icons;
+ generateMissingIcons();
+ }
+
+ /**
+ * Creates a new instance from an array of images.
+ * All icons must have the same dimensions.
+ * If an icon is null, an icon is derived for the state from the
+ * other icons.
+ */
+ public MultiIcon(Image[] images) {
+ this.icons = new Icon[images.length];
+ for (int i=0, n = icons.length; i < n; i++) {
+ if (images[i] != null) {
+ icons[i] = new ImageIcon(images[i]);
+ }
+ }
+ generateMissingIcons();
+ }
+
+ /**
+ * Creates a new instance.
+ * The icon representations are created lazily from the tiled image.
+ */
+ public MultiIcon(Image tiledImage, int tileCount, boolean isTiledHorizontaly) {
+ this.tiledImage = tiledImage;
+ this.tileCount = tileCount;
+ this.isTiledHorizontaly = isTiledHorizontaly;
+ }
+
+
+ @Override
+ public int getIconHeight() {
+ generateIconsFromTiledImage();
+ return icons[0].getIconHeight();
+ }
+
+ @Override
+ public int getIconWidth() {
+ generateIconsFromTiledImage();
+ return icons[0].getIconWidth();
+ }
+
+ @Override
+ public void paintIcon(java.awt.Component c, java.awt.Graphics g, int x, int y) {
+ generateIconsFromTiledImage();
+ Icon icon = getIcon(c);
+ if (icon != null) {
+ icon.paintIcon(c, g, x, y);
+ }
+ }
+
+ private void generateIconsFromTiledImage() {
+ if (icons == null) {
+ icons = new Icon[tileCount];
+ Image[] images = Images.split(tiledImage, tileCount, isTiledHorizontaly);
+ for (int i=0, n = Math.min(images.length, icons.length); i < n; i++) {
+ if (images[i] != null) {
+ icons[i] = new ImageIcon(images[i]);
+ }
+ }
+ generateMissingIcons();
+ tiledImage = null;
+ }
+ }
+
+ protected abstract Icon getIcon(Component c);
+ protected abstract void generateMissingIcons();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Quaqua13ColorChooserUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Quaqua13ColorChooserUI.java
new file mode 100644
index 0000000..2c9a3f3
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Quaqua13ColorChooserUI.java
@@ -0,0 +1,226 @@
+/*
+ * @(#)QuaquaColorChooserUI.java 1.2.1 2006-05-10
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser.ColorChooserMainPanel;
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser.QuaquaColorPreviewPanel;
+
+import javax.swing.JColorChooser;
+import javax.swing.JComponent;
+import javax.swing.LookAndFeel;
+import javax.swing.UIManager;
+import javax.swing.colorchooser.AbstractColorChooserPanel;
+import javax.swing.colorchooser.ColorSelectionModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.ColorChooserUI;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.security.AccessControlException;
+import java.util.ArrayList;
+/**
+ * QuaquaColorChooserUI.
+ *
+ * @author Werner Randelshofer
+ * @version 1.2.1 2006-05-10 Method createDefaultChoosers mustn't return an
+ * an array with null entries in it.
+ * <br>1.2 2005-09-18 Read class names of default choosers from UIManager.
+ * <br>1.1 2005-08-28 ColorWheelChooser and CrayonsChooser added.
+ * <br>1.0 29 March 2005 Created.
+ */
+public class Quaqua13ColorChooserUI extends ColorChooserUI {
+ protected ColorChooserMainPanel mainPanel;
+ protected JColorChooser chooser;
+ protected ChangeListener previewListener;
+ protected PropertyChangeListener propertyChangeListener;
+ protected AbstractColorChooserPanel[] defaultChoosers;
+ protected JComponent previewPanel;
+
+ public static ComponentUI createUI(JComponent c) {
+ return new Quaqua13ColorChooserUI();
+ }
+
+ @Override
+ public void installUI( JComponent c ) {
+ chooser = (JColorChooser)c;
+
+ installDefaults();
+ installListeners();
+
+ chooser.setLayout( new BorderLayout() );
+ mainPanel = new ColorChooserMainPanel();
+ chooser.add(mainPanel);
+ defaultChoosers = createDefaultChoosers();
+ chooser.setChooserPanels(defaultChoosers);
+
+ installPreviewPanel();
+ }
+
+ protected AbstractColorChooserPanel[] createDefaultChoosers() {
+ String[] defaultChoosers = (String[]) UIManager.get("ColorChooser.defaultChoosers");
+ ArrayList panels = new ArrayList(defaultChoosers.length);
+ for (int i=0; i < defaultChoosers.length; i++) {
+ try {
+
+ panels.add(Class.forName(defaultChoosers[i]).newInstance());
+ } catch (AccessControlException e) {
+ // suppress
+ System.err.println("Quaqua13ColorChooserUI warning: unable to instantiate "+defaultChoosers[i]);
+ } catch (Exception e) {
+ throw new InternalError("Unable to instantiate "+defaultChoosers[i]);
+ } catch (UnsupportedClassVersionError e) {
+ // suppress
+ System.err.println("Quaqua13ColorChooserUI warning: unable to instantiate "+defaultChoosers[i]);
+ }
+ }
+ //AbstractColorChooserPanel[] panels = new AbstractColorChooserPanel[defaultChoosers.length];
+ return (AbstractColorChooserPanel[]) panels.toArray(new AbstractColorChooserPanel[panels.size()]);
+ }
+
+
+ @Override
+ public void uninstallUI( JComponent c ) {
+ chooser.remove(mainPanel);
+
+ uninstallListeners();
+ uninstallDefaultChoosers();
+ uninstallDefaults();
+
+ mainPanel.setPreviewPanel(null);
+ if (previewPanel instanceof UIResource) {
+ chooser.setPreviewPanel(null);
+ }
+
+ mainPanel = null;
+ previewPanel = null;
+ defaultChoosers = null;
+ chooser = null;
+ }
+ protected void installDefaults() {
+ LookAndFeel.installColorsAndFont(chooser, "ColorChooser.background",
+ "ColorChooser.foreground",
+ "ColorChooser.font");
+ }
+
+ protected void uninstallDefaults() {
+ }
+
+
+ protected void installListeners() {
+ propertyChangeListener = createPropertyChangeListener();
+ chooser.addPropertyChangeListener( propertyChangeListener );
+
+ previewListener = new PreviewListener();
+ chooser.getSelectionModel().addChangeListener(previewListener);
+ }
+
+ protected void uninstallListeners() {
+ chooser.removePropertyChangeListener( propertyChangeListener );
+ chooser.getSelectionModel().removeChangeListener(previewListener);
+ }
+
+ protected PropertyChangeListener createPropertyChangeListener() {
+ return new PropertyHandler();
+ }
+
+ protected void installPreviewPanel() {
+ if (previewPanel != null) {
+ mainPanel.setPreviewPanel(null);
+ }
+
+ previewPanel = chooser.getPreviewPanel();
+ // reject the default preview panel
+ if (previewPanel!= null && "javax.swing.colorchooser.DefaultPreviewPanel".equals(previewPanel.getClass().getName())) {
+ previewPanel = null;
+ }
+ if ((previewPanel != null) && (mainPanel != null) && (chooser != null) && (previewPanel.getSize().getHeight()+previewPanel.getSize().getWidth() == 0)) {
+ mainPanel.setPreviewPanel(null);
+ return;
+ }
+ if (previewPanel == null || previewPanel instanceof UIResource) {
+ //previewPanel = ColorChooserComponentFactory.getPreviewPanel(); // get from table?
+ previewPanel = new QuaquaColorPreviewPanel();
+ chooser.setPreviewPanel(previewPanel);
+ }
+ previewPanel.setForeground(chooser.getColor());
+ mainPanel.setPreviewPanel(previewPanel);
+ }
+
+ class PreviewListener implements ChangeListener {
+ @Override
+ public void stateChanged( ChangeEvent e ) {
+ ColorSelectionModel model = (ColorSelectionModel)e.getSource();
+ if (previewPanel != null) {
+ previewPanel.setForeground(model.getSelectedColor());
+ previewPanel.repaint();
+ }
+ }
+ }
+ protected void uninstallDefaultChoosers() {
+ for( int i = 0 ; i < defaultChoosers.length; i++) {
+ chooser.removeChooserPanel( defaultChoosers[i] );
+ }
+ }
+
+ /**
+ * This inner class is marked "public" due to a compiler bug.
+ * This class should be treated as a "protected" inner class.
+ * Instantiate it only within subclasses of <Foo>.
+ */
+ public class PropertyHandler implements PropertyChangeListener {
+
+ @Override
+ public void propertyChange(PropertyChangeEvent e) {
+
+ if (e.getPropertyName().equals( JColorChooser.CHOOSER_PANELS_PROPERTY)) {
+ AbstractColorChooserPanel[] oldPanels = (AbstractColorChooserPanel[]) e.getOldValue();
+ AbstractColorChooserPanel[] newPanels = (AbstractColorChooserPanel[]) e.getNewValue();
+
+ for (int i = 0; i < oldPanels.length; i++) { // remove old panels
+ Container wrapper = oldPanels[i].getParent();
+ if (wrapper != null) {
+ Container parent = wrapper.getParent();
+ if (parent != null)
+ parent.remove(wrapper); // remove from hierarchy
+ oldPanels[i].uninstallChooserPanel(chooser); // uninstall
+ }
+ }
+
+ mainPanel.removeAllColorChooserPanels();
+ for (int i = 0; i < newPanels.length; i++) {
+ if (newPanels[i] != null) {
+ mainPanel.addColorChooserPanel(newPanels[i]);
+ }
+ }
+
+ for (int i = 0; i < newPanels.length; i++) {
+ if (newPanels[i] != null) {
+ newPanels[i].installChooserPanel(chooser);
+ }
+ }
+ }
+
+ if ( e.getPropertyName().equals( JColorChooser.PREVIEW_PANEL_PROPERTY)) {
+ if (e.getNewValue() != previewPanel) {
+ installPreviewPanel();
+ }
+ }
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Quaqua14ColorChooserUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Quaqua14ColorChooserUI.java
new file mode 100644
index 0000000..cc80a80
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Quaqua14ColorChooserUI.java
@@ -0,0 +1,159 @@
+/*
+ * @(#)QuaquaColorChooserUI.java 1.1 2005-12-18
+ *
+ * Copyright (c) 2004 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.colorchooser.*;
+import javax.swing.event.*;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser.*;
+
+/**
+ * QuaquaColorChooserUI with enhancements for Java 1.4.
+ *
+ * @author Werner Randelshofer
+ * @version 1.1 2005-12-18 Gracefully handle instantiation failures of
+ * color chooser panels.
+ * <br>1.0 29 March 2005 Created.
+ */
+public class Quaqua14ColorChooserUI extends Quaqua13ColorChooserUI {
+ private static TransferHandler defaultTransferHandler = new ColorTransferHandler();
+ private MouseListener previewMouseListener;
+
+ public static ComponentUI createUI(JComponent c) {
+ return new Quaqua14ColorChooserUI();
+ }
+
+ @Override
+ public void installUI(JComponent c) {
+ super.installUI(c);
+ chooser.applyComponentOrientation(c.getComponentOrientation());
+ }
+
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ TransferHandler th = chooser.getTransferHandler();
+ if (th == null || th instanceof UIResource) {
+ chooser.setTransferHandler(defaultTransferHandler);
+ }
+ }
+
+ @Override
+ protected void uninstallDefaults() {
+ if (chooser.getTransferHandler() instanceof UIResource) {
+ chooser.setTransferHandler(null);
+ }
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ previewMouseListener = new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (chooser.getDragEnabled()) {
+ TransferHandler th = chooser.getTransferHandler();
+ th.exportAsDrag(chooser, e, TransferHandler.COPY);
+ }
+ }
+ };
+
+ }
+
+ @Override
+ protected void uninstallListeners() {
+ super.uninstallListeners();
+ previewPanel.removeMouseListener(previewMouseListener);
+ }
+
+ @Override
+ protected void installPreviewPanel() {
+ if (previewPanel != null) {
+ previewPanel.removeMouseListener(previewMouseListener);
+ }
+ super.installPreviewPanel();
+ previewPanel.addMouseListener(previewMouseListener);
+ }
+
+ @Override
+ protected PropertyChangeListener createPropertyChangeListener() {
+ return new PropertyHandler();
+ }
+
+ public class PropertyHandler implements PropertyChangeListener {
+
+ @Override
+ public void propertyChange(PropertyChangeEvent e) {
+
+ if ( e.getPropertyName().equals( JColorChooser.CHOOSER_PANELS_PROPERTY ) ) {
+ AbstractColorChooserPanel[] oldPanels = (AbstractColorChooserPanel[]) e.getOldValue();
+ AbstractColorChooserPanel[] newPanels = (AbstractColorChooserPanel[]) e.getNewValue();
+
+ for (int i = 0; i < oldPanels.length; i++) { // remove old panels
+ if (oldPanels[i] != null) {
+ Container wrapper = oldPanels[i].getParent();
+ if (wrapper != null) {
+ Container parent = wrapper.getParent();
+ if (parent != null)
+ parent.remove(wrapper); // remove from hierarchy
+ oldPanels[i].uninstallChooserPanel(chooser); // uninstall
+ }
+ }
+ }
+
+ mainPanel.removeAllColorChooserPanels();
+ for (int i = 0; i < newPanels.length; i++) {
+ if (newPanels[i] != null) {
+ mainPanel.addColorChooserPanel(newPanels[i]);
+ }
+ }
+
+ chooser.applyComponentOrientation(chooser.getComponentOrientation());
+ for (int i = 0; i < newPanels.length; i++) {
+ if (newPanels[i] != null) {
+ newPanels[i].installChooserPanel(chooser);
+ }
+ }
+ }
+ if ( e.getPropertyName().equals( JColorChooser.PREVIEW_PANEL_PROPERTY ) ) {
+ if (e.getNewValue() != previewPanel) {
+ installPreviewPanel();
+ }
+ }
+ if (e.getPropertyName().equals("componentOrientation")) {
+ ComponentOrientation o = (ComponentOrientation)e.getNewValue();
+ JColorChooser cc = (JColorChooser)e.getSource();
+ if (o != (ComponentOrientation)e.getOldValue()) {
+ cc.applyComponentOrientation(o);
+ cc.updateUI();
+ }
+ }
+ }
+ }
+ static class ColorTransferHandler extends TransferHandler implements UIResource {
+
+ ColorTransferHandler() {
+ super("color");
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/QuaquaIconFactory.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/QuaquaIconFactory.java
new file mode 100644
index 0000000..f40787a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/QuaquaIconFactory.java
@@ -0,0 +1,136 @@
+/*
+ * @(#)QuaquaIconFactory.java 3.2 2007-01-05
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua;
+
+import java.net.*;
+import java.awt.*;
+import java.awt.image.*;
+import javax.swing.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+//import javax.imageio.*;
+//import javax.imageio.stream.*;
+import java.io.*;
+import java.util.*;
+
+/**
+ * QuaquaIconFactory.
+ *
+ * @author Werner Randelshofer, Christopher Atlan
+ * @version 3.2 2007-01-05 Issue #1: Changed LazyOptionPaneIcon to load image
+ * asynchronously before paintIcon is invoked.
+ * <br>3.1 2006-12-24 by Karl von Randow: Use Images class to create artwork.
+ * <br>3.0.2 2006-11-01 Use Graphics2D.drawImage() to scale application
+ * image icon instead of using Image.getScaledInstance().
+ * <br>3.0.1 2006-05-14 Application icon was unnecessarily created multiple
+ * times.
+ * <br>3.0 2006-05-12 Added support for file icon images. Renamed some
+ * methods.
+ * <br>2.1 2006-02-14 Added method createFrameButtonStateIcon.
+ * <br>2.0 2006-02-12 Added methods createApplicationIcon, compose,
+ * createOptionPaneIcon. These methods were contributed by Christopher Atlan.
+ * <br>1.0 December 4, 2005 Created.
+ */
+public class QuaquaIconFactory {
+ private static BufferedImage applicationImage;
+
+
+ /**
+ * Prevent instance creation.
+ */
+ private QuaquaIconFactory() {
+ }
+
+ public static URL getResource(String location) {
+ URL url = QuaquaIconFactory.class.getResource(location);
+ if (url == null) {
+ throw new InternalError("image resource missing: "+location);
+ }
+ return url;
+ }
+
+ public static Image createImage(String location) {
+ return createImage(QuaquaIconFactory.class, location);
+ }
+ public static Image createImage(Class baseClass, String location) {
+ return Images.createImage(baseClass.getResource(location));
+ }
+ public static Image createBufferedImage(String location) {
+ return Images.toBufferedImage(createImage(location));
+ }
+
+ public static Icon[] createIcons(String location, int count, boolean horizontal) {
+ Icon[] icons = new Icon[count];
+
+ BufferedImage[] images = Images.split(
+ (Image) createImage(location),
+ count, horizontal
+ );
+
+ for (int i=0; i < count; i++) {
+ icons[i] = new IconUIResource(new ImageIcon(images[i]));
+ }
+ return icons;
+ }
+
+ public static Icon createIcon(String location, int count, boolean horizontal, int index) {
+ return createIcons(location, count, horizontal)[index];
+ }
+
+
+ public static Icon createButtonStateIcon(String location, int states) {
+ return new ButtonStateIcon(
+ (Image) createImage(location),
+ states, true
+ );
+ }
+ public static Icon createButtonStateIcon(String location, int states, Point shift) {
+ return new ShiftedIcon(
+ new ButtonStateIcon(
+ (Image) createImage(location),
+ states, true
+ ),
+ shift
+ );
+ }
+ public static Icon createButtonStateIcon(String location, int states, Rectangle shift) {
+ return new ShiftedIcon(
+ new ButtonStateIcon(
+ (Image) createImage(location),
+ states, true
+ ),
+ shift
+ );
+ }
+
+ public static Icon createIcon(Class baseClass, String location) {
+ return new ImageIcon(createImage(baseClass, location));
+ }
+ public static Icon createIcon(Class baseClass, String location, Point shift) {
+ return new ShiftedIcon(
+ new ImageIcon(createImage(baseClass, location)),
+ shift
+ );
+ }
+ public static Icon createIcon(Class baseClass, String location, Rectangle shiftAndSize) {
+ return new ShiftedIcon(
+ new ImageIcon(createImage(baseClass, location)),
+ shiftAndSize
+ );
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/QuaquaUtilities.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/QuaquaUtilities.java
new file mode 100644
index 0000000..203f54b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/QuaquaUtilities.java
@@ -0,0 +1,703 @@
+/*
+ * @(#)QuaquaUtilities.java 3.1 2006-09-04
+ *
+ * Copyright (c) 2003-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua;
+
+import java.applet.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.font.*;
+import java.awt.image.*;
+import java.awt.peer.*;
+import java.net.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import javax.swing.border.*;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+/**
+ * Utility class for the Quaqua LAF.
+ *
+ * @author Werner Randelshofer, Staldenmattweg 2, CH-6405 Immensee, Switzerland
+ * @version 3.1 2006-09-04 Added method compositeRequestFocus.
+ * <br>3.0.5 2006-08-20 Method endGraphics must not set
+ * KEY_TEXT_ANTIALIASING to null.
+ * <br>3.0.4 2006-02-19 Catch Throwable in method setWindowAlpha instead
+ * of catching NoSuchMethodException.
+ * <br>3.0.3 2006-01-08 Don't set Window alpha, when running on
+ * Java 1.4.2_05 on Mac OS X 10.3.5. Because this only has the effect of turning
+ * the background color of the Window to white.
+ * <br>3.0.2 2005-12-10 Method isOnActiveWindow() did not reliably
+ * return true.
+ * <br>3.0.1 2005-11-12 Fixed NPE in method repaint border.
+ * <br>3.0 2005-09-24 Removed all reflection helper methods. Moved Sheet
+ * helper methods out into class Sheets.
+ * <br>2.6 2005-09-17 Method isOnFocusedWindow returns true, if
+ * the window returns false on "getFocusableWindowState".
+ * <br>2.5 2005-03-13 Renamed method isFrameActive to isOnActiveFrame.
+ * <br>2.4 2004-12-28 Method createBufferdImage added. Method
+ * isOnActiveWindow() renamed to isFrameActive().
+ * <br>2.3 2004-12-14 Method getUI added.
+ * <br>2.2.1 2004-12-01 Methods setDragEnabled and getDragEnabled never
+ * worked because the attempted to get method objects on the wrong class.
+ * <br>2.2 2004-09-19 Refined algorithm of method isFrameActive.
+ * <br>2.1 2004-07-04 Methods repaintBorder, beginFont, endFont and
+ * isFocused added.
+ * <br>2.0 2004-04-27 Renamed from QuaquaGraphicUtils to QuaquaUtilities.
+ * Added method isFrameActive(Component).
+ * <br>1.1.1 2003-10-08 Diagnostic output to System.out removed.
+ * <br>1.1 2003-10-05 Methods getModifiersText and getModifiersUnicode
+ * added.
+ * <br>1.0 2003-07-19 Created.
+ */
+public class QuaquaUtilities extends BasicGraphicsUtils implements SwingConstants {
+ /** Prevent instance creation. */
+ private QuaquaUtilities() {
+ }
+
+ /*
+ * Convenience function for determining ComponentOrientation. Helps us
+ * avoid having Munge directives throughout the code.
+ */
+ public static boolean isLeftToRight( Component c ) {
+ return c.getComponentOrientation().isLeftToRight();
+ }
+
+ /**
+ * Draw a string with the graphics <code>g</code> at location
+ * (<code>x</code>, <code>y</code>)
+ * just like <code>g.drawString</code> would.
+ * The character at index <code>underlinedIndex</code>
+ * in text will be underlined. If <code>index</code> is beyond the
+ * bounds of <code>text</code> (including < 0), nothing will be
+ * underlined.
+ *
+ * @param g Graphics to draw with
+ * @param text String to draw
+ * @param underlinedIndex Index of character in text to underline
+ * @param x x coordinate to draw at
+ * @param y y coordinate to draw at
+ * @since 1.4
+ */
+ public static void drawStringUnderlineCharAt(Graphics g, String text,
+ int underlinedIndex, int x,int y) {
+ g.drawString(text,x,y);
+ if (underlinedIndex >= 0 && underlinedIndex < text.length() ) {
+ FontMetrics fm = g.getFontMetrics();
+ int underlineRectX = x + fm.stringWidth(text.substring(0,underlinedIndex));
+ int underlineRectY = y;
+ int underlineRectWidth = fm.charWidth(text.charAt(underlinedIndex));
+ int underlineRectHeight = 1;
+ g.fillRect(underlineRectX, underlineRectY + fm.getDescent() - 1,
+ underlineRectWidth, underlineRectHeight);
+ }
+ }
+ /**
+ * Returns index of the first occurrence of <code>mnemonic</code>
+ * within string <code>text</code>. Matching algorithm is not
+ * case-sensitive.
+ *
+ * @param text The text to search through, may be null
+ * @param mnemonic The mnemonic to find the character for.
+ * @return index into the string if exists, otherwise -1
+ */
+ static int findDisplayedMnemonicIndex(String text, int mnemonic) {
+ if (text == null || mnemonic == '\0') {
+ return -1;
+ }
+
+ char uc = Character.toUpperCase((char)mnemonic);
+ char lc = Character.toLowerCase((char)mnemonic);
+
+ int uci = text.indexOf(uc);
+ int lci = text.indexOf(lc);
+
+ if (uci == -1) {
+ return lci;
+ } else if(lci == -1) {
+ return uci;
+ } else {
+ return (lci < uci) ? lci : uci;
+ }
+ }
+
+ /**
+ * Returns true if the component is on a Dialog or a Frame, which is active,
+ * or if it is on a Window, which is focused.
+ * Always returns true, if the component has no parent window.
+ */
+ public static boolean isOnActiveWindow(Component c) {
+ // In the RootPaneUI, we set a client property on the whole component
+ // tree, if the ancestor Frame gets activated or deactivated.
+ if (c instanceof JComponent) {
+ Boolean value = (Boolean) ((JComponent) c).getClientProperty("Frame.active");
+ // Unfortunately, the value is not always reliable.
+ // Therefore we can only do a short circuit, if the value is true.
+ if (value != null && value) {
+ return true;
+ //return value.booleanValue();
+ }
+ }
+ /*
+ // This is how I would have implemented the code, if Quaqua would
+ // not be required to work an a Java 1.3 VM.
+ Window window = SwingUtilities.getWindowAncestor(c);
+ if (window == null) {
+ return true;
+ } else if ((window instanceof Frame) || (window instanceof Dialog)) {
+ return window.isActive();
+ } else {
+ if (window.getFocusableWindowState()) {
+ return window.isFocused();
+ } else {
+ return true;
+ }
+ }
+ */
+
+ // If we missed the client property or if it was false, we have to
+ // figure out the activation state on our own.
+ // The following code works from Java 1.3 onwards.
+ Window window = SwingUtilities.getWindowAncestor(c);
+ boolean isOnActiveWindow;
+ if (window == null) {
+ isOnActiveWindow = true;
+ } else if ((window instanceof Frame) || (window instanceof Dialog)) {
+ isOnActiveWindow = Methods.invokeGetter(window, "isActive", true);
+ } else {
+ if (Methods.invokeGetter(window, "getFocusableWindowState", true)) {
+ isOnActiveWindow = Methods.invokeGetter(window, "isFocused", true);
+ } else {
+ isOnActiveWindow = true;
+ }
+ }
+
+ // In case the activation property is true, we fix the value of the
+ // client property, so that we can do a short circuit next time.
+ if (isOnActiveWindow && (c instanceof JComponent)) {
+ ((JComponent) c).putClientProperty("Frame.active", isOnActiveWindow);
+ }
+ return isOnActiveWindow;
+ }
+
+ /**
+ * Returns a Mac OS X specific String describing the modifier key(s),
+ * such as "Shift", or "Ctrl+Shift".
+ *
+ * @return string a text description of the combination of modifier
+ * keys that were held down during the event
+ */
+ public static String getKeyModifiersText(int modifiers, boolean leftToRight) {
+ return getKeyModifiersUnicode(modifiers, leftToRight);
+ }
+ static String getKeyModifiersUnicode(int modifiers, boolean leftToRight) {
+ char[] cs = new char[4];
+ int count = 0;
+ if (leftToRight) {
+ if ((modifiers & InputEvent.CTRL_MASK) != 0)
+ cs[count++] = '\u2303'; // Unicode: UP ARROWHEAD
+ if ((modifiers & (InputEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK)) != 0)
+ cs[count++] = '\u2325'; // Unicode: OPTION KEY
+ if ((modifiers & InputEvent.SHIFT_MASK) != 0)
+ cs[count++] = '\u21e7'; // Unicode: UPWARDS WHITE ARROW
+ if ((modifiers & InputEvent.META_MASK) != 0)
+ cs[count++] = '\u2318'; // Unicode: PLACE OF INTEREST SIGN
+ } else {
+ if ((modifiers & InputEvent.META_MASK) != 0)
+ cs[count++] = '\u2318'; // Unicode: PLACE OF INTEREST SIGN
+ if ((modifiers & InputEvent.SHIFT_MASK) != 0)
+ cs[count++] = '\u21e7'; // Unicode: UPWARDS WHITE ARROW
+ if ((modifiers & (InputEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK)) != 0)
+ cs[count++] = '\u2325'; // Unicode: OPTION KEY
+ if ((modifiers & InputEvent.CTRL_MASK) != 0)
+ cs[count++] = '\u2303'; // Unicode: UP ARROWHEAD
+ }
+ return new String(cs, 0, count);
+ }
+
+ public static void repaintBorder(JComponent component) {
+ JComponent c = component;
+ Border border = null;
+ Container container = component.getParent();
+ if (container instanceof JViewport) {
+ c = (JComponent) container.getParent();
+ if (c != null) {
+ border = c.getBorder();
+ }
+ }
+ if (border == null) {
+ border = component.getBorder();
+ c = component;
+ }
+ if (border != null && c != null) {
+ int w = c.getWidth();
+ int h = c.getHeight();
+ Insets insets = c.getInsets();
+ c.repaint(0, 0, w, insets.top);
+ c.repaint(0, 0, insets.left, h);
+ c.repaint(0, h - insets.bottom, w, insets.bottom);
+ c.repaint(w - insets.right, 0, insets.right, h);
+ }
+ }
+ public static final Object beginGraphics(Graphics2D graphics2d) {
+ Object object = graphics2d.getRenderingHint(RenderingHints
+ .KEY_TEXT_ANTIALIASING);
+ graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ return object;
+ }
+
+ public static final void endGraphics(Graphics2D graphics2d, Object oldHints) {
+ if (oldHints != null) {
+ graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ oldHints);
+ }
+ }
+ public static final boolean isFocused(Component component) {
+ if (QuaquaUtilities.isOnActiveWindow(component)) {
+ Component c = component;
+ if (c instanceof JComponent) {
+ if (c instanceof JScrollPane) {
+ JViewport viewport = ((JScrollPane) component).getViewport();
+ if (viewport != null) {
+ c = viewport.getView();
+ }
+ }
+ if (c instanceof JTextComponent
+ && !((JTextComponent) c).isEditable()) {
+ return false;
+ }
+ return c != null
+ && (((JComponent) c).hasFocus() || ((JComponent) c).getClientProperty("Quaqua.drawFocusBorder") == Boolean.TRUE);
+ }
+ }
+ return false;
+ }
+
+ static boolean isHeadless() {
+ return Methods.invokeStaticGetter(GraphicsEnvironment.class, "isHeadless", false);
+ }
+
+ public static int getLeftSideBearing(Font f, String string) {
+ return (Integer) Methods.invokeStatic(
+ "com.sun.java.swing.SwingUtilities2", "getLeftSideBearing",
+ new Class[]{Font.class, String.class}, new Object[]{f, string},
+ new Integer(0));
+ }
+
+ /**
+ * Invoked when the user attempts an invalid operation,
+ * such as pasting into an uneditable <code>JTextField</code>
+ * that has focus. The default implementation beeps. Subclasses
+ * that wish different behavior should override this and provide
+ * the additional feedback.
+ *
+ * @param component Component the error occured in, may be null
+ * indicating the error condition is not directly
+ * associated with a <code>Component</code>.
+ */
+ static void provideErrorFeedback(Component component) {
+ Toolkit toolkit = null;
+ if (component != null) {
+ toolkit = component.getToolkit();
+ } else {
+ toolkit = Toolkit.getDefaultToolkit();
+ }
+ toolkit.beep();
+ } // provideErrorFeedback()
+
+ public static BufferedImage createBufferedImage(URL location) {
+ Image image = Toolkit.getDefaultToolkit().createImage(location);
+ BufferedImage buf;
+ if (image instanceof BufferedImage) {
+ buf = (BufferedImage) image;
+ } else {
+ loadImage(image);
+ //buf = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
+ buf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(image.getWidth(null), image.getHeight(null), Transparency.OPAQUE);
+
+ Graphics g = buf.getGraphics();
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+ image.flush();
+ }
+ return buf;
+ }
+ public static TexturePaint createTexturePaint(URL location) {
+ BufferedImage texture = createBufferedImage(location);
+ TexturePaint paint = new TexturePaint(texture, new Rectangle(0, 0, texture.getWidth(), texture.getHeight()));
+ return paint;
+ }
+
+
+ /**
+ * Loads the image, returning only when the image is loaded.
+ * @param image the image
+ */
+ private static void loadImage(Image image) {
+ Component component = new Component() {};
+ MediaTracker tracker = new MediaTracker(component);
+ synchronized (tracker) {
+ int id = 0;
+
+ tracker.addImage(image, id);
+ try {
+ tracker.waitForID(id, 0);
+ } catch (InterruptedException e) {
+ System.out.println("INTERRUPTED while loading Image");
+ }
+ int loadStatus = tracker.statusID(id, false);
+ tracker.removeImage(image, id);
+ }
+ }
+
+ /**
+ * Compute and return the location of the icons origin, the
+ * location of origin of the text baseline, and a possibly clipped
+ * version of the compound labels string. Locations are computed
+ * relative to the viewR rectangle.
+ * The JComponents orientation (LEADING/TRAILING) will also be taken
+ * into account and translated into LEFT/RIGHT values accordingly.
+ */
+ public static String layoutCompoundLabel(JComponent c,
+ FontMetrics fm,
+ String text,
+ Icon icon,
+ int verticalAlignment,
+ int horizontalAlignment,
+ int verticalTextPosition,
+ int horizontalTextPosition,
+ Rectangle viewR,
+ Rectangle iconR,
+ Rectangle textR,
+ int textIconGap) {
+ boolean orientationIsLeftToRight = true;
+ int hAlign = horizontalAlignment;
+ int hTextPos = horizontalTextPosition;
+
+ if (c != null) {
+ if (!(c.getComponentOrientation().isLeftToRight())) {
+ orientationIsLeftToRight = false;
+ }
+ }
+
+ // Translate LEADING/TRAILING values in horizontalAlignment
+ // to LEFT/RIGHT values depending on the components orientation
+ switch (horizontalAlignment) {
+ case LEADING:
+ hAlign = (orientationIsLeftToRight) ? LEFT : RIGHT;
+ break;
+ case TRAILING:
+ hAlign = (orientationIsLeftToRight) ? RIGHT : LEFT;
+ break;
+ }
+
+ // Translate LEADING/TRAILING values in horizontalTextPosition
+ // to LEFT/RIGHT values depending on the components orientation
+ switch (horizontalTextPosition) {
+ case LEADING:
+ hTextPos = (orientationIsLeftToRight) ? LEFT : RIGHT;
+ break;
+ case TRAILING:
+ hTextPos = (orientationIsLeftToRight) ? RIGHT : LEFT;
+ break;
+ }
+
+ return layoutCompoundLabelImpl(c,
+ fm,
+ text,
+ icon,
+ verticalAlignment,
+ hAlign,
+ verticalTextPosition,
+ hTextPos,
+ viewR,
+ iconR,
+ textR,
+ textIconGap);
+ }
+
+ /**
+ * Compute and return the location of the icons origin, the
+ * location of origin of the text baseline, and a possibly clipped
+ * version of the compound labels string. Locations are computed
+ * relative to the viewR rectangle.
+ * This layoutCompoundLabel() does not know how to handle LEADING/TRAILING
+ * values in horizontalTextPosition (they will default to RIGHT) and in
+ * horizontalAlignment (they will default to CENTER).
+ * Use the other version of layoutCompoundLabel() instead.
+ */
+ public static String layoutCompoundLabel(
+ FontMetrics fm,
+ String text,
+ Icon icon,
+ int verticalAlignment,
+ int horizontalAlignment,
+ int verticalTextPosition,
+ int horizontalTextPosition,
+ Rectangle viewR,
+ Rectangle iconR,
+ Rectangle textR,
+ int textIconGap) {
+ return layoutCompoundLabelImpl(null, fm, text, icon,
+ verticalAlignment,
+ horizontalAlignment,
+ verticalTextPosition,
+ horizontalTextPosition,
+ viewR, iconR, textR, textIconGap);
+ }
+
+ /**
+ * Compute and return the location of the icons origin, the
+ * location of origin of the text baseline, and a possibly clipped
+ * version of the compound labels string. Locations are computed
+ * relative to the viewR rectangle.
+ * This layoutCompoundLabel() does not know how to handle LEADING/TRAILING
+ * values in horizontalTextPosition (they will default to RIGHT) and in
+ * horizontalAlignment (they will default to CENTER).
+ * Use the other version of layoutCompoundLabel() instead.
+ *
+ * This is the same as SwingUtilities.layoutCompoundLabelImpl, except for
+ * the algorithm for clipping the text. If a text is too long, "..." are
+ * inserted at the middle of the text instead of at the end.
+ */
+ private static String layoutCompoundLabelImpl(
+ JComponent c,
+ FontMetrics fm,
+ String text,
+ Icon icon,
+ int verticalAlignment,
+ int horizontalAlignment,
+ int verticalTextPosition,
+ int horizontalTextPosition,
+ Rectangle viewR,
+ Rectangle iconR,
+ Rectangle textR,
+ int textIconGap) {
+ /* Initialize the icon bounds rectangle iconR.
+ */
+
+ if (icon != null) {
+ iconR.width = icon.getIconWidth();
+ iconR.height = icon.getIconHeight();
+ } else {
+ iconR.width = iconR.height = 0;
+ }
+
+ /* Initialize the text bounds rectangle textR. If a null
+ * or and empty String was specified we substitute "" here
+ * and use 0,0,0,0 for textR.
+ */
+
+ boolean textIsEmpty = (text == null) || text.equals("");
+ int lsb = 0;
+
+ View v = null;
+ if (textIsEmpty) {
+ textR.width = textR.height = 0;
+ text = "";
+ } else {
+ v = (c != null) ? (View) c.getClientProperty("html") : null;
+ if (v != null) {
+ textR.width = (int) v.getPreferredSpan(View.X_AXIS);
+ textR.height = (int) v.getPreferredSpan(View.Y_AXIS);
+ } else {
+ textR.width = SwingUtilities.computeStringWidth(fm,text);
+
+ lsb = getLeftSideBearing(fm.getFont(), text);
+ if (lsb < 0) {
+ // If lsb is negative, add it to the width, the
+ // text bounds will later be adjusted accordingly.
+ textR.width -= lsb;
+ }
+ textR.height = fm.getHeight();
+ }
+ }
+
+ /* Unless both text and icon are non-null, we effectively ignore
+ * the value of textIconGap. The code that follows uses the
+ * value of gap instead of textIconGap.
+ */
+
+ int gap = (textIsEmpty || (icon == null)) ? 0 : textIconGap;
+
+ if (!textIsEmpty) {
+
+ /* If the label text string is too wide to fit within the available
+ * space "..." and as many characters as will fit will be
+ * displayed instead.
+ */
+
+ int availTextWidth;
+
+ if (horizontalTextPosition == CENTER) {
+ availTextWidth = viewR.width;
+ } else {
+ availTextWidth = viewR.width - (iconR.width + gap);
+ }
+
+
+ if (textR.width > availTextWidth) {
+ if (v != null) {
+ textR.width = availTextWidth;
+ } else {
+ String clipString = "...";
+ int totalWidth = SwingUtilities.computeStringWidth(fm,clipString);
+ int nChars;
+ int len = text.length();
+ for(nChars = 0; nChars < len; nChars++) {
+ int charIndex = (nChars % 2 == 0) ? nChars / 2 : len - 1 - nChars / 2;
+ totalWidth += fm.charWidth(text.charAt(charIndex));
+ if (totalWidth > availTextWidth) {
+ break;
+ }
+ }
+ text = text.substring(0, nChars / 2) + clipString + text.substring(len - nChars / 2);
+ textR.width = SwingUtilities.computeStringWidth(fm,text);
+ }
+ }
+ }
+
+
+ /* Compute textR.x,y given the verticalTextPosition and
+ * horizontalTextPosition properties
+ */
+
+ if (verticalTextPosition == TOP) {
+ if (horizontalTextPosition != CENTER) {
+ textR.y = 0;
+ } else {
+ textR.y = -(textR.height + gap);
+ }
+ } else if (verticalTextPosition == CENTER) {
+ textR.y = (iconR.height / 2) - (textR.height / 2);
+ } else { // (verticalTextPosition == BOTTOM)
+ if (horizontalTextPosition != CENTER) {
+ textR.y = iconR.height - textR.height;
+ } else {
+ textR.y = (iconR.height + gap);
+ }
+ }
+
+ if (horizontalTextPosition == LEFT) {
+ textR.x = -(textR.width + gap);
+ } else if (horizontalTextPosition == CENTER) {
+ textR.x = (iconR.width / 2) - (textR.width / 2);
+ } else { // (horizontalTextPosition == RIGHT)
+ textR.x = (iconR.width + gap);
+ }
+
+ /* labelR is the rectangle that contains iconR and textR.
+ * Move it to its proper position given the labelAlignment
+ * properties.
+ *
+ * To avoid actually allocating a Rectangle, Rectangle.union
+ * has been inlined below.
+ */
+ int labelR_x = Math.min(iconR.x, textR.x);
+ int labelR_width = Math.max(iconR.x + iconR.width,
+ textR.x + textR.width) - labelR_x;
+ int labelR_y = Math.min(iconR.y, textR.y);
+ int labelR_height = Math.max(iconR.y + iconR.height,
+ textR.y + textR.height) - labelR_y;
+
+ int dx, dy;
+
+ if (verticalAlignment == TOP) {
+ dy = viewR.y - labelR_y;
+ } else if (verticalAlignment == CENTER) {
+ dy = (viewR.y + (viewR.height / 2)) - (labelR_y + (labelR_height / 2));
+ } else { // (verticalAlignment == BOTTOM)
+ dy = (viewR.y + viewR.height) - (labelR_y + labelR_height);
+ }
+
+ if (horizontalAlignment == LEFT) {
+ dx = viewR.x - labelR_x;
+ } else if (horizontalAlignment == RIGHT) {
+ dx = (viewR.x + viewR.width) - (labelR_x + labelR_width);
+ } else { // (horizontalAlignment == CENTER)
+ dx = (viewR.x + (viewR.width / 2)) -
+ (labelR_x + (labelR_width / 2));
+ }
+
+ /* Translate textR and glypyR by dx,dy.
+ */
+
+ textR.x += dx;
+ textR.y += dy;
+
+ iconR.x += dx;
+ iconR.y += dy;
+
+ if (lsb < 0) {
+ // lsb is negative. We previously adjusted the bounds by lsb,
+ // we now need to shift the x location so that the text is
+ // drawn at the right location. The result is textR does not
+ // line up with the actual bounds (on the left side), but we will
+ // have provided enough space for the text.
+ textR.width += lsb;
+ textR.x -= lsb;
+ }
+
+ return text;
+ }
+
+ public static void configureGraphics(Graphics gr) {
+ Graphics2D g = (Graphics2D) gr;
+ g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+ }
+
+ /** Copied from BasicLookAndFeel.
+ */
+ public static Component compositeRequestFocus(Component component) {
+ try {
+ if (component instanceof Container) {
+ Container container = (Container)component;
+ if (Methods.invokeGetter(container,"isFocusCycleRoot", false)) {
+
+ Object policy = Methods.invokeGetter(container,"getFocusTraversalPolicy", null);
+ Component comp = (Component) Methods.invoke(policy,"getDefaultComponent", Container.class, container);
+ if (comp!=null) {
+ comp.requestFocus();
+ return comp;
+ }
+ }
+ Container rootAncestor = (Container) Methods.invokeGetter(container, "getFocusCycleRootAncestor", null);
+ if (rootAncestor!=null) {
+ Object policy = Methods.invokeGetter(rootAncestor,"getFocusTraversalPolicy", null);
+ Component comp = (Component) Methods.invoke(policy,"getComponentAfter",
+ new Class[] {Container.class, Component.class},
+ new Object[] {rootAncestor, container});
+
+ if (comp!=null && SwingUtilities.isDescendingFrom(comp, container)) {
+ comp.requestFocus();
+ return comp;
+ }
+ }
+ }
+ } catch (NoSuchMethodException e) {
+ // ignore
+ }
+ if (Methods.invokeGetter(component,"isFocusable", true)) {
+ component.requestFocus();
+ return component;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/VisualMargin.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/VisualMargin.java
new file mode 100644
index 0000000..10896c6
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/VisualMargin.java
@@ -0,0 +1,185 @@
+/*
+ * @(#)VisualMargin.java 2.2 2005-10-01
+ *
+ * Copyright (c) 2004-2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import javax.swing.plaf.*;
+
+/**
+ * The VisualMargin is used to visually align components using bounds
+ * based on other criterias than the clip bounds of the component.
+ *
+ * For example: The clip bounds of a JButton includes its cast shadow and its
+ * focus ring. When we align the JButton with a JLabel, we want to align the
+ * baseline of the Text of the JButton with the text in the JLabel.
+ *
+ * The visual margin may be quite large. We allow to programmatically set a
+ * smaller margin using the client property "Quaqua.Component.margin".
+ *
+ * @author Werner Randelshofer
+ * @version 2.2 2005-10-01 Added method getVisualMargin.
+ * <br>2.1 2005-06-21 Implements UIResource.
+ * <br>2.0 2005-05-08 Renamed from BorderMargin to VisualMargin. Reworked
+ * API.
+ * <br>1.0 31 March 2005 Created.
+ */
+public class VisualMargin extends AbstractBorder implements UIResource {
+ /**
+ * Defines the margin from the clip bounds of the
+ * component to its visually perceived borderline.
+ */
+ private Insets layoutMargin;
+
+ /**
+ * The UIManager Property to be used for the default margin.
+ */
+ private String uiManagerPropertyName = "Component.visualMargin";
+ /**
+ * The Client Property to be used for the default margin.
+ */
+ private String propertyName = "Quaqua.Component.visualMargin";
+
+ private boolean isTopFixed, isLeftFixed, isBottomFixed, isRightFixed;
+
+ /**
+ * Creates a new VisualMargin.
+ */
+ public VisualMargin() {
+ layoutMargin = new Insets(0, 0, 0, 0);
+ }
+
+ /**
+ * The UIManager Property to be used for the default margin.
+ */
+ public void setPropertyName(String propertyName) {
+ // this.propertyName = propertyName;
+ }
+
+ /*
+ * Specifies SwingConstants.TOP, LEFT, BOTTOM, RIGHT to be fixed.
+ * Set to false to unfix.
+ */
+ public void setFixed(boolean top, boolean left, boolean bottom, boolean right) {
+ isTopFixed = top;
+ isLeftFixed = left;
+ isBottomFixed = bottom;
+ isRightFixed = right;
+ }
+ /**
+ * Creates a new VisualMargin.
+ *
+ * @param top Defines the margin from the clip bounds of the
+ * component to its visual bounds.
+ * @param left Defines the margin from the clip bounds of the
+ * component to its visual bounds.
+ * @param bottom Defines the margin from the clip bounds of the
+ * component to its visual bounds.
+ * @param right Defines the margin from the clip bounds of the
+ * component to its visual bounds.
+ */
+ public VisualMargin(int top, int left, int bottom, int right) {
+ layoutMargin = new Insets(top, left, bottom, right);
+ }
+ public VisualMargin(int top, int left, int bottom, int right, boolean ftop, boolean fleft, boolean fbottom, boolean fright) {
+ layoutMargin = new Insets(top, left, bottom, right);
+ isTopFixed = ftop;
+ isLeftFixed = fleft;
+ isBottomFixed = fbottom;
+ isRightFixed = fright;
+ }
+ public VisualMargin(boolean ftop, boolean fleft, boolean fbottom, boolean fright) {
+ layoutMargin = new Insets(0, 0, 0, 0);
+ isTopFixed = ftop;
+ isLeftFixed = fleft;
+ isBottomFixed = fbottom;
+ isRightFixed = fright;
+ }
+ /**
+ * Creates a new VisualMargin.
+ *
+ * @param layoutMargin Defines the margin from the clip bounds of the
+ * component to its visual bounds. The margin has usually negative values!
+ */
+ public VisualMargin(Insets layoutMargin) {
+ this.layoutMargin = layoutMargin;
+ }
+
+ public Insets getVisualMargin(Component c) {
+ Insets insets = new Insets(
+ layoutMargin.top,
+ layoutMargin.left,
+ layoutMargin.bottom,
+ layoutMargin.right
+ );
+
+ if (c instanceof JComponent) {
+ Insets componentMargin = (Insets) ((JComponent) c).getClientProperty(propertyName);
+ if (componentMargin == null && propertyName != null) {
+ componentMargin = UIManager.getInsets(uiManagerPropertyName);
+ }
+ if (componentMargin != null) {
+ if (! isTopFixed) insets.top = componentMargin.top;
+ if (! isLeftFixed) insets.left = componentMargin.left;
+ if (! isBottomFixed) insets.bottom = componentMargin.bottom;
+ if (! isRightFixed) insets.right = componentMargin.right;
+ }
+ }
+ return insets;
+ }
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return getBorderInsets(c, new Insets(0, 0, 0, 0));
+ }
+
+ /**
+ * Reinitializes the insets parameter with this Border's current Insets.
+ * @param c the component for which this border insets value applies
+ * @param insets the object to be reinitialized
+ * @return the <code>insets</code> object
+ */
+ @Override
+ public Insets getBorderInsets(Component c, Insets insets) {
+ return getVisualMargin(c, insets);
+ }
+ /**
+ * Reinitializes the insets parameter with this Border's current Insets.
+ * @param c the component for which this border insets value applies
+ * @param insets the object to be reinitialized
+ * @return the <code>insets</code> object
+ */
+ protected Insets getVisualMargin(Component c, Insets insets) {
+ insets.top = -layoutMargin.top;
+ insets.left = -layoutMargin.left;
+ insets.bottom = -layoutMargin.bottom;
+ insets.right = -layoutMargin.right;
+
+ if (c instanceof JComponent) {
+ Insets componentMargin = (Insets) ((JComponent) c).getClientProperty(propertyName);
+ if (componentMargin == null && propertyName != null) {
+ componentMargin = UIManager.getInsets(uiManagerPropertyName);
+ }
+ if (componentMargin != null) {
+ if (! isTopFixed) insets.top += componentMargin.top;
+ if (! isLeftFixed) insets.left += componentMargin.left;
+ if (! isBottomFixed) insets.bottom += componentMargin.bottom;
+ if (! isRightFixed) insets.right += componentMargin.right;
+ }
+ }
+ return insets;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/CMYKChooser.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/CMYKChooser.form
new file mode 100644
index 0000000..f7faa8f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/CMYKChooser.form
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <AuxValues>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-65"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="cyanLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.cmykCyanText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="cyanSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="100"/>
+ <Property name="minorTickSpacing" type="int" value="50"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JPanel" name="cyanFieldPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout">
+ <Property name="horizontalGap" type="int" value="0"/>
+ <Property name="verticalGap" type="int" value="0"/>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JTextField" name="cyanField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="cyanFieldFocusLost"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JLabel" name="cyanFieldLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="%"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Component class="javax.swing.JLabel" name="magentaLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.cmykMagentaText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="magentaSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="100"/>
+ <Property name="minorTickSpacing" type="int" value="50"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JPanel" name="magentaFieldPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout">
+ <Property name="horizontalGap" type="int" value="0"/>
+ <Property name="verticalGap" type="int" value="0"/>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JTextField" name="magentaField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="magentaFieldFocusLost"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JLabel" name="magentaFieldLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="%"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Component class="javax.swing.JLabel" name="yellowLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.cmykYellowText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="yellowSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="100"/>
+ <Property name="minorTickSpacing" type="int" value="50"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JPanel" name="yellowFieldPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="4" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout">
+ <Property name="horizontalGap" type="int" value="0"/>
+ <Property name="verticalGap" type="int" value="0"/>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JTextField" name="yellowField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="yellowFieldFocusLost"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JLabel" name="yellowFieldLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="%"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Component class="javax.swing.JLabel" name="blackLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.cmykBlackText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="blackSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="100"/>
+ <Property name="minorTickSpacing" type="int" value="50"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JPanel" name="blackFieldPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="6" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout">
+ <Property name="horizontalGap" type="int" value="0"/>
+ <Property name="verticalGap" type="int" value="0"/>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JTextField" name="blackField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="blackFieldFocusLost"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JLabel" name="blackFieldLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="%"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="springPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="100" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="1.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/CMYKChooser.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/CMYKChooser.java
new file mode 100644
index 0000000..3d314c9
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/CMYKChooser.java
@@ -0,0 +1,422 @@
+/*
+ * @(#)CMYKChooser.java 1.4 2006-04-23
+ *
+ * Copyright (c) 2004-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entecyan into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.VisualMargin;
+
+import javax.swing.Icon;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import javax.swing.colorchooser.AbstractColorChooserPanel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.UIResource;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Insets;
+
+/**
+ * A color chooser with CMYK color sliders.
+ *
+ * @author Werner Randelshofer
+ * @version 1.4 2006-04-23 Get labels directly from UIManager.
+ * <br>1.3 2005-11-22 Moved handler for text fields into separate class.
+ * <br>1.2.2 2005-11-22 If the user enters a non-numeric value, set the
+ * corresponding bounded range model to 0.
+ * <br>1.2.1 2005-11-07 Get "Labels" ResourceBundle from UIManager.
+ * <br>1.2 2005-09-05 Get font, spacing and icon from UIManager.
+ * <br>1.1.1 2005-05-23 Localized form.
+ * <br>1.0 29 March 2005 Created.
+ */
+public class CMYKChooser
+extends AbstractColorChooserPanel
+implements UIResource {
+ private ColorSliderModel ccModel;
+ private int updateRecursion = 0;
+
+
+ /** Creates new form. */
+ public CMYKChooser() {
+ initComponents();
+
+ //
+ Font font = UIManager.getFont("ColorChooser.font");
+ cyanLabel.setFont(font);
+ cyanSlider.setFont(font);
+ cyanField.setFont(font);
+ cyanFieldLabel.setFont(font);
+ magentaLabel.setFont(font);
+ magentaSlider.setFont(font);
+ magentaField.setFont(font);
+ magentaFieldLabel.setFont(font);
+ yellowLabel.setFont(font);
+ yellowSlider.setFont(font);
+ yellowField.setFont(font);
+ yellowFieldLabel.setFont(font);
+ blackLabel.setFont(font);
+ blackSlider.setFont(font);
+ blackField.setFont(font);
+ blackFieldLabel.setFont(font);
+ //
+ int textSliderGap = UIManager.getInt("ColorChooser.textSliderGap");
+ if (textSliderGap != 0) {
+ Border fieldBorder = new EmptyBorder(0,textSliderGap,0,0);
+ cyanFieldPanel.setBorder(fieldBorder);
+ magentaFieldPanel.setBorder(fieldBorder);
+ yellowFieldPanel.setBorder(fieldBorder);
+ blackFieldPanel.setBorder(fieldBorder);
+ }
+
+ ccModel = new NominalCMYKColorSliderModel();
+ /* Unfortunately the following does not work due to Java bug #4760025 as
+ * described at http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4760025
+ InputStream in = null;
+ try {
+ in = new FileInputStream("/System/Library/ColorSync/Profiles/Generic CMYK Profile.icc");
+ ccModel = new ICC_CMYKColorSliderModel(in);
+ } catch (IOException e) {
+ e.printStackTrace();
+ ccModel = new NominalCMYKColorSliderModel();
+ } finally {
+ try {
+ if (in != null) in.close();
+ } catch (IOException e) {
+ // suppress
+ }
+ }*/
+
+ ccModel.configureColorSlider(0, cyanSlider);
+ ccModel.configureColorSlider(1, magentaSlider);
+ ccModel.configureColorSlider(2, yellowSlider);
+ ccModel.configureColorSlider(3, blackSlider);
+ cyanField.setText(Integer.toString(cyanSlider.getValue()));
+ magentaField.setText(Integer.toString(magentaSlider.getValue()));
+ yellowField.setText(Integer.toString(yellowSlider.getValue()));
+ blackField.setText(Integer.toString(blackSlider.getValue()));
+ Insets borderMargin = (Insets) UIManager.getInsets("Component.visualMargin").clone();
+ borderMargin.left = 3 - borderMargin.left;
+ cyanFieldLabel.putClientProperty("Quaqua.Component.visualMargin",borderMargin);
+ magentaFieldLabel.putClientProperty("Quaqua.Component.visualMargin",borderMargin);
+ yellowFieldLabel.putClientProperty("Quaqua.Component.visualMargin",borderMargin);
+ blackFieldLabel.putClientProperty("Quaqua.Component.visualMargin",borderMargin);
+
+ new ColorSliderTextFieldHandler(cyanField, ccModel, 0);
+ new ColorSliderTextFieldHandler(magentaField, ccModel, 1);
+ new ColorSliderTextFieldHandler(yellowField, ccModel, 2);
+ new ColorSliderTextFieldHandler(blackField, ccModel, 3);
+
+ ccModel.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent evt) {
+ setColorToModel(ccModel.getColor());
+ }
+ });
+ cyanField.setMinimumSize(cyanField.getPreferredSize());
+ magentaField.setMinimumSize(magentaField.getPreferredSize());
+ yellowField.setMinimumSize(yellowField.getPreferredSize());
+ blackField.setMinimumSize(blackField.getPreferredSize());
+
+ VisualMargin bm = new VisualMargin(false,false,true,false);
+ cyanLabel.setBorder(bm);
+ magentaLabel.setBorder(bm);
+ yellowLabel.setBorder(bm);
+ blackLabel.setBorder(bm);
+ }
+
+ @Override
+ protected void buildChooser() {
+ }
+
+ @Override
+ public String getDisplayName() {
+ return UIManager.getString("ColorChooser.cmykSliders");
+ }
+
+ @Override
+ public Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.colorSlidersIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ @Override
+ public void updateChooser() {
+ if (updateRecursion == 0) {
+ updateRecursion++;
+ ccModel.setColor(getColorFromModel());
+ updateRecursion--;
+ }
+ }
+ public void setColorToModel(Color color) {
+ if (updateRecursion == 0) {
+ updateRecursion++;
+ getColorSelectionModel().setSelectedColor(color);
+ updateRecursion--;
+ }
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
+ private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+
+ cyanLabel = new javax.swing.JLabel();
+ cyanSlider = new javax.swing.JSlider();
+ cyanFieldPanel = new javax.swing.JPanel();
+ cyanField = new javax.swing.JTextField();
+ cyanFieldLabel = new javax.swing.JLabel();
+ magentaLabel = new javax.swing.JLabel();
+ magentaSlider = new javax.swing.JSlider();
+ magentaFieldPanel = new javax.swing.JPanel();
+ magentaField = new javax.swing.JTextField();
+ magentaFieldLabel = new javax.swing.JLabel();
+ yellowLabel = new javax.swing.JLabel();
+ yellowSlider = new javax.swing.JSlider();
+ yellowFieldPanel = new javax.swing.JPanel();
+ yellowField = new javax.swing.JTextField();
+ yellowFieldLabel = new javax.swing.JLabel();
+ blackLabel = new javax.swing.JLabel();
+ blackSlider = new javax.swing.JSlider();
+ blackFieldPanel = new javax.swing.JPanel();
+ blackField = new javax.swing.JTextField();
+ blackFieldLabel = new javax.swing.JLabel();
+ springPanel = new javax.swing.JPanel();
+
+ setLayout(new java.awt.GridBagLayout());
+
+ cyanLabel.setText(UIManager.getString("ColorChooser.cmykCyanText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(cyanLabel, gridBagConstraints);
+
+ cyanSlider.setMajorTickSpacing(100);
+ cyanSlider.setMinorTickSpacing(50);
+ cyanSlider.setPaintTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weightx = 1.0;
+ add(cyanSlider, gridBagConstraints);
+
+ cyanFieldPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 0, 0));
+
+ cyanField.setColumns(3);
+ cyanField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ cyanField.setText("0");
+ cyanField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ cyanFieldFocusLost(evt);
+ }
+ });
+
+ cyanFieldPanel.add(cyanField);
+
+ cyanFieldLabel.setText("%");
+ cyanFieldPanel.add(cyanFieldLabel);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(cyanFieldPanel, gridBagConstraints);
+
+ magentaLabel.setText(UIManager.getString("ColorChooser.cmykMagentaText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(magentaLabel, gridBagConstraints);
+
+ magentaSlider.setMajorTickSpacing(100);
+ magentaSlider.setMinorTickSpacing(50);
+ magentaSlider.setPaintTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weightx = 1.0;
+ add(magentaSlider, gridBagConstraints);
+
+ magentaFieldPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 0, 0));
+
+ magentaField.setColumns(3);
+ magentaField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ magentaField.setText("0");
+ magentaField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ magentaFieldFocusLost(evt);
+ }
+ });
+
+ magentaFieldPanel.add(magentaField);
+
+ magentaFieldLabel.setText("%");
+ magentaFieldPanel.add(magentaFieldLabel);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(magentaFieldPanel, gridBagConstraints);
+
+ yellowLabel.setText(UIManager.getString("ColorChooser.cmykYellowText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(yellowLabel, gridBagConstraints);
+
+ yellowSlider.setMajorTickSpacing(100);
+ yellowSlider.setMinorTickSpacing(50);
+ yellowSlider.setPaintTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weightx = 1.0;
+ add(yellowSlider, gridBagConstraints);
+
+ yellowFieldPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 0, 0));
+
+ yellowField.setColumns(3);
+ yellowField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ yellowField.setText("0");
+ yellowField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ yellowFieldFocusLost(evt);
+ }
+ });
+
+ yellowFieldPanel.add(yellowField);
+
+ yellowFieldLabel.setText("%");
+ yellowFieldPanel.add(yellowFieldLabel);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 4;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(yellowFieldPanel, gridBagConstraints);
+
+ blackLabel.setText(UIManager.getString("ColorChooser.cmykBlackText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(blackLabel, gridBagConstraints);
+
+ blackSlider.setMajorTickSpacing(100);
+ blackSlider.setMinorTickSpacing(50);
+ blackSlider.setPaintTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weightx = 1.0;
+ add(blackSlider, gridBagConstraints);
+
+ blackFieldPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 0, 0));
+
+ blackField.setColumns(3);
+ blackField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ blackField.setText("0");
+ blackField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ blackFieldFocusLost(evt);
+ }
+ });
+
+ blackFieldPanel.add(blackField);
+
+ blackFieldLabel.setText("%");
+ blackFieldPanel.add(blackFieldLabel);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 6;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(blackFieldPanel, gridBagConstraints);
+
+ springPanel.setLayout(new java.awt.BorderLayout());
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 100;
+ gridBagConstraints.weighty = 1.0;
+ add(springPanel, gridBagConstraints);
+
+ }// </editor-fold>//GEN-END:initComponents
+
+ private void blackFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_blackFieldFocusLost
+ blackField.setText(Integer.toString(ccModel.getBoundedRangeModel(3).getValue()));
+ }//GEN-LAST:event_blackFieldFocusLost
+
+ private void yellowFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_yellowFieldFocusLost
+ yellowField.setText(Integer.toString(ccModel.getBoundedRangeModel(2).getValue()));
+ }//GEN-LAST:event_yellowFieldFocusLost
+
+ private void magentaFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_magentaFieldFocusLost
+ magentaField.setText(Integer.toString(ccModel.getBoundedRangeModel(1).getValue()));
+ }//GEN-LAST:event_magentaFieldFocusLost
+
+ private void cyanFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_cyanFieldFocusLost
+ cyanField.setText(Integer.toString(ccModel.getBoundedRangeModel(0).getValue()));
+ }//GEN-LAST:event_cyanFieldFocusLost
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JTextField blackField;
+ private javax.swing.JLabel blackFieldLabel;
+ private javax.swing.JPanel blackFieldPanel;
+ private javax.swing.JLabel blackLabel;
+ private javax.swing.JSlider blackSlider;
+ private javax.swing.JTextField cyanField;
+ private javax.swing.JLabel cyanFieldLabel;
+ private javax.swing.JPanel cyanFieldPanel;
+ private javax.swing.JLabel cyanLabel;
+ private javax.swing.JSlider cyanSlider;
+ private javax.swing.JTextField magentaField;
+ private javax.swing.JLabel magentaFieldLabel;
+ private javax.swing.JPanel magentaFieldPanel;
+ private javax.swing.JLabel magentaLabel;
+ private javax.swing.JSlider magentaSlider;
+ private javax.swing.JPanel springPanel;
+ private javax.swing.JTextField yellowField;
+ private javax.swing.JLabel yellowFieldLabel;
+ private javax.swing.JPanel yellowFieldPanel;
+ private javax.swing.JLabel yellowLabel;
+ private javax.swing.JSlider yellowSlider;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorChooserMainPanel.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorChooserMainPanel.form
new file mode 100644
index 0000000..d659581
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorChooserMainPanel.form
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <NonVisualComponents>
+ <Component class="javax.swing.ButtonGroup" name="toolBarButtonGroup">
+ </Component>
+ </NonVisualComponents>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ <SubComponents>
+ <Container class="javax.swing.JToolBar" name="toolBar">
+ <Properties>
+ <Property name="floatable" type="boolean" value="false"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="North"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout"/>
+ </Container>
+ <Container class="javax.swing.JPanel" name="mainPanel">
+ <Properties>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
+ <EmptyBorder bottom="7" left="4" right="4" top="5"/>
+ </Border>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="Center"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ <SubComponents>
+ <Container class="javax.swing.JPanel" name="northPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="North"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ <SubComponents>
+ <Container class="javax.swing.JPanel" name="previewPanelHolder">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="Center"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ </Container>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="chooserPanelHolder">
+ <Properties>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
+ <EmptyBorder bottom="0" left="0" right="0" top="5"/>
+ </Border>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="Center"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>
+ </Container>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorChooserMainPanel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorChooserMainPanel.java
new file mode 100644
index 0000000..30b3696
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorChooserMainPanel.java
@@ -0,0 +1,166 @@
+/*
+ * @(#)ColorChooserMainPanel.java 1.4 2005-12-18
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.*;
+import javax.swing.colorchooser.AbstractColorChooserPanel;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.Methods;
+
+
+/**
+ * The main panel of the color chooser UI.
+ *
+ * @author Werner Randelshofer
+ * @version 1.4 2005-12-18 ColorPicker added. <br>
+ * 1.3 2005-09-05 Get font from UIManager. <br>
+ * 1.2 2005-08-27 Remember chooser panel. <br>
+ * 1.1 2005-04-23 Removed main method. <br>
+ * 1.0 30 March 2005 Created.
+ */
+public class ColorChooserMainPanel extends javax.swing.JPanel {
+ /**
+ * We store here the name of the last selected chooser. When the
+ * ColorChooserMainPanel is recreated multiple times in the same applicatin,
+ * the application 'remembers' which panel the user had opened before.
+ */
+ private static String lastSelectedChooserName = null;
+
+ /** Creates new form. */
+ public ColorChooserMainPanel() {
+ initComponents();
+ toolBar
+ .putClientProperty("Quaqua.ToolBar.isDividerDrawn",
+ Boolean.TRUE);
+
+ }
+
+ public void setPreviewPanel(JComponent c) {
+ previewPanelHolder.removeAll();
+ if (c != null) {
+ previewPanelHolder.add(c);
+ }
+ }
+
+ public void addColorChooserPanel(final AbstractColorChooserPanel ccp) {
+ final String displayName = ccp.getDisplayName();
+
+ if (displayName.equals("Color Picker")) {
+ northPanel.add(ccp, BorderLayout.WEST);
+ } else {
+ Icon displayIcon = ccp.getLargeDisplayIcon();
+ JToggleButton tb = new JToggleButton(null, displayIcon);
+ tb.setToolTipText(displayName);
+ Methods.invokeIfExists(tb, "setFocusable", false);
+ tb.setHorizontalTextPosition(SwingConstants.CENTER);
+ tb.setVerticalTextPosition(SwingConstants.BOTTOM);
+ tb.setFont(UIManager.getFont("ColorChooser.font"));
+ tb.putClientProperty("Quaqua.Button.style", "toolBarTab");
+ JPanel centerView = new JPanel(new BorderLayout());
+ centerView.add(ccp);
+ chooserPanelHolder.add(centerView, displayName);
+ toolBarButtonGroup.add(tb);
+ toolBar.add(tb);
+
+ if (toolBar.getComponentCount() == 1
+ || lastSelectedChooserName != null
+ && lastSelectedChooserName.equals(displayName)) {
+ tb.setSelected(true);
+ CardLayout cl = (CardLayout) chooserPanelHolder.getLayout();
+ cl.show(chooserPanelHolder, displayName);
+ }
+
+ tb.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent evt) {
+ if (evt.getStateChange() == ItemEvent.SELECTED) {
+ CardLayout cl = (CardLayout) chooserPanelHolder
+ .getLayout();
+ cl.show(chooserPanelHolder, displayName);
+ lastSelectedChooserName = displayName;
+ }
+ }
+ });
+ }
+ }
+
+ public void removeAllColorChooserPanels() {
+ Component[] tb = toolBar.getComponents();
+ for (int i = 0; i < tb.length; i++) {
+ if (tb[i] instanceof AbstractButton) {
+ toolBarButtonGroup.remove((AbstractButton) tb[i]);
+ }
+ }
+ toolBar.removeAll();
+ chooserPanelHolder.removeAll();
+
+ northPanel.removeAll();
+ northPanel.add(previewPanelHolder);
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ private void initComponents() {// GEN-BEGIN:initComponents
+ toolBarButtonGroup = new javax.swing.ButtonGroup();
+ toolBar = new javax.swing.JToolBar();
+ mainPanel = new javax.swing.JPanel();
+ northPanel = new javax.swing.JPanel();
+ previewPanelHolder = new javax.swing.JPanel();
+ chooserPanelHolder = new javax.swing.JPanel();
+
+ setLayout(new java.awt.BorderLayout());
+
+ toolBar.setFloatable(false);
+ add(toolBar, java.awt.BorderLayout.NORTH);
+
+ mainPanel.setLayout(new java.awt.BorderLayout());
+
+ mainPanel.setBorder(new javax.swing.border.EmptyBorder(
+ new java.awt.Insets(5, 4, 7, 4)));
+ northPanel.setLayout(new java.awt.BorderLayout());
+
+ previewPanelHolder.setLayout(new java.awt.BorderLayout());
+
+ northPanel.add(previewPanelHolder, java.awt.BorderLayout.CENTER);
+
+ mainPanel.add(northPanel, java.awt.BorderLayout.NORTH);
+
+ chooserPanelHolder.setLayout(new java.awt.CardLayout());
+
+ chooserPanelHolder.setBorder(new javax.swing.border.EmptyBorder(
+ new java.awt.Insets(5, 0, 0, 0)));
+ mainPanel.add(chooserPanelHolder, java.awt.BorderLayout.CENTER);
+
+ add(mainPanel, java.awt.BorderLayout.CENTER);
+
+ }// GEN-END:initComponents
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JPanel chooserPanelHolder;
+ private javax.swing.JPanel mainPanel;
+ private javax.swing.JPanel northPanel;
+ private javax.swing.JPanel previewPanelHolder;
+ private javax.swing.JToolBar toolBar;
+ private javax.swing.ButtonGroup toolBarButtonGroup;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorPalettesChooser.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorPalettesChooser.form
new file mode 100644
index 0000000..5f87fd9
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorPalettesChooser.form
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <AuxValues>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-109"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="paletteLabel">
+ <Properties>
+ <Property name="labelFor" type="java.awt.Component" editor="org.netbeans.modules.form.ComponentChooserEditor">
+ <ComponentRef name="paletteCombo"/>
+ </Property>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.list" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="-1" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JComboBox" name="paletteCombo">
+ <Events>
+ <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="paletteChanged"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="-1" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JScrollPane" name="paletteScrollPane">
+ <Properties>
+ <Property name="horizontalScrollBarPolicy" type="int" value="31"/>
+ <Property name="verticalScrollBarPolicy" type="int" value="22"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="-1" gridY="1" gridWidth="0" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="1.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JList" name="paletteList">
+ <Properties>
+ <Property name="selectionMode" type="int" value="0"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorPalettesChooser.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorPalettesChooser.java
new file mode 100644
index 0000000..efa300b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorPalettesChooser.java
@@ -0,0 +1,282 @@
+/*
+ * @(#)ColorPalettesChooser.java 1.1 2006-04-23
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import java.text.*;
+import javax.swing.*;
+import javax.swing.colorchooser.*;
+import javax.swing.border.*;
+import javax.swing.event.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.*;
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+
+import java.util.*;
+
+/**
+ * ColorPalettesChooser.
+ *
+ * @author Werner Randelshofer
+ * @version 1.1 2006-04-23 Get UIManager directly from UIManager.
+ * <br>1.0.1 2005-11-07 Load "UIManager" resource bundle from UIManager.
+ * <br>1.0 September 18, 2005 Created.
+ */
+public class ColorPalettesChooser extends AbstractColorChooserPanel implements UIResource {
+
+ /**
+ * We store here the name of the last selected color sliders panel.
+ * When the ColorSlidersChooser is recreated multiple times in the same
+ * panel, the application 'remembers' which panel the user had opened
+ * before.
+ */
+ private static int lastSelectedPalette = 0;
+
+ /**
+ * Creates a new instance.
+ */
+ public ColorPalettesChooser() {
+ initComponents();
+ //
+ Font font = UIManager.getFont("ColorChooser.font");
+ paletteLabel.setFont(font);
+ paletteCombo.setFont(font);
+ paletteScrollPane.setFont(font);
+ paletteList.setFont(font);
+ //
+ paletteList.setCellRenderer(new PaletteEntryCellRenderer());
+ //
+ DefaultComboBoxModel cbm = new DefaultComboBoxModel(loadPalettes());
+ paletteCombo.setModel(cbm);
+ //updatePaletteList();
+
+ paletteList.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ /**
+ * Called whenever the value of the selection changes.
+ * @param e the event that characterizes the change.
+ */
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ PaletteEntry entry = (PaletteEntry) paletteList.getSelectedValue();
+ if (entry != null) {
+ PaletteListModel lm = (PaletteListModel) paletteList.getModel();
+ lm.setClosestIndex(-1);
+ setColorToModel(entry.getColor());
+ }
+ }
+ });
+
+ paletteCombo.setSelectedIndex(lastSelectedPalette);
+
+ loadPalettes();
+ updatePaletteList();
+ }
+
+ /**
+ * @return Vector<PaletteListModel>.
+ */
+ protected Vector loadPalettes() {
+ Vector palettes = new Vector();
+
+ Color[] colors;
+ PaletteEntry[] entries;
+
+ colors = DefaultPalettes.APPLE_COLORS;
+ entries = new PaletteEntry[colors.length];
+ for (int i=0; i < colors.length; i++) {
+ entries[i] = new PaletteEntry(
+ UIManager.getString("ColorChooser.apple."+Integer.toHexString(0xff000000|colors[i].getRGB()).substring(2)),
+ colors[i]
+ );
+ }
+ palettes.add(new PaletteListModel(
+ UIManager.getString("ColorChooser.appleColors"),
+ MessageFormat.format(UIManager.getString("ColorChooser.profileContainsNColors"), new Object[] {UIManager.getString("ColorChooser.appleColors"), entries.length}),
+ entries)
+ );
+
+ colors = DefaultPalettes.CRAYONS;
+ entries = new PaletteEntry[colors.length];
+ for (int i=0; i < colors.length; i++) {
+ entries[i % 8 + colors.length - (i / 8) * 8 - 8] = new PaletteEntry(
+ UIManager.getString("ColorChooser.crayon."+Integer.toHexString(0xff000000|colors[i].getRGB()).substring(2)),
+ colors[i]
+ );
+ }
+ palettes.add(new PaletteListModel(
+ UIManager.getString("ColorChooser.crayons"),
+ MessageFormat.format(UIManager.getString("ColorChooser.profileContainsNColors"), new Object[] {UIManager.getString("ColorChooser.crayons"), entries.length}),
+ entries)
+ );
+
+ colors = DefaultPalettes.WEB_SAFE_COLORS;
+ entries = new PaletteEntry[colors.length];
+ for (int i=0; i < colors.length; i++) {
+ entries[i] = new PaletteEntry(
+ Integer.toHexString(0xff000000|colors[i].getRGB()).substring(2).toUpperCase(),
+ colors[i]
+ );
+ }
+ palettes.add(new PaletteListModel(
+ UIManager.getString("ColorChooser.webSafeColors"),
+ MessageFormat.format(UIManager.getString("ColorChooser.profileContainsNColors"), new Object[] {UIManager.getString("ColorChooser.webSafeColors"), entries.length}),
+ entries)
+ );
+
+ return palettes;
+ }
+
+ private void updatePaletteList() {
+ PaletteListModel palette = (PaletteListModel) paletteCombo.getSelectedItem();
+ paletteList.setModel(palette);
+ paletteCombo.setToolTipText(palette.getInfo());
+ updateChooser();
+ }
+
+ @Override
+ protected void buildChooser() {
+ }
+
+ @Override
+ public String getDisplayName() {
+ return UIManager.getString("ColorChooser.colorPalettes");
+ }
+
+ @Override
+ public javax.swing.Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.colorPalettesIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ @Override
+ public void updateChooser() {
+ Color color;
+ try {
+ color = getColorFromModel();
+ if (color == null) return;
+ } catch (NullPointerException e) {
+ return;
+ }
+
+ int rgb = color.getRGB() & 0xffffff;
+
+ // Return quickly if color is the same as selected entry
+ PaletteEntry entry = (PaletteEntry) paletteList.getSelectedValue();
+ if (entry != null && (entry.getColor().getRGB() & 0xffffff) == rgb) {
+ return;
+ }
+
+ // Search for entry and select it
+ PaletteListModel lm = (PaletteListModel) paletteList.getModel();
+ int i, n;
+ for (i=0, n = lm.getSize(); i < n; i++) {
+ entry = (PaletteEntry) lm.getElementAt(i);
+ if ((entry.getColor().getRGB() & 0xffffff) == rgb) {
+ break;
+ }
+ }
+ if (i < n) {
+ // Matching color found? Select it and scroll it to visible.
+ lm.setClosestIndex(-1);
+ paletteList.setSelectedIndex(i);
+ paletteList.scrollRectToVisible(paletteList.getCellBounds(i,i));
+ } else {
+ // No matching color found? Clear selection,
+ paletteList.clearSelection();
+ int closest = lm.computeClosestIndex(color);
+ lm.setClosestIndex(closest);
+ if (closest != -1) {
+ paletteList.scrollRectToVisible(paletteList.getCellBounds(closest,closest));
+ }
+ }
+ }
+
+ public void setColorToModel(Color color) {
+ getColorSelectionModel().setSelectedColor(color);
+ }
+
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
+ private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+
+ paletteLabel = new javax.swing.JLabel();
+ paletteCombo = new javax.swing.JComboBox();
+ paletteScrollPane = new javax.swing.JScrollPane();
+ paletteList = new javax.swing.JList();
+
+ setLayout(new java.awt.GridBagLayout());
+
+ paletteLabel.setLabelFor(paletteCombo);
+ paletteLabel.setText(UIManager.getString("ColorChooser.list"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridy = 0;
+ add(paletteLabel, gridBagConstraints);
+
+ paletteCombo.addItemListener(new java.awt.event.ItemListener() {
+ @Override
+ public void itemStateChanged(java.awt.event.ItemEvent evt) {
+ paletteChanged(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weightx = 1.0;
+ add(paletteCombo, gridBagConstraints);
+
+ paletteScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+ paletteScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
+ paletteList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
+ paletteScrollPane.setViewportView(paletteList);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.weighty = 1.0;
+ add(paletteScrollPane, gridBagConstraints);
+
+ }// </editor-fold>//GEN-END:initComponents
+
+ private void paletteChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_paletteChanged
+ updatePaletteList();
+ lastSelectedPalette = paletteCombo.getSelectedIndex();
+ }//GEN-LAST:event_paletteChanged
+
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JComboBox paletteCombo;
+ private javax.swing.JLabel paletteLabel;
+ private javax.swing.JList paletteList;
+ private javax.swing.JScrollPane paletteScrollPane;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorPicker.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorPicker.java
new file mode 100755
index 0000000..32dfdb9
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorPicker.java
@@ -0,0 +1,337 @@
+/*
+ * @(#)ColorPicker.java 1.1 2006-03-05
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import java.security.AccessControlException;
+
+import javax.swing.*;
+import javax.swing.colorchooser.AbstractColorChooserPanel;
+import javax.swing.plaf.IconUIResource;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
+
+/**
+ * ColorPicker.
+ *
+ * @author Werner Randelshofer
+ * @version 1.1 2006-03-06 Abort picker when the user presses the Escape-Key. <br>
+ * 1.0 December 18, 2005 Created.
+ */
+public class ColorPicker extends AbstractColorChooserPanel {
+ /**
+ * This frame is constantly moved to the current location of the mouse. This
+ * ensures that we can trap mouse clicks while the picker cursor is showing.
+ * Also, by tying the picker cursor to this frame, we ensure that the picker
+ * cursor (the magnifying glass) is shown.
+ */
+ private Dialog pickerFrame;
+
+ private Timer pickerTimer;
+
+ /**
+ * Holds the image of the picker cursor.
+ */
+ private BufferedImage cursorImage;
+ /**
+ * Graphics object used for drawing on the cursorImage.
+ */
+ private Graphics2D cursorGraphics;
+
+ /**
+ * The picker cursor.
+ */
+ private Cursor pickerCursor;
+
+ /**
+ * The hot spot of the cursor.
+ */
+ private Point hotSpot;
+
+ /**
+ * Offset from the hot spot of the pickerCursor to the pixel that we want to
+ * pick. We can't pick the color at the hotSpot of the cursor, because this
+ * point is obscured by the pickerFrame.
+ */
+ private Point pickOffset;
+
+ /**
+ * The magnifying glass image.
+ */
+ private BufferedImage magnifierImage;
+
+ /**
+ * The robot is used for creating screen captures.
+ */
+ private Robot robot;
+
+ private Color previousColor = Color.white;
+ private Point previousLoc = new Point();
+ private Point pickLoc = new Point();
+ private Point captureOffset = new Point();
+ private Rectangle captureRect;
+ private final static Color transparentColor = new Color(0, true);
+ private Rectangle zoomRect;
+ private Rectangle glassRect;
+
+ /**
+ * Creates a new instance.
+ */
+ public ColorPicker() {
+ // Try immediately to create a screen capture in order to fail quickly,
+ // when
+ // we can't provide a color picker functionality.
+ try {
+ robot = new Robot();
+ robot.createScreenCapture(new Rectangle(0, 0, 1, 1));
+ } catch (AWTException e) {
+ throw new AccessControlException("Unable to capture screen");
+ }
+
+ }
+
+ /**
+ * Gets the picker frame. If the frame does not yet exist, it is created
+ * along with all the other objects that are needed to make the picker work.
+ */
+ private Dialog getPickerFrame() {
+ if (pickerFrame == null) {
+ Window owner = SwingUtilities.getWindowAncestor(this);
+ if (owner instanceof Dialog) {
+ pickerFrame = new Dialog((Dialog) owner);
+ } else if (owner instanceof Frame) {
+ pickerFrame = new Dialog((Frame) owner);
+ } else {
+ pickerFrame = new Dialog(new JFrame());
+ }
+
+ pickerFrame.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent evt) {
+ pickFinish();
+ }
+
+ @Override
+ public void mouseExited(MouseEvent evt) {
+ updatePicker();
+ }
+ });
+
+ pickerFrame.addMouseMotionListener(new MouseMotionAdapter() {
+ @Override
+ public void mouseMoved(MouseEvent evt) {
+ updatePicker();
+ }
+ });
+ pickerFrame.setSize(3, 3);
+ pickerFrame.setUndecorated(true);
+ pickerFrame.setAlwaysOnTop(true);
+
+ pickerFrame.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ switch (e.getKeyCode()) {
+ case KeyEvent.VK_ESCAPE:
+ pickCancel();
+ break;
+ case KeyEvent.VK_ENTER:
+ pickFinish();
+ break;
+ }
+ }
+ });
+
+ magnifierImage = (BufferedImage) UIManager
+ .get("ColorChooser.colorPickerMagnifier");
+ glassRect = (Rectangle) UIManager
+ .get("ColorChooser.colorPickerGlassRect");
+ zoomRect = (Rectangle) UIManager
+ .get("ColorChooser.colorPickerZoomRect");
+ hotSpot = (Point) UIManager.get("ColorChooser.colorPickerHotSpot");// new
+ // Point(29,
+ // 29);
+ captureRect = new Rectangle((Rectangle) UIManager
+ .get("ColorChooser.colorPickerCaptureRect"));
+ pickOffset = (Point) UIManager
+ .get("ColorChooser.colorPickerPickOffset");
+ captureOffset = new Point(captureRect.x, captureRect.y);
+ cursorImage = getGraphicsConfiguration().createCompatibleImage(
+ magnifierImage.getWidth(), magnifierImage.getHeight(),
+ Transparency.TRANSLUCENT);
+ cursorGraphics = cursorImage.createGraphics();
+ cursorGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+
+ pickerTimer = new Timer(5, new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ updatePicker();
+ }
+ });
+ }
+ return pickerFrame;
+ }
+
+ /**
+ * Updates the color picker.
+ */
+ protected void updatePicker() {
+ if (pickerFrame != null && pickerFrame.isShowing()) {
+ PointerInfo info = MouseInfo.getPointerInfo();
+ if (info == null) return; //pointer not on a graphics device
+ Point mouseLoc = info.getLocation();
+ pickerFrame.setLocation(mouseLoc.x - pickerFrame.getWidth() / 2,
+ mouseLoc.y - pickerFrame.getHeight() / 2);
+
+ pickLoc.x = mouseLoc.x + pickOffset.x;
+ pickLoc.y = mouseLoc.y + pickOffset.y;
+
+ if (pickLoc.x >= 0 && pickLoc.y >= 0) {
+ Color c = robot.getPixelColor(pickLoc.x, pickLoc.y);
+ if (!c.equals(previousColor) || !mouseLoc.equals(previousLoc)) {
+ previousColor = c;
+ previousLoc = mouseLoc;
+
+ captureRect.setLocation(mouseLoc.x + captureOffset.x,
+ mouseLoc.y + captureOffset.y);
+ if (captureRect.x >= 0 && captureRect.y >= 0) {
+ BufferedImage capture = robot
+ .createScreenCapture(captureRect);
+
+ // Clear the cursor graphics
+ cursorGraphics.setComposite(AlphaComposite.Src);
+ cursorGraphics.setColor(transparentColor);
+ cursorGraphics.fillRect(0, 0, cursorImage.getWidth(),
+ cursorImage.getHeight());
+
+ // Fill the area for the zoomed screen capture with a
+ // non-transparent color (any non-transparent color does
+ // the job).
+ cursorGraphics.setColor(Color.red);
+ cursorGraphics.fillOval(glassRect.x, glassRect.y,
+ glassRect.width, glassRect.height);
+
+ // Paint the screen capture with a zoom factor of 5
+ cursorGraphics.setComposite(AlphaComposite.SrcIn);
+ cursorGraphics.drawImage(capture, zoomRect.x,
+ zoomRect.y, zoomRect.width, zoomRect.height,
+ this);
+
+ // Draw the magnifying glass image
+ cursorGraphics.setComposite(AlphaComposite.SrcOver);
+ cursorGraphics.drawImage(magnifierImage, 0, 0, this);
+
+ // We need to create a new subImage. This forces that
+ // the color picker uses the new imagery.
+ BufferedImage subImage = cursorImage
+ .getSubimage(0, 0, cursorImage.getWidth(),
+ cursorImage.getHeight());
+ pickerFrame.setCursor(getToolkit().createCustomCursor(
+ cursorImage, hotSpot, "ColorPicker"));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ private void initComponents() {// GEN-BEGIN:initComponents
+ pickerButton = new javax.swing.JButton();
+
+ setLayout(new java.awt.BorderLayout());
+
+ pickerButton.setBorderPainted(false);
+ pickerButton.setMargin(new java.awt.Insets(0, 0, 0, 0));
+ pickerButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ pickBegin(evt);
+ }
+ });
+
+ add(pickerButton, java.awt.BorderLayout.CENTER);
+
+ }// GEN-END:initComponents
+
+ private void pickBegin(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_pickBegin
+ getPickerFrame();
+ pickerTimer.start();
+ getPickerFrame().setVisible(true);
+ }// GEN-LAST:event_pickBegin
+
+ protected void pickFinish() {
+ pickerTimer.stop();
+ pickerFrame.setVisible(false);
+ PointerInfo info = MouseInfo.getPointerInfo();
+ if (info == null) return; //pointer not on a graphics device
+
+ Point loc = info.getLocation();
+ Color c = robot.getPixelColor(loc.x + pickOffset.x, loc.y
+ + pickOffset.y);
+ getColorSelectionModel().setSelectedColor(c);
+ }
+
+ protected void pickCancel() {
+ pickerTimer.stop();
+ pickerFrame.setVisible(false);
+ }
+
+ @Override
+ protected void buildChooser() {
+ initComponents();
+ pickerButton.setIcon(new TransitionAwareIcon(pickerButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ return new IconUIResource(SubstanceImageCreator
+ .getSearchIcon(15, scheme, pickerButton
+ .getComponentOrientation()
+ .isLeftToRight()));
+ }
+ }, "ColorChooser.colorPickerIcon"));
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Color Picker";
+ }
+
+ @Override
+ public Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.colorPickerIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ @Override
+ public void updateChooser() {
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton pickerButton;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSliderModel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSliderModel.java
new file mode 100644
index 0000000..691a345
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSliderModel.java
@@ -0,0 +1,196 @@
+/*
+ * @(#)ColorSliderModel.java 1.0 May 22, 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import javax.swing.DefaultBoundedRangeModel;
+import javax.swing.JSlider;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.Color;
+import java.util.Iterator;
+import java.util.LinkedList;
+/**
+ * Abstract super class for ColorModels which can be used in conjunction with
+ * ColorSliderUI user interface delegates.
+ * <p>
+ * Colors are represented as arrays of color components represented as
+ * BoundedRangeModel's. Each BoundedRangeModel can be visualized using a JSlider
+ * having a ColorSliderUI.
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 May 22, 2005 Created.
+ */
+public abstract class ColorSliderModel {
+ /**
+ * JSlider's associated to this ColorSliderModel.
+ */
+ private LinkedList sliders = new LinkedList();
+ /**
+ * ChangeListener's listening to changes in this ColorSliderModel.
+ */
+ private LinkedList listeners = new LinkedList();
+
+ /**
+ * Components of the color model.
+ */
+ protected DefaultBoundedRangeModel[] components;
+
+ /**
+ * Speed optimization. This way, we do not need to create a new array
+ * for each invocation of method getInterpolatedRGB().
+ * Note: This variable must not use in reentrant methods.
+ */
+ protected int[] values;
+
+ /**
+ * Creates a new ColorSliderModel with an array of BoundedRangeModel's
+ * for the color components.
+ */
+ protected ColorSliderModel(DefaultBoundedRangeModel[] components) {
+ this.components = components;
+ values = new int[components.length];
+
+ for (int i=0; i < components.length; i++) {
+ final int componentIndex = i;
+ components[i].addChangeListener(
+ new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ fireColorChanged(componentIndex);
+ fireStateChanged();
+ }
+ });
+ }
+ }
+
+ /**
+ * Configures a JSlider for this ColorSliderModel.
+ * If the JSlider is already configured for another ColorSliderModel,
+ * it is unconfigured first.
+ */
+ public void configureColorSlider(int component, JSlider slider) {
+ if (slider.getClientProperty("ColorSliderModel") != null) {
+ ((ColorSliderModel) slider.getClientProperty("ColorSliderModel"))
+ .unconfigureColorSlider(slider);
+ }
+ if ( ! (slider.getUI() instanceof ColorSliderUI)) {
+ slider.setUI(new ColorSliderUI(slider));
+ slider.createStandardLabels(16);
+ }
+ slider.setModel(getBoundedRangeModel(component));
+ slider.putClientProperty("ColorSliderModel", this);
+ slider.putClientProperty("ColorComponentIndex", component);
+ addColorSlider(slider);
+ }
+
+ /**
+ * Unconfigures a JSlider from this ColorSliderModel.
+ */
+ public void unconfigureColorSlider(JSlider slider) {
+ if (slider.getClientProperty("ColorSliderModel") == this) {
+ // XXX - This creates a NullPointerException ??
+ //slider.setUI((SliderUI) UIManager.getUI(slider));
+ slider.setModel(new DefaultBoundedRangeModel());
+ slider.putClientProperty("ColorSliderModel", null);
+ slider.putClientProperty("ColorComponentIndex", null);
+ removeColorSlider(slider);
+ }
+ }
+
+ /**
+ * Returns the number of components of this color component model.
+ */
+ public int getComponentCount() {
+ return components.length;
+ }
+ /**
+ * Returns the bounded range model of the specified color component.
+ */
+ public DefaultBoundedRangeModel getBoundedRangeModel(int component) {
+ return components[component];
+ }
+ /**
+ * Returns the value of the specified color component.
+ */
+ public int getValue(int component) {
+ return components[component].getValue();
+ }
+ /**
+ * Sets the value of the specified color component.
+ */
+ public void setValue(int component, int value) {
+ components[component].setValue(value);
+ }
+
+ /**
+ * Returns an interpolated RGB value by using the values of the color
+ * components of this ColorSliderModel except for the component specified
+ * as an argument. For this component the ratio between zero
+ * and the maximum of its BoundedRangeModel is used.
+ */
+ public int getInterpolatedRGB(int component, float ratio) {
+ for (int i=0, n = getComponentCount(); i < n; i++) {
+ values[i] = components[i].getValue();
+ }
+ values[component] = (int) (ratio * components[component].getMaximum());
+ return toRGB(values);
+ }
+
+ protected void addColorSlider(JSlider slider) {
+ sliders.add(slider);
+ }
+ protected void removeColorSlider(JSlider slider) {
+ sliders.remove(slider);
+ }
+ public void addChangeListener(ChangeListener l) {
+ listeners.add(l);
+ }
+ public void removeChangeListener(ChangeListener l) {
+ listeners.remove(l);
+ }
+
+
+ protected void fireColorChanged(int componentIndex) {
+ Integer index = componentIndex;
+ Color value = getColor();
+ for (Iterator i = sliders.iterator(); i.hasNext(); ) {
+ JSlider slider = (JSlider) i.next();
+ slider.putClientProperty("ColorComponentChange", index);
+ slider.putClientProperty("ColorComponentValue", value);
+ }
+ }
+ public void fireStateChanged() {
+ ChangeEvent event = new ChangeEvent(this);
+ for (Iterator i=listeners.iterator(); i.hasNext(); ) {
+ ChangeListener l = (ChangeListener) i.next();
+ l.stateChanged(event);
+ }
+ }
+
+ public Color getColor() {
+ return new Color(getRGB());
+ }
+
+ public void setColor(Color color) {
+ int rgb = color.getRGB();
+ if (rgb != getRGB()) {
+ setRGB(rgb);
+ }
+ }
+
+ public abstract void setRGB(int rgb);
+ public abstract int getRGB();
+ public abstract int toRGB(int[] values);
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSliderTextFieldHandler.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSliderTextFieldHandler.java
new file mode 100644
index 0000000..ef48335
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSliderTextFieldHandler.java
@@ -0,0 +1,73 @@
+/*
+ * @(#)ColorSliderTextFieldHandler.java 1.0 November 22, 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+/**
+ * This handler adjusts the value of a component in the color slider model,
+ * when the user enters text into the text field.
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 November 22, 2005 Created.
+ */
+public class ColorSliderTextFieldHandler implements DocumentListener, ChangeListener {
+ private JTextField textField;
+ private ColorSliderModel ccModel;
+ private int component;
+
+ public ColorSliderTextFieldHandler(JTextField textField, ColorSliderModel ccModel, int component) {
+ this.textField = textField;
+ this.ccModel = ccModel;
+ this.component = component;
+
+ textField.getDocument().addDocumentListener(this);
+ ccModel.getBoundedRangeModel(component).addChangeListener(this);
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent evt) {
+ docChanged();
+ }
+ @Override
+ public void removeUpdate(DocumentEvent evt) {
+ docChanged();
+ }
+ @Override
+ public void insertUpdate(DocumentEvent evt) {
+ docChanged();
+ }
+ private void docChanged() {
+ if (textField.hasFocus()) {
+ BoundedRangeModel brm = ccModel.getBoundedRangeModel(component);
+ try {
+ int value = Integer.decode(textField.getText());
+ if (brm.getMinimum() <= value && value <= brm.getMaximum()) {
+ brm.setValue(value);
+ }
+ } catch (NumberFormatException e) {
+ // Don't change value if it isn't numeric.
+ }
+ }
+ }
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (! textField.hasFocus()) {
+ textField.setText(Integer.toString(ccModel.getBoundedRangeModel(component).getValue()));
+ }
+ }
+}
+
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSliderUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSliderUI.java
new file mode 100644
index 0000000..c430798
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSliderUI.java
@@ -0,0 +1,586 @@
+/*
+ * @(#)ColorSliderUI.java 1.0.3 2005-09-11
+ *
+ * Copyright (c) 2004 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.QuaquaUtilities;
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.VisualMargin;
+import org.pushingpixels.substance.internal.utils.RolloverControlListener;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicSliderUI;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+/**
+ * A UI delegate for color sliders. The track of the slider visualizes how
+ * changing the value of the slider affects the color.
+ *
+ *
+ * @author Werner Randelshofer
+ * @version 1.0.3 2005-09-11 Tweaked layout and drawing code. <br>
+ * 1.0.2 2005-08-28 Color track must always be regenerated if the
+ * snapToTicks property changes. <br>
+ * 1.0.1 2005-04-18 Fixed an undesired shift of the track on the
+ * x-axis. <br>
+ * 1.0 29 March 2005 Created.
+ */
+public class ColorSliderUI extends BasicSliderUI implements TransitionAwareUI {
+ private final static Color foreground = new Color(0x949494);
+ private final static Color trackBackground = new Color(0xffffff);
+
+ protected Integer componentIndex;
+ protected ColorSliderModel colorSliderModel;
+
+ /**
+ * Surrogate button model for tracking the thumb transitions.
+ */
+ private ButtonModel thumbModel;
+
+ /**
+ * Listener for transition animations.
+ */
+ private RolloverControlListener substanceRolloverListener;
+
+ /**
+ * Listener on property change events.
+ */
+ private PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Listener for transition animations.
+ */
+ protected StateTransitionTracker stateTransitionTracker;
+
+ private static final Dimension PREFERRED_HORIZONTAL_SIZE = new Dimension(
+ 36, 16);
+ private static final Dimension PREFERRED_VERTICAL_SIZE = new Dimension(26,
+ 100);
+ private static final Dimension MINIMUM_HORIZONTAL_SIZE = new Dimension(36,
+ 16);
+ private static final Dimension MINIMUM_VERTICAL_SIZE = new Dimension(26, 36);
+
+ /** Creates a new instance. */
+ @SuppressWarnings("UseOfObsoleteCollectionType")
+ public ColorSliderUI(JSlider b) {
+ super(b);
+ this.thumbModel = new DefaultButtonModel();
+ this.thumbModel.setArmed(false);
+ this.thumbModel.setSelected(false);
+ this.thumbModel.setPressed(false);
+ this.thumbModel.setRollover(false);
+ this.thumbModel.setEnabled(b.isEnabled());
+
+ this.stateTransitionTracker = new StateTransitionTracker(b,
+ this.thumbModel);
+
+ b.setLabelTable(new java.util.Hashtable());
+ }
+
+ public static ComponentUI createUI(JComponent b) {
+ return new ColorSliderUI((JSlider) b);
+ }
+
+ @Override
+ protected void installDefaults(JSlider slider) {
+ super.installDefaults(slider);
+ focusInsets = new Insets(0, 0, 0, 0);
+ slider.setOpaque(false);
+ if (slider.getOrientation() == JSlider.HORIZONTAL) {
+ slider.setBorder(new VisualMargin(0, 1, -1, 1));
+ } else {
+ slider.setBorder(new VisualMargin(0, 0, 0, 1));
+ }
+ // slider.setRequestFocusEnabled(QuaquaManager.getBoolean("Slider.requestFocusEnabled"));
+ slider.setRequestFocusEnabled(true);
+ }
+
+ @Override
+ protected void installListeners(final JSlider slider) {
+ super.installListeners(slider);
+
+ this.substanceRolloverListener = new RolloverControlListener(this,
+ this.thumbModel);
+ slider.addMouseListener(this.substanceRolloverListener);
+ slider.addMouseMotionListener(this.substanceRolloverListener);
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("enabled".equals(evt.getPropertyName())) {
+ thumbModel.setEnabled(slider.isEnabled());
+ }
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ slider.updateUI();
+ }
+ });
+ }
+ }
+ };
+ this.slider
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+
+ this.stateTransitionTracker.registerModelListeners();
+ this.stateTransitionTracker.registerFocusListeners();
+ }
+
+ @Override
+ protected void uninstallListeners(JSlider slider) {
+ super.uninstallListeners(slider);
+
+ slider.removeMouseListener(this.substanceRolloverListener);
+ slider.removeMouseMotionListener(this.substanceRolloverListener);
+ this.substanceRolloverListener = null;
+
+ slider
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ this.stateTransitionTracker.unregisterModelListeners();
+ this.stateTransitionTracker.unregisterFocusListeners();
+ }
+
+ @Override
+ protected Dimension getThumbSize() {
+ Icon thumb = getThumbIcon();
+ return new Dimension(thumb.getIconWidth(), thumb.getIconHeight());
+ }
+
+ @Override
+ public Dimension getPreferredHorizontalSize() {
+ return PREFERRED_HORIZONTAL_SIZE;
+ }
+
+ @Override
+ public Dimension getPreferredVerticalSize() {
+ return PREFERRED_VERTICAL_SIZE;
+ }
+
+ @Override
+ public Dimension getMinimumHorizontalSize() {
+ return MINIMUM_HORIZONTAL_SIZE;
+ }
+
+ @Override
+ public Dimension getMinimumVerticalSize() {
+ return MINIMUM_VERTICAL_SIZE;
+ }
+
+ @Override
+ protected void calculateThumbLocation() {
+ super.calculateThumbLocation();
+ if (slider.getOrientation() == JSlider.HORIZONTAL) {
+ thumbRect.y -= 1;
+ } else {
+ thumbRect.x -= 1;
+ }
+ }
+
+ /*
+ * public void paint( Graphics g, JComponent c ) { g.setColor(Color.green);
+ * g.fillRect(0,0,c.getWidth(), c.getHeight()); super.paint(g,c); }
+ */
+ protected Icon getThumbIcon() {
+ if (slider.getOrientation() == JSlider.HORIZONTAL) {
+ return UIManager.getIcon("Slider.upThumbSmall");
+ } else {
+ return UIManager.getIcon("Slider.leftThumbSmall");
+ }
+ }
+
+ @Override
+ public void paintThumb(Graphics g) {
+ Rectangle knobBounds = thumbRect;
+ int w = knobBounds.width;
+ int h = knobBounds.height;
+
+ getThumbIcon().paintIcon(slider, g, knobBounds.x, knobBounds.y);
+ /*
+ * g.setColor(Color.green); ((Graphics2D) g).draw(knobBounds);
+ */
+ }
+
+ @Override
+ public void paintTrack(Graphics g) {
+ int cx, cy, cw, ch;
+ int pad;
+
+ Rectangle trackBounds = trackRect;
+ if (slider.getOrientation() == JSlider.HORIZONTAL) {
+ pad = trackBuffer;// - thumbRect.width / 2 + 2;
+ cx = trackBounds.x - pad + 1;
+ cy = trackBounds.y;
+ // cy = (trackBounds.height / 2) - 4;
+ cw = trackBounds.width + pad * 2 - 2;
+ ch = trackBounds.height;
+ } else {
+ pad = trackBuffer;
+ // cx = (trackBounds.width / 2) - 4;
+ // cx = (trackBounds.width / 2);
+ // cx = thumbRect.x + 2;
+ cx = trackBounds.x;
+ // cy = pad;
+ cy = contentRect.y + 2;
+ cw = trackBounds.width - 1;
+ // ch = trackBounds.height;
+ ch = trackBounds.height + pad * 2 - 5;
+ }
+ g.setColor(trackBackground);
+ g.fillRect(cx, cy, cw, ch);
+ g.setColor(foreground);
+ g.drawRect(cx, cy, cw - 1, ch - 1);
+ paintColorTrack(g, cx + 2, cy + 2, cw - 4, ch - 4, trackBuffer);
+ }
+
+ @Override
+ public void paintTicks(Graphics g) {
+ Rectangle tickBounds = tickRect;
+ int i;
+ int maj, min, max;
+ int w = tickBounds.width;
+ int h = tickBounds.height;
+ int centerEffect, tickHeight;
+ /*
+ * g.setColor(slider.getBackground()); g.fillRect(tickBounds.x,
+ * tickBounds.y, tickBounds.width, tickBounds.height);
+ */
+ g.setColor(foreground);
+
+ if (slider.getOrientation() == JSlider.HORIZONTAL) {
+ g.translate(0, tickBounds.y);
+
+ int value = slider.getMinimum();
+ int xPos;
+
+ if (slider.getMinorTickSpacing() > 0) {
+ while (value <= slider.getMaximum()) {
+ xPos = xPositionForValue(value);
+ paintMinorTickForHorizSlider(g, tickBounds, xPos);
+ value += slider.getMinorTickSpacing();
+ }
+ }
+
+ if (slider.getMajorTickSpacing() > 0) {
+ value = slider.getMinimum();
+
+ while (value <= slider.getMaximum()) {
+ xPos = xPositionForValue(value);
+ paintMajorTickForHorizSlider(g, tickBounds, xPos);
+ value += slider.getMajorTickSpacing();
+ }
+ }
+
+ g.translate(0, -tickBounds.y);
+ } else {
+ g.translate(tickBounds.x, 0);
+
+ int value = slider.getMinimum();
+ int yPos;
+
+ if (slider.getMinorTickSpacing() > 0) {
+ int offset = 0;
+ if (!QuaquaUtilities.isLeftToRight(slider)) {
+ offset = tickBounds.width - tickBounds.width / 2;
+ g.translate(offset, 0);
+ }
+
+ while (value <= slider.getMaximum()) {
+ yPos = yPositionForValue(value);
+ paintMinorTickForVertSlider(g, tickBounds, yPos);
+ value += slider.getMinorTickSpacing();
+ }
+
+ if (!QuaquaUtilities.isLeftToRight(slider)) {
+ g.translate(-offset, 0);
+ }
+ }
+
+ if (slider.getMajorTickSpacing() > 0) {
+ value = slider.getMinimum();
+ if (!QuaquaUtilities.isLeftToRight(slider)) {
+ g.translate(2, 0);
+ }
+
+ while (value <= slider.getMaximum()) {
+ yPos = yPositionForValue(value);
+ paintMajorTickForVertSlider(g, tickBounds, yPos);
+ value += slider.getMajorTickSpacing();
+ }
+
+ if (!QuaquaUtilities.isLeftToRight(slider)) {
+ g.translate(-2, 0);
+ }
+ }
+ g.translate(-tickBounds.x, 0);
+ }
+ /*
+ * g.setColor(Color.red); ((Graphics2D) g).draw(tickBounds);
+ */
+ }
+
+ @Override
+ protected void paintMajorTickForHorizSlider(Graphics g,
+ Rectangle tickBounds, int x) {
+ g.drawLine(x, 0, x, tickBounds.height - 1);
+ }
+
+ @Override
+ protected void paintMinorTickForHorizSlider(Graphics g,
+ Rectangle tickBounds, int x) {
+ // g.drawLine( x, 0, x, tickBounds.height / 2 - 1 );
+ g.drawLine(x, 0, x, tickBounds.height - 1);
+ }
+
+ @Override
+ protected void paintMinorTickForVertSlider(Graphics g,
+ Rectangle tickBounds, int y) {
+ g.drawLine(tickBounds.width / 2, y, tickBounds.width / 2 - 1, y);
+ }
+
+ @Override
+ protected void paintMajorTickForVertSlider(Graphics g,
+ Rectangle tickBounds, int y) {
+ g.drawLine(0, y, tickBounds.width - 1, y);
+ }
+
+ @Override
+ public void paintFocus(Graphics g) {
+ }
+
+ public void paintColorTrack(Graphics g, int x, int y, int width,
+ int height, int buffer) {
+ int x2 = x;
+ int y2 = y;
+ if (slider.getOrientation() == JSlider.HORIZONTAL) {
+ x2 += width;
+ } else {
+ y2 += height;
+ }
+
+ if (componentIndex == null) {
+ componentIndex = (Integer) slider.getClientProperty("ColorComponentIndex");
+ }
+ if (colorSliderModel == null) {
+ colorSliderModel = (ColorSliderModel) slider.getClientProperty("ColorSliderModel");
+ }
+
+
+ float[] rgbRatios;
+ if (slider.getOrientation() == JSlider.HORIZONTAL) {
+ rgbRatios = new float[] {0.0f, 1.0f};
+ } else {
+ rgbRatios = new float[] {1.0f, 0.0f};
+ }
+ Graphics2D gg = (Graphics2D) g.create();
+ gg.setPaint(new LinearGradientPaint(x, y, x2, y2,
+ new float[] {0f, 1.0f},
+ new Color[] { new Color(colorSliderModel.getInterpolatedRGB(componentIndex, rgbRatios[0]), true),
+ new Color(colorSliderModel.getInterpolatedRGB(componentIndex, rgbRatios[1]))}));
+ gg.fillRect(x, y, width, height);
+ gg.dispose();
+ }
+
+ @Override
+ protected void calculateTrackRect() {
+ int centerSpacing; // used to center sliders added using
+ // BorderLayout.CENTER (bug 4275631)
+ if (slider.getOrientation() == JSlider.HORIZONTAL) {
+ centerSpacing = thumbRect.height;
+ if (slider.getPaintTicks()) {
+ centerSpacing += getTickLength();
+ }
+ if (slider.getPaintLabels()) {
+ centerSpacing += getHeightOfTallestLabel();
+ }
+ trackRect.x = contentRect.x + trackBuffer + 1;
+ // trackRect.y = contentRect.y + (contentRect.height - centerSpacing
+ // - 1)/2;
+ trackRect.height = 13;
+ trackRect.y = contentRect.y + contentRect.height - trackRect.height;
+ trackRect.width = contentRect.width - (trackBuffer * 2) - 1;
+ } else {
+ /*
+ * centerSpacing = thumbRect.width; if (!
+ * QuaquaUtilities.isLeftToRight(slider)) { if (
+ * slider.getPaintTicks() ) centerSpacing += getTickLength(); if (
+ * slider.getPaintLabels() ) centerSpacing +=
+ * getWidthOfWidestLabel(); } else { if ( slider.getPaintTicks() )
+ * centerSpacing -= getTickLength(); if ( slider.getPaintLabels() )
+ * centerSpacing -= getWidthOfWidestLabel(); } trackRect.x =
+ * contentRect.x + (contentRect.width - centerSpacing - 1)/2 + 2;
+ */
+ trackRect.width = 14;
+ trackRect.x = contentRect.x + contentRect.width - trackRect.width;
+ trackRect.y = contentRect.y + trackBuffer;
+ trackRect.height = contentRect.height - (trackBuffer * 2) + 1;
+ }
+
+ }
+
+ @Override
+ protected void calculateTickRect() {
+ if (slider.getOrientation() == JSlider.HORIZONTAL) {
+ tickRect.x = trackRect.x;
+ // tickRect.y = trackRect.y + trackRect.height;
+ tickRect.y = trackRect.y - getTickLength();
+ tickRect.width = trackRect.width;
+ tickRect.height = getTickLength();
+
+ if (!slider.getPaintTicks()) {
+ --tickRect.y;
+ tickRect.height = 0;
+ }
+ } else {
+ /*
+ * if(! QuaquaUtilities.isLeftToRight(slider)) { tickRect.x =
+ * trackRect.x + trackRect.width; tickRect.width = getTickLength();
+ * } else { tickRect.width = getTickLength(); tickRect.x =
+ * trackRect.x - tickRect.width; }
+ */
+ tickRect.width = getTickLength();
+ tickRect.x = contentRect.x;// trackRect.x - tickRect.width - 1;
+ tickRect.y = trackRect.y;
+ tickRect.height = trackRect.height;
+
+ if (!slider.getPaintTicks()) {
+ --tickRect.x;
+ tickRect.width = 0;
+ }
+ }
+ }
+
+ /**
+ * Gets the height of the tick area for horizontal sliders and the width of
+ * the tick area for vertical sliders. BasicSliderUI uses the returned value
+ * to determine the tick area rectangle. If you want to give your ticks some
+ * room, make this larger than you need and paint your ticks away from the
+ * sides in paintTicks().
+ */
+ @Override
+ protected int getTickLength() {
+ return 4;
+ }
+
+ @Override
+ protected PropertyChangeListener createPropertyChangeListener(JSlider slider) {
+ return new CSUIPropertyChangeHandler();
+ }
+
+ public class CSUIPropertyChangeHandler extends
+ BasicSliderUI.PropertyChangeHandler {
+ @Override
+ public void propertyChange(PropertyChangeEvent e) {
+ String propertyName = e.getPropertyName();
+
+ if (propertyName.equals("Frame.active")) {
+ // calculateGeometry();
+ slider.repaint();
+ } else if (propertyName.equals("ColorSliderModel")) {
+ colorSliderModel = (ColorSliderModel) e.getNewValue();
+ slider.repaint();
+ } else if (propertyName.equals("snapToTicks")) {
+ slider.repaint();
+ } else if (propertyName.equals("ColorComponentIndex")) {
+ componentIndex = (Integer) e.getNewValue();
+ slider.repaint();
+ } else if (propertyName.equals("ColorComponentChange")) {
+ Integer value = (Integer) e.getNewValue();
+ slider.repaint();
+ } else if (propertyName.equals("ColorComponentValue")) {
+ slider.repaint();
+ } else if (propertyName.equals("Orientation")) {
+ if (slider.getOrientation() == JSlider.HORIZONTAL) {
+ slider.setBorder(new VisualMargin(0, 1, -1, 1));
+ } else {
+ slider.setBorder(new VisualMargin(0, 0, 0, 1));
+ }
+ }
+
+ super.propertyChange(e);
+ }
+ }
+
+ @Override
+ protected TrackListener createTrackListener(JSlider slider) {
+ return new QuaquaTrackListener();
+ }
+
+ /**
+ * Track mouse movements.
+ *
+ * This inner class is marked "public" due to a compiler bug. This
+ * class should be treated as a "protected" inner class.
+ * Instantiate it only within subclasses of <Foo>.
+ */
+ public class QuaquaTrackListener extends BasicSliderUI.TrackListener {
+ /**
+ * If the mouse is pressed above the "thumb" component then reduce the
+ * scrollbars value by one page ("page up"), otherwise increase it by
+ * one page. If there is no thumb then page up if the mouse is in the
+ * upper half of the track.
+ */
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (!slider.isEnabled())
+ return;
+
+ currentMouseX = e.getX();
+ currentMouseY = e.getY();
+
+ if (slider.isRequestFocusEnabled()) {
+ slider.requestFocus();
+ }
+
+ // Clicked inside the Thumb area?
+ if (thumbRect.contains(currentMouseX, currentMouseY)) {
+ super.mousePressed(e);
+ } else {
+ Dimension sbSize = slider.getSize();
+ int direction = POSITIVE_SCROLL;
+
+ switch (slider.getOrientation()) {
+ case JSlider.VERTICAL:
+ slider.setValue(valueForYPosition(currentMouseY));
+ break;
+ case JSlider.HORIZONTAL:
+ slider.setValue(valueForXPosition(currentMouseX));
+ break;
+ }
+
+ // FIXME:
+ // We should set isDragging to false here. Unfortunately,
+ // we can not access this variable in class BasicSliderUI.
+ }
+ }
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ Rectangle thumbB = this.thumbRect;
+ return thumbB != null && thumbB.contains(me.getX(), me.getY());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSlidersChooser.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSlidersChooser.form
new file mode 100644
index 0000000..b3817bf
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSlidersChooser.form
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <AuxValues>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JComboBox" name="slidersComboBox">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="North"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JPanel" name="slidersHolder">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="Center"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSlidersChooser.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSlidersChooser.java
new file mode 100644
index 0000000..ef05f7d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorSlidersChooser.java
@@ -0,0 +1,153 @@
+/*
+ * @(#)ColorSlidersChooser.java 1.4 2006-04-23
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.colorchooser.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.*;
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+
+import java.util.*;
+/**
+ * The ColorSlidersChooser contains four individual color slider pages: gray
+ * slider, RGB sliders, CMYK sliders, and HTML sliders.
+ *
+ * @author Werner Randelshofer
+ * @version 1.4 2006-04-23 Retrieve labels from UIManager.
+ * <br>1.3.1 2005-11-07 Get "UIManager" ResourceBundle from UIManager.
+ * <br>1.3 2005-09-05 Get Font and icon from UIManager.
+ * <br>1.2 2005-08-27 Keep track of last slider panel, and open this
+ * panel, when the ColorSlidersChooser is recreated.
+ * <br>1.1.1 2005-04-23 Localized form.
+ * <br>1.0 30 March 2005 Created.
+ */
+public class ColorSlidersChooser extends AbstractColorChooserPanel
+implements UIResource {
+
+ /**
+ * We store here the name of the last selected color sliders panel.
+ * When the ColorSlidersChooser is recreated multiple times in the same
+ * panel, the application 'remembers' which panel the user had opened
+ * before.
+ */
+ private static int lastSelectedPanelIndex = 1;
+
+
+ /** Creates new form. */
+ public ColorSlidersChooser() {
+ initComponents();
+ slidersComboBox.setFont(UIManager.getFont("ColorChooser.font"));
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ private void initComponents() {//GEN-BEGIN:initComponents
+ slidersComboBox = new javax.swing.JComboBox();
+ slidersHolder = new javax.swing.JPanel();
+
+ setLayout(new java.awt.BorderLayout());
+
+ add(slidersComboBox, java.awt.BorderLayout.NORTH);
+
+ slidersHolder.setLayout(new java.awt.CardLayout());
+
+ add(slidersHolder, java.awt.BorderLayout.CENTER);
+
+ }//GEN-END:initComponents
+
+ @Override
+ protected void buildChooser() {
+ slidersHolder.add(new GrayChooser(),UIManager.getString("ColorChooser.grayScaleSlider"));
+ slidersHolder.add(new RGBChooser(),UIManager.getString("ColorChooser.rgbSliders"));
+ slidersHolder.add(new CMYKChooser(),UIManager.getString("ColorChooser.cmykSliders"));
+ slidersHolder.add(new HSBChooser(),UIManager.getString("ColorChooser.hsbSliders"));
+ slidersHolder.add(new HTMLChooser(),UIManager.getString("ColorChooser.htmlSliders"));
+ DefaultComboBoxModel cbm = new DefaultComboBoxModel();
+ cbm.addElement(UIManager.getString("ColorChooser.grayScaleSlider"));
+ cbm.addElement(UIManager.getString("ColorChooser.rgbSliders"));
+ cbm.addElement(UIManager.getString("ColorChooser.cmykSliders"));
+ cbm.addElement(UIManager.getString("ColorChooser.hsbSliders"));
+ cbm.addElement(UIManager.getString("ColorChooser.htmlSliders"));
+ slidersComboBox.setModel(cbm);
+ slidersComboBox.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent evt) {
+ if (evt.getStateChange() == ItemEvent.SELECTED) {
+ ((CardLayout) slidersHolder.getLayout()).show(slidersHolder, (String) evt.getItem());
+ lastSelectedPanelIndex = slidersComboBox.getSelectedIndex();
+ }
+ }
+ });
+ slidersComboBox.setSelectedIndex(lastSelectedPanelIndex);
+ }
+
+ @Override
+ public void installChooserPanel(JColorChooser enclosingChooser) {
+ super.installChooserPanel(enclosingChooser);
+ Component[] components = slidersHolder.getComponents();
+ for (int i=0; i < components.length; i++) {
+ AbstractColorChooserPanel ccp = (AbstractColorChooserPanel) components[i];
+ ccp.installChooserPanel(enclosingChooser);
+ }
+ }
+
+ /**
+ * Invoked when the panel is removed from the chooser.
+ * If override this, be sure to call <code>super</code>.
+ */
+ @Override
+ public void uninstallChooserPanel(JColorChooser enclosingChooser) {
+ Component[] components = slidersHolder.getComponents();
+ for (int i=0; i < components.length; i++) {
+ AbstractColorChooserPanel ccp = (AbstractColorChooserPanel) components[i];
+ ccp.uninstallChooserPanel(enclosingChooser);
+ }
+ super.uninstallChooserPanel(enclosingChooser);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return UIManager.getString("ColorChooser.colorSliders");
+ }
+
+ @Override
+ public Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.colorSlidersIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ @Override
+ public void updateChooser() {
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JComboBox slidersComboBox;
+ private javax.swing.JPanel slidersHolder;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheel.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheel.form
new file mode 100644
index 0000000..fafa671
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheel.form
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheel.java
new file mode 100644
index 0000000..f148a90
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheel.java
@@ -0,0 +1,169 @@
+/*
+ * @(#)ColorWheel.java 1.0 August 27, 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+/**
+ * The ColorWheel displays a hue/saturation wheel of the HSB color model.
+ * The user can click at the wheel to pick a color on the ColorWheel.
+ * The ColorWheel should be used together with a HSB brightness color slider.
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 August 27, 2005 Created.
+ */
+public class ColorWheel extends JPanel {
+ private Image colorWheelImage;
+ private ColorWheelImageProducer colorWheelProducer;
+ private HSBColorSliderModel model;
+
+ private class MouseHandler implements MouseListener, MouseMotionListener {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ update(e);
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ update(e);
+ }
+ private void update(MouseEvent e) {
+ int x = e.getX() - getWidth() / 2;
+ int y = e.getY() - getHeight() / 2;
+ float r = (float) Math.sqrt(x*x + y*y);
+ float theta = (float) Math.atan2(y, -x);
+
+ model.setValue(0, 180 + (int) (theta / Math.PI * 180d));
+ model.setValue(1, (int) (Math.min(1f, (float) r / colorWheelProducer.getRadius()) * 100f));
+
+ // FIXME - We should only repaint the damaged area
+ repaint();
+ }
+ }
+
+ private MouseHandler mouseHandler;
+
+ private class ModelHandler implements ChangeListener {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ repaint();
+ }
+ };
+
+ private ModelHandler modelHandler;
+
+ /**
+ * Creates a new instance.
+ */
+ public ColorWheel() {
+ model = new HSBColorSliderModel();
+ initComponents();
+ colorWheelProducer = new ColorWheelImageProducer(0, 0);
+ mouseHandler = new MouseHandler();
+ modelHandler = new ModelHandler();
+ addMouseListener(mouseHandler);
+ addMouseMotionListener(mouseHandler);
+ setOpaque(false);
+ }
+
+ public void setModel(HSBColorSliderModel m) {
+ if (model != null) {
+ model.removeChangeListener(modelHandler);
+ }
+ model = m;
+ if (model != null) {
+ model.addChangeListener(modelHandler);
+ repaint();
+ }
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(100,100);
+ }
+
+ public HSBColorSliderModel getModel() {
+ return model;
+ }
+
+ @Override
+ public void paintComponent(Graphics g) {
+ int w = getWidth();
+ int h = getHeight();
+
+ if (colorWheelImage == null
+ || colorWheelImage.getWidth(this) != w
+ || colorWheelImage.getHeight(this) != h) {
+ if (colorWheelImage != null) {
+ colorWheelImage.flush();
+ }
+ colorWheelProducer = new ColorWheelImageProducer(w, h);
+ colorWheelImage = createImage(colorWheelProducer);
+ }
+
+ colorWheelProducer.setBrightness(model.getValue(2) / 100f);
+ colorWheelProducer.regenerateColorWheel();
+
+ g.drawImage(colorWheelImage, 0, 0, this);
+
+ int x = w / 2 + (int) (colorWheelProducer.getRadius() * model.getValue(1) / 100d * Math.cos(model.getValue(0) * Math.PI / 180d));
+ int y = h / 2 - (int) (colorWheelProducer.getRadius() * model.getValue(1) / 100d * Math.sin(model.getValue(0) * Math.PI / 180d));
+
+ g.setColor(Color.white);
+ g.fillRect(x - 1, y - 1, 2, 2);
+ g.setColor(Color.black);
+ g.drawRect(x - 2, y - 2, 3, 3);
+ }
+
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ private void initComponents() {//GEN-BEGIN:initComponents
+
+ setLayout(new java.awt.BorderLayout());
+
+ }//GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheelChooser.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheelChooser.form
new file mode 100644
index 0000000..0a0f9ef
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheelChooser.form
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <AuxValues>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JSlider" name="brightnessSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="50"/>
+ <Property name="orientation" type="int" value="1"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="East"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ </SubComponents>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheelChooser.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheelChooser.java
new file mode 100644
index 0000000..778bae7
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheelChooser.java
@@ -0,0 +1,114 @@
+/*
+ * @(#)ColorWheelChooser.java 1.1 2006-04-23
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.colorchooser.*;
+import javax.swing.event.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+/**
+ * A HSB color chooser, which displays a hue/saturation color wheel, and a
+ * brightness slider.
+ *
+ * @author Werner Randelshofer
+ * @version 1.1 2006-04-23 Retrieve labels from UIManager.
+ * <br>1.0.2 2005-11-07 Get "labels" resource bundle from UIManager.
+ * <br>1.0.1 2005-09-11 Get icon from UIManager.
+ * <br>1.0 August 27, 2005 Created.
+ */
+public class ColorWheelChooser extends AbstractColorChooserPanel implements UIResource {
+ private ColorWheel colorWheel;
+ private HSBColorSliderModel ccModel = new HSBColorSliderModel();
+
+ /**
+ * Creates a new instance.
+ */
+ public ColorWheelChooser() {
+ initComponents();
+
+ int textSliderGap = UIManager.getInt("ColorChooser.textSliderGap");
+ if (textSliderGap != 0) {
+ BorderLayout layout = (BorderLayout) getLayout();
+ layout.setHgap(textSliderGap);
+}
+
+ colorWheel = new ColorWheel();
+ add(colorWheel);
+
+ ccModel.configureColorSlider(2, brightnessSlider);
+ colorWheel.setModel(ccModel);
+
+ ccModel.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent evt) {
+ setColorToModel(ccModel.getColor());
+ }
+ });
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ private void initComponents() {//GEN-BEGIN:initComponents
+ brightnessSlider = new javax.swing.JSlider();
+
+ setLayout(new java.awt.BorderLayout());
+
+ brightnessSlider.setMajorTickSpacing(50);
+ brightnessSlider.setOrientation(javax.swing.JSlider.VERTICAL);
+ brightnessSlider.setPaintTicks(true);
+ add(brightnessSlider, java.awt.BorderLayout.EAST);
+
+ }//GEN-END:initComponents
+
+ @Override
+ protected void buildChooser() {
+ }
+
+ @Override
+ public String getDisplayName() {
+ return UIManager.getString("ColorChooser.colorWheel");
+ }
+
+ @Override
+ public javax.swing.Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.colorWheelIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ @Override
+ public void updateChooser() {
+ ccModel.setColor(getColorFromModel());
+ }
+ public void setColorToModel(Color color) {
+ getColorSelectionModel().setSelectedColor(color);
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JSlider brightnessSlider;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheelImageProducer.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheelImageProducer.java
new file mode 100644
index 0000000..2099257
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ColorWheelImageProducer.java
@@ -0,0 +1,121 @@
+/*
+ * @(#)ColorWheelImageProducer.java 1.0 August 27, 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.awt.color.*;
+/**
+ * Produces the image of a ColorWheel.
+ *
+ * @see ColorWheel
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 August 27, 2005 Created.
+ */
+public class ColorWheelImageProducer extends MemoryImageSource {
+ private int[] pixels;
+ private int w, h;
+ private float brightness = 1f;
+ private boolean isDirty = true;
+
+ /** Lookup table for hues. */
+ private float[] hues;
+ /** Lookup table for saturations. */
+ private float[] saturations;
+ /** Lookup table for alphas.
+ * The alpha value is used for antialiasing the
+ * color wheel.
+ */
+ private int[] alphas;
+
+ /** Creates a new instance. */
+ public ColorWheelImageProducer(int w, int h) {
+ super(w, h, null, 0, w);
+ pixels = new int[w*h];
+ this.w = w;
+ this.h = h;
+ generateLookupTables();
+ newPixels(pixels, ColorModel.getRGBdefault(), 0, w);
+ setAnimated(true);
+ generateColorWheel();
+ }
+
+ public int getRadius() {
+ return Math.min(w, h) / 2 - 2;
+ }
+
+ private void generateLookupTables() {
+ saturations = new float[w*h];
+ hues = new float[w*h];
+ alphas = new int[w*h];
+ float radius = getRadius();
+
+ // blend is used to create a linear alpha gradient of two extra pixels
+ float blend = (radius + 2f) / radius - 1f;
+
+ // Center of the color wheel circle
+ int cx = w / 2;
+ int cy = h / 2;
+
+ for (int x=0; x < w; x++) {
+ int kx = x - cx; // Kartesian coordinates of x
+ int squarekx = kx * kx; // Square of kartesian x
+
+ for (int y=0; y < h; y++) {
+ int ky = cy - y; // Kartesian coordinates of y
+
+ int index = x + y * w;
+ saturations[index] = (float) Math.sqrt(squarekx + ky*ky) / radius;
+ if (saturations[index] <= 1f) {
+ alphas[index] = 0xff000000;
+ } else {
+ alphas[index] = (int) ((blend - Math.min(blend,saturations[index] - 1f)) * 255 / blend) << 24;
+ saturations[index] = 1f;
+ }
+ if (alphas[index] != 0) {
+ hues[index] = (float) (Math.atan2(ky, kx) / Math.PI / 2d);
+ }
+ }
+ }
+ }
+
+ public void setBrightness(float newValue) {
+ isDirty = isDirty || brightness != newValue;
+ brightness = newValue;
+ }
+
+ public boolean needsGeneration() {
+ return isDirty;
+ }
+
+ public void regenerateColorWheel() {
+ if (isDirty) {
+ generateColorWheel();
+ }
+ }
+
+ public void generateColorWheel() {
+ float radius = (float) Math.min(w, h);
+ for (int index=0; index < pixels.length; index++) {
+ if (alphas[index] != 0) {
+ pixels[index] = alphas[index]
+ | 0xffffff & Color.HSBtoRGB(hues[index], saturations[index], brightness);
+ }
+ }
+ newPixels();
+ isDirty = false;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/Crayons.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/Crayons.form
new file mode 100644
index 0000000..b404a39
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/Crayons.form
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <AuxValues>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/Crayons.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/Crayons.java
new file mode 100644
index 0000000..4f1b113
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/Crayons.java
@@ -0,0 +1,214 @@
+/*
+ * @(#)Crayons.java 1.2 2006-04-23
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.awt.geom.*;
+import javax.swing.*;
+import javax.swing.event.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.*;
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+/**
+ * A panel which displays a selection of color crayons. The user can click at
+ * a crayon to pick a color.
+ *
+ * @author Werner Randelshofer
+ * @version 1.2 2006-04-23 Retrieve labels directly from UIManager.
+ * <br>1.1.1 2005-11-07 Get "labels" resource bundle from UIManager.
+ * <br>1.1 2005-08-30 Rearranged code to ease the creation of derived look
+ * and feels.
+ * <br>1.0 August 28, 2005 Created.
+ */
+public class Crayons extends javax.swing.JPanel {
+
+ /**
+ * Shared crayons image.
+ */
+ private Image crayonsImage;
+
+ /**
+ * Coordinates of crayon shaped polygon.
+ */
+ private final static int[] crayonXPoints = { 10, 12, 20, 22, 22, 0, 0, 2 }; // xpoints
+ private final static int[] crayonYPoints = { 0, 0, 21, 21, 104, 104, 21, 21 }; // ypoints
+
+ /**
+ * Current color.
+ */
+ private Color color = Color.white;
+ /**
+ * Selected crayon.
+ */
+ private Crayon selectedCrayon = null;
+
+ /**
+ * Crayon.
+ */
+ private class Crayon {
+ Polygon shape;
+ Color color;
+ String name;
+
+ public Crayon(Color color, String name, Polygon shape) {
+ this.color = color;
+ this.name = name;
+ this.shape = shape;
+ }
+ }
+
+ private class MouseHandler extends MouseAdapter {
+ @Override
+ public void mousePressed(MouseEvent evt) {
+ int x = evt.getX();
+ int y = evt.getY();
+ if (x > 0 && x < crayonsImage.getWidth(Crayons.this)
+ && y > 0 && y < crayonsImage.getHeight(Crayons.this)
+ ) {
+ for (int i=crayons.length - 1; i >= 0; i--) {
+ if (crayons[i].shape.contains(x, y)) {
+ setColor(crayons[i].color);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private MouseHandler mouseHandler;
+
+ /**
+ * Crayons.
+ */
+ private Crayon[] crayons;
+
+ /**
+ * Creates a new instance.
+ */
+ public Crayons() {
+ initComponents();
+
+ setForeground(new Color(0x808080));
+ setPreferredSize(new Dimension(195, 208));
+ setFont(UIManager.getFont("ColorChooser.crayonsFont"));
+
+ crayonsImage = createCrayonsImage();
+ crayons = createCrayons();
+
+ mouseHandler = new MouseHandler();
+ addMouseListener(mouseHandler);
+ }
+
+ protected Image createCrayonsImage() {
+ return (Image) UIManager.get("ColorChooser.crayonsImage");
+ }
+
+ /**
+ * Creates the crayons.
+ * @return Array of crayons in z-order from bottom to top.
+ */
+ protected Crayon[] createCrayons() {
+ Color[] colors = DefaultPalettes.CRAYONS;
+ crayons = new Crayon[colors.length];
+ for (int i=0; i < colors.length; i++) {
+ crayons[i] = new Crayon(
+ colors[i],
+ UIManager.getString("ColorChooser.crayon."+Integer.toHexString(0xff000000|colors[i].getRGB()).substring(2)),
+ new Polygon((int[]) crayonXPoints.clone(), (int[]) crayonYPoints.clone(), crayonXPoints.length));
+ crayons[i].shape.translate(
+ (i % 8) * 22 + 4 +((i / 8) % 2) * 11,
+ (i / 8) * 20 + 23
+ );
+ }
+
+ return crayons;
+ }
+
+ /**
+ * Sets the current color.
+ * This results in a selection of a crayon, if a crayon with the same
+ * RGB values exists.
+ */
+ public void setColor(Color newValue) {
+ Color oldValue = color;
+ color = newValue;
+
+ Crayon newSelectedCrayon = null;
+ int newRGB = newValue.getRGB() & 0xffffff;
+ for (int i=0; i < crayons.length; i++) {
+ if ((crayons[i].color.getRGB() & 0xffffff) == newRGB) {
+ newSelectedCrayon = crayons[i];
+ }
+ }
+ if (newSelectedCrayon != selectedCrayon) {
+ selectedCrayon = newSelectedCrayon;
+ repaint();
+ }
+
+ firePropertyChange("Color", oldValue, newValue);
+ }
+
+ /**
+ * Returns the current color.
+ */
+ public Color getColor() {
+ return color;
+ }
+
+ @Override
+ public void paintComponent(Graphics gr) {
+ Graphics2D g = (Graphics2D) gr;
+ Object oldHints = QuaquaUtilities.beginGraphics((Graphics2D) g);
+
+ g.drawImage(crayonsImage, 0, 0, this);
+
+
+ if (selectedCrayon != null) {
+ /*
+ g.setColor(new Color(0x60ffffff & selectedCrayon.color.getRGB(),true));
+ g.fill(selectedCrayon.shape);
+ */
+ g.setColor(getForeground());
+ FontMetrics fm = g.getFontMetrics();
+ int nameWidth = fm.stringWidth(selectedCrayon.name);
+ g.drawString(
+ selectedCrayon.name,
+ (crayonsImage.getWidth(this) - nameWidth) / 2,
+ fm.getAscent() + 1
+ );
+ }
+ QuaquaUtilities.endGraphics((Graphics2D) g, oldHints);
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ private void initComponents() {//GEN-BEGIN:initComponents
+
+ setLayout(new java.awt.BorderLayout());
+
+ }//GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/CrayonsChooser.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/CrayonsChooser.form
new file mode 100644
index 0000000..00fd70f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/CrayonsChooser.form
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/CrayonsChooser.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/CrayonsChooser.java
new file mode 100644
index 0000000..3a2a8a2
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/CrayonsChooser.java
@@ -0,0 +1,98 @@
+/*
+ * @(#)CrayonsChooser.java 1.1 2006-04-23
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+
+import java.awt.*;
+//import java.awt.event.*;
+import java.beans.*;
+import javax.swing.*;
+import javax.swing.colorchooser.*;
+import javax.swing.event.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+
+/**
+ * A color chooser which provides a choice of Crayons.
+ *
+ * @author Werner Randelshofer
+ * @version 1.2 2006-04-23 Retrieve labels from UIManager.
+ * <br>1.0.2 2005-11-07 Get "labels" resource bundle from UIManager.
+ * <br>1.0.1 2005-09-11 Get icon from UIManager.
+ * <br>1.0 August 28, 2005 Created.
+ */
+public class CrayonsChooser extends AbstractColorChooserPanel implements UIResource {
+ private Crayons crayons;
+
+ /**
+ * Creates a new instance.
+ */
+ public CrayonsChooser() {
+ initComponents();
+
+ crayons = new Crayons();
+ add(crayons);
+ crayons.addPropertyChangeListener(new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (evt.getPropertyName().equals("Color")) {
+ setColorToModel(crayons.getColor());
+ }
+ }
+ });
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ private void initComponents() {//GEN-BEGIN:initComponents
+
+ }//GEN-END:initComponents
+
+ @Override
+ protected void buildChooser() {
+ }
+
+ @Override
+ public String getDisplayName() {
+ return UIManager.getString("ColorChooser.crayons");
+ }
+
+ @Override
+ public javax.swing.Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.crayonsIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ @Override
+ public void updateChooser() {
+ crayons.setColor(getColorFromModel());
+ }
+ public void setColorToModel(Color color) {
+ getColorSelectionModel().setSelectedColor(color);
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/DefaultPalettes.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/DefaultPalettes.java
new file mode 100644
index 0000000..352b797
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/DefaultPalettes.java
@@ -0,0 +1,468 @@
+/*
+ * @(#)ColorPalettes.java 1.0 September 18, 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+/**
+ * This class provides some well known color palettes as array constants.
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 September 18, 2005 Created.
+ */
+public class DefaultPalettes {
+ /**
+ * Apple Crayon Colors.
+ * Copyright Apple Computer Inc., All rights reserved.
+ *
+ * The colors are listed here in a logical sequence.
+ * Do not change this sequence! Other classes depend on it.
+ */
+ public final static Color[] CRAYONS = {
+ new Color(0x800000), //Cayenne
+ new Color(0x808000), //Asparagus
+ new Color(0x008000), //Clover
+ new Color(0x008080), //Teal
+ new Color(0x000080), //Midnight
+ new Color(0x800080), //Plum
+ new Color(0x7f7f7f), //Tin
+ new Color(0x808080), //Nickel
+
+ new Color(0x804000), //Mocha
+ new Color(0x408000), //Fern
+ new Color(0x008040), //Moss
+ new Color(0x004080), //Ocean
+ new Color(0x400080), //Eggplant
+ new Color(0x800040), //Maroon
+ new Color(0x666666), //Steel
+ new Color(0x999999), //Aluminium
+
+ new Color(0xff0000), //Maraschino
+ new Color(0xffff00), //Lemon
+ new Color(0x00ff00), //Spring
+ new Color(0x00ffff), //Turquoise
+ new Color(0x0000ff), //Blueberry
+ new Color(0xff00ff), //Magenta
+ new Color(0x4c4c4c), //Iron
+ new Color(0xb3b3b3), //Magnesium
+
+ new Color(0xff8000), //Tangerine
+ new Color(0x80ff00), //Lime
+ new Color(0x00ff80), //Sea Foam
+ new Color(0x0080ff), //Aqua
+ new Color(0x8000ff), //Grape
+ new Color(0xff0080), //Strawberry
+ new Color(0x333333), //Tungsten
+ new Color(0xcccccc), //Silver
+
+ new Color(0xff6666), //Salmon
+ new Color(0xffff66), //Banana
+ new Color(0x66ff66), //Flora
+ new Color(0x66ffff), //Ice
+ new Color(0x6666ff), //Orchid
+ new Color(0xff66ff), //Bubblegum
+ new Color(0x191919), //Lead
+ new Color(0xe6e6e6), //Mercury
+
+ new Color(0xffcc66), //Cantaloupe
+ new Color(0xccff66), //Honeydew
+ new Color(0x66ffcc), //Spindrift
+ new Color(0x66ccff), //Sky
+ new Color(0xcc66ff), //Lavender
+ new Color(0xff6fcf), //Carnation
+ new Color(0x000000), //Licorice
+ new Color(0xffffff), //Snow
+ };
+
+ /**
+ * Apple Colors.
+ * Copyright Apple Computer Inc., All rights reserved.
+ *
+ * The colors are listed here in a logical sequence.
+ * Do not change this sequence! Other classes depend on it.
+ */
+ public final static Color[] APPLE_COLORS = {
+ new Color(0x000000), //Black
+ new Color(0x0000ff), //Blue
+ new Color(0x996633), //Brown
+ new Color(0x00ffff), //Cyan
+ new Color(0x00ff00), //Green
+ new Color(0xff00ff), //Magenta
+ new Color(0xff8000), //Orange
+ new Color(0x800080), //Purple
+ new Color(0xff0000), //Red
+ new Color(0xffff00), //Yellow
+ new Color(0xffffff), //White
+};
+
+ /**
+ * Windows Basic Colors.
+ * Copyright Microsoft Inc., All rights reserved.
+ *
+ * The colors are listed here in a logical sequence.
+ * (This is the sequence used by native the Microsoft Windows color dialog.)
+ * Do not alter this sequence! Other classes depend on it.
+ */
+ public final static Color[] WINDOWS_BASIC_COLORS = {
+ new Color(0xff8080), //salmon
+ new Color(0xffff80), //pale yellow
+ new Color(0x80ff80), //pale green
+ new Color(0x00ff80), //spring green
+ new Color(0x80ffff), //pale turquoise
+ new Color(0x0080ff), //deep sky blue
+ new Color(0xff80c0), //pale rose
+ new Color(0xff80ff), //pink
+
+ new Color(0xff0000), //red
+ new Color(0xffff00), //yellow
+ new Color(0x80ff00), //apple green
+ new Color(0x00ff40), //light green
+ new Color(0x00ffff), //aqua
+ new Color(0x0080c0), //turquoise
+ new Color(0x8080c0), //pale slate blue
+ new Color(0xff00ff), //magenta
+
+ new Color(0x804040), //chocolate
+ new Color(0xff8040), //pumpkin
+ new Color(0x00ff00), //lime
+ new Color(0x008080), //teal
+ new Color(0x004080), //dark turquoise
+ new Color(0x8080ff), //medium slate blue
+ new Color(0x800040), //maroon
+ new Color(0xff0080), //rose
+
+ new Color(0x800000), //dark red
+ new Color(0xff8000), //dark orange
+ new Color(0x008000), //green
+ new Color(0x008040), //sea green
+ new Color(0x0000ff), //blue
+ new Color(0x0000a0), //medium blue
+ new Color(0x800080), //purple
+ new Color(0x8000ff), //blueviolet
+
+ new Color(0x400000), //dark brown
+ new Color(0x804000), //saddle brown
+ new Color(0x004000), //dark forest green
+ new Color(0x004040), //dark teal
+ new Color(0x000080), //navy
+ new Color(0x000040), //midnight blue
+ new Color(0x400040), //dark purple
+ new Color(0x400080), //dark blueviolet
+
+ new Color(0x000000), //black
+ new Color(0x808000), //olive
+ new Color(0x808040), //dark olive
+ new Color(0x808080), //grey
+ new Color(0x408080), //light teal
+ new Color(0xc0c0c0), //light grey
+ new Color(0x400040), //dark purple
+ new Color(0xffffff), //white
+ };
+
+ /**
+ * Web safe colors.
+ * Copyright Apple Computer Inc., All rights reserved.
+ *
+ * The colors are listed here in a logical sequence.
+ * (This is the sequence used by native NSColorPicker color dialog.)
+ * Do not alter this sequence! Other classes depend on it.
+ */
+ public final static Color[] WEB_SAFE_COLORS = {
+ new Color(0xffffff),
+ new Color(0xcccccc),
+ new Color(0x999999),
+ new Color(0x666666),
+ new Color(0x333333),
+ new Color(0x000000),
+
+ new Color(0xffcccc),
+ new Color(0xcc9999),
+ new Color(0x996666),
+ new Color(0x663333),
+ new Color(0x330000),
+
+ new Color(0xff9999),
+ new Color(0xcc6666),
+ new Color(0xcc3333),
+ new Color(0x993333),
+ new Color(0x660000),
+
+ new Color(0xff6666),
+ new Color(0xff3333),
+ new Color(0xff0000),
+ new Color(0xcc0000),
+ new Color(0x990000),
+
+ new Color(0xff9966),
+ new Color(0xff6633),
+ new Color(0xff3300),
+ new Color(0xcc3300),
+ new Color(0x993300),
+
+ new Color(0xffcc99),
+ new Color(0xcc9966),
+ new Color(0xcc6633),
+ new Color(0x996633),
+ new Color(0x663300),
+
+ new Color(0xff9933),
+ new Color(0xff6600),
+ new Color(0xff9900),
+ new Color(0xcc6600),
+ new Color(0xcc9933),
+
+ new Color(0xffcc66),
+ new Color(0xffcc33),
+ new Color(0xffcc00),
+ new Color(0xcc9900),
+ new Color(0x996600),
+
+ new Color(0xffffcc),
+ new Color(0xcccc99),
+ new Color(0x999966),
+ new Color(0x666633),
+ new Color(0x333300),
+
+ new Color(0xffff99),
+ new Color(0xcccc66),
+ new Color(0xcccc33),
+ new Color(0x999933),
+ new Color(0x666600),
+
+ new Color(0xffff66),
+ new Color(0xffff33),
+ new Color(0xffff00),
+ new Color(0xcccc00),
+ new Color(0x999900),
+
+ new Color(0xccff66),
+ new Color(0xccff33),
+ new Color(0xccff00),
+ new Color(0x99cc00),
+ new Color(0x669900),
+
+ new Color(0xccff99),
+ new Color(0x99cc66),
+ new Color(0x99cc33),
+ new Color(0x669933),
+ new Color(0x336600),
+
+ new Color(0x99ff33),
+ new Color(0x99ff00),
+ new Color(0x66ff00),
+ new Color(0x66cc00),
+ new Color(0x66cc33),
+
+ new Color(0x99ff66),
+ new Color(0x66ff33),
+ new Color(0x33ff00),
+ new Color(0x33cc00),
+ new Color(0x339900),
+
+ new Color(0xccffcc),
+ new Color(0x99cc99),
+ new Color(0x669966),
+ new Color(0x336633),
+ new Color(0x003300),
+
+ new Color(0x99ff99),
+ new Color(0x66cc66),
+ new Color(0x33cc33),
+ new Color(0x339933),
+ new Color(0x006600),
+
+ new Color(0x66ff66),
+ new Color(0x33ff33),
+ new Color(0x00ff00),
+ new Color(0x00cc00),
+ new Color(0x009900),
+
+ new Color(0x66ff99),
+ new Color(0x33ff66),
+ new Color(0x00ff33),
+ new Color(0x00cc33),
+ new Color(0x009933),
+
+ new Color(0x99ffcc),
+ new Color(0x66cc99),
+ new Color(0x33cc66),
+ new Color(0x339966),
+ new Color(0x006633),
+
+ new Color(0x33ff99),
+ new Color(0x00ff66),
+ new Color(0x00ff99),
+ new Color(0x00cc66),
+ new Color(0x33cc99),
+
+ new Color(0x66ffcc),
+ new Color(0x33ffcc),
+ new Color(0x00ffcc),
+ new Color(0x00cc99),
+ new Color(0x009966),
+
+ new Color(0xccffff),
+ new Color(0x99cccc),
+ new Color(0x669999),
+ new Color(0x336666),
+ new Color(0x003333),
+
+ new Color(0x99ffff),
+ new Color(0x66cccc),
+ new Color(0x33cccc),
+ new Color(0x339999),
+ new Color(0x006666),
+
+ new Color(0x66ffff),
+ new Color(0x33ffff),
+ new Color(0x00ffff),
+ new Color(0x00cccc),
+ new Color(0x009999),
+
+ new Color(0x66ccff),
+ new Color(0x33ccff),
+ new Color(0x00ccff),
+ new Color(0x0099cc),
+ new Color(0x006699),
+
+ new Color(0x99ccff),
+ new Color(0x6699cc),
+ new Color(0x3399cc),
+ new Color(0x336699),
+ new Color(0x003366),
+
+ new Color(0x3399ff),
+ new Color(0x0099ff),
+ new Color(0x0066ff),
+ new Color(0x0066cc),
+ new Color(0x3366cc),
+
+ new Color(0x6699ff),
+ new Color(0x3366ff),
+ new Color(0x0033ff),
+ new Color(0x0033cc),
+ new Color(0x003399),
+
+ new Color(0xccccff),
+ new Color(0x9999cc),
+ new Color(0x666699),
+ new Color(0x333366),
+ new Color(0x003333),
+
+ new Color(0x9999ff),
+ new Color(0x6666cc),
+ new Color(0x3333cc),
+ new Color(0x333399),
+ new Color(0x000066),
+
+ new Color(0x6666ff),
+ new Color(0x3333ff),
+ new Color(0x0000ff),
+ new Color(0x0000cc),
+ new Color(0x000099),
+
+ new Color(0x9966ff),
+ new Color(0x6633ff),
+ new Color(0x3300ff),
+ new Color(0x3300cc),
+ new Color(0x330099),
+
+ new Color(0xcc99ff),
+ new Color(0x9966cc),
+ new Color(0x6633cc),
+ new Color(0x663399),
+ new Color(0x330066),
+
+ new Color(0x9933ff),
+ new Color(0x6600ff),
+ new Color(0x9900ff),
+ new Color(0x6600cc),
+ new Color(0x9933cc),
+
+ new Color(0xcc66ff),
+ new Color(0xcc33ff),
+ new Color(0xcc00ff),
+ new Color(0x9900cc),
+ new Color(0x660099),
+
+ new Color(0xffccff),
+ new Color(0xcc99cc),
+ new Color(0x996699),
+ new Color(0x663366),
+ new Color(0x330033),
+
+ new Color(0xff99ff),
+ new Color(0xcc66cc),
+ new Color(0xcc33cc),
+ new Color(0x993399),
+ new Color(0x660066),
+
+ new Color(0xff66ff),
+ new Color(0xff33ff),
+ new Color(0xff00ff),
+ new Color(0xcc00cc),
+ new Color(0x990099),
+
+ new Color(0xff66cc),
+ new Color(0xff33cc),
+ new Color(0xff00cc),
+ new Color(0xcc0099),
+ new Color(0x990066),
+
+ new Color(0xff99cc),
+ new Color(0xcc6699),
+ new Color(0xcc3399),
+ new Color(0x993366),
+ new Color(0x660033),
+
+ new Color(0xff3399),
+ new Color(0xff0099),
+ new Color(0xff0066),
+ new Color(0xcc0066),
+ new Color(0xcc3366),
+
+ new Color(0xff6699),
+ new Color(0xff3366),
+ new Color(0xff0033),
+ new Color(0xcc0033),
+ new Color(0x990033),
+ };
+
+
+ /**
+ * Microsoft Office Colors.
+ * Copyright Microsoft Inc., All rights reserved.
+ *
+ * The colors are listed here in a logical sequence.
+ * (This is the sequence used by native the Microsoft Office color dialog.)
+ * Do not alter this sequence! Other classes depend on it.
+ */
+ public final static Color[] MS_OFFICE_COLORS = {
+ new Color(0x000000), //Black
+ };
+
+ /**
+ * Prevent instance creation.
+ */
+ private DefaultPalettes() {
+ }
+ /*
+ public static void main(String[] args) {
+ for (int i=0; i < WINDOWS_BASIC_COLORS.length; i+=2) {
+ System.out.println("new Color(0x"
+ +Integer.toHexString(((Color)WINDOWS_BASIC_COLORS[i+1]).getRGB()).substring(2)
+ +"), //"+WINDOWS_BASIC_COLORS[i]);
+ }
+ }*/
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/GrayChooser.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/GrayChooser.form
new file mode 100644
index 0000000..c31d134
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/GrayChooser.form
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <AuxValues>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,95"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="brightnessLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.hsbBrightnessText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="0" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="brightnessSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="100"/>
+ <Property name="minorTickSpacing" type="int" value="50"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="5" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JPanel" name="brightnessFieldPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="5" gridY="0" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout">
+ <Property name="horizontalGap" type="int" value="0"/>
+ <Property name="verticalGap" type="int" value="0"/>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JTextField" name="brightnessField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="brightnessFieldFocusLost"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JLabel" name="brightnessFieldLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="%"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="springPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="100" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="1.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ </Container>
+ <Container class="javax.swing.JPanel" name="percentPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="-1" gridY="2" gridWidth="5" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JButton" name="zeroPercentButton">
+ <Properties>
+ <Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
+ <Color blue="0" green="0" red="0" type="rgb"/>
+ </Property>
+ <Property name="toolTipText" type="java.lang.String" value="0 %"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="percentActionPerformed"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="-1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JButton" name="twentyFivePercentButton">
+ <Properties>
+ <Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
+ <Color blue="40" green="40" red="40" type="rgb"/>
+ </Property>
+ <Property name="toolTipText" type="java.lang.String" value="25 %"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="percentActionPerformed"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="-1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JButton" name="fiftyPercentButton">
+ <Properties>
+ <Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
+ <Color blue="80" green="80" red="80" type="rgb"/>
+ </Property>
+ <Property name="toolTipText" type="java.lang.String" value="50 %"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="percentActionPerformed"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="-1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JButton" name="seventyFivePercentButton">
+ <Properties>
+ <Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
+ <Color blue="c0" green="c0" red="c0" type="rgb"/>
+ </Property>
+ <Property name="toolTipText" type="java.lang.String" value="75 %"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="percentActionPerformed"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="-1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JButton" name="hundredPercentButton">
+ <Properties>
+ <Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
+ <Color blue="ff" green="ff" red="ff" type="rgb"/>
+ </Property>
+ <Property name="toolTipText" type="java.lang.String" value="100 %"/>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="percentActionPerformed"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="-1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="13" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ </SubComponents>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/GrayChooser.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/GrayChooser.java
new file mode 100644
index 0000000..ab89be5
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/GrayChooser.java
@@ -0,0 +1,319 @@
+/*
+ * @(#)GrayChooser.java 1.4 2006-04-23
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entehue into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+import javax.swing.border.*;
+import javax.swing.colorchooser.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.*;
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+/**
+ * A color chooser with a brightness slider.
+ *
+ * @author Werner Randelshofer
+ * @version 1.4 2006-04-23 Retrieve labels from UIManager.
+ * <br>1.3 2005-11-22 Moved handler for text fields into separate class.
+ * <br>1.2.1 2005-11-07 Get "Labels" ResourceBundle from UIManager.
+ * <br>1.2 2005-09-05 Get font, spacing and icon from UIManager.
+ * <br>1.1.1 2005-04-23 Localized form. Added color swatches for 0%, 25%, 50%,
+ * 75% and 100% brightness.
+ * <br>1.0 29 March 2005 Created.
+ */
+public class GrayChooser extends AbstractColorChooserPanel implements UIResource {
+ private ColorSliderModel ccModel = new GrayColorSliderModel();
+
+ /** Creates new form. */
+ public GrayChooser() {
+ initComponents();
+
+ Font font = UIManager.getFont("ColorChooser.font");
+ brightnessLabel.setFont(font);
+ brightnessSlider.setFont(font);
+ brightnessField.setFont(font);
+ brightnessFieldLabel.setFont(font);
+ zeroPercentButton.setFont(font);
+ twentyFivePercentButton.setFont(font);
+ fiftyPercentButton.setFont(font);
+ seventyFivePercentButton.setFont(font);
+ //
+ int textSliderGap = UIManager.getInt("ColorChooser.textSliderGap");
+ if (textSliderGap != 0) {
+ Border fieldBorder = new EmptyBorder(0,textSliderGap,0,0);
+ brightnessFieldPanel.setBorder(fieldBorder);
+ }
+
+
+ ccModel.configureColorSlider(0, brightnessSlider);
+ brightnessField.setText(Integer.toString(brightnessSlider.getValue()));
+ Insets borderMargin = (Insets) UIManager.getInsets("Component.visualMargin").clone();
+ borderMargin.left = 3 - borderMargin.left;
+ brightnessFieldLabel.putClientProperty("Quaqua.Component.visualMargin",borderMargin);
+
+ new ColorSliderTextFieldHandler(brightnessField, ccModel, 0);
+
+ ccModel.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent evt) {
+ if (updateRecursion == 0) {
+ setColorToModel(ccModel.getColor());
+ }
+ }
+ });
+ brightnessField.setMinimumSize(brightnessField.getPreferredSize());
+
+ VisualMargin bm = new VisualMargin(false,false,true,false);
+ brightnessLabel.setBorder(bm);
+ zeroPercentButton.putClientProperty("Quaqua.Button.style","colorWell");
+ twentyFivePercentButton.putClientProperty("Quaqua.Button.style","colorWell");
+ fiftyPercentButton.putClientProperty("Quaqua.Button.style","colorWell");
+ seventyFivePercentButton.putClientProperty("Quaqua.Button.style","colorWell");
+ hundredPercentButton.putClientProperty("Quaqua.Button.style","colorWell");
+ Border b = new CompoundBorder(new VisualMargin(), new SmallColorWellBorder());
+ zeroPercentButton.setBorder(b);
+ twentyFivePercentButton.setBorder(b);
+ fiftyPercentButton.setBorder(b);
+ seventyFivePercentButton.setBorder(b);
+ hundredPercentButton.setBorder(b);
+ Insets bmInsets = UIManager.getInsets("Component.visualMargin");
+ Dimension d = new Dimension(12+bmInsets.left+bmInsets.right,12+bmInsets.top+bmInsets.bottom);
+ zeroPercentButton.setPreferredSize(d);
+ twentyFivePercentButton.setPreferredSize(d);
+ fiftyPercentButton.setPreferredSize(d);
+ seventyFivePercentButton.setPreferredSize(d);
+ hundredPercentButton.setPreferredSize(d);
+ }
+
+ @Override
+ protected void buildChooser() {
+ }
+
+ @Override
+ public String getDisplayName() {
+ return UIManager.getString("ColorChooser.grayScaleSlider");
+ }
+
+ @Override
+ public Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.colorSlidersIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ /**
+ * We have to prevent us from constantly updating the color model, because
+ * the gray chooser is not able to preserve all color components.
+ */
+ private int updateRecursion;
+
+ @Override
+ public void updateChooser() {
+ updateRecursion++;
+ Color cfm = getColorFromModel();
+ ccModel.setColor(cfm);
+ updateRecursion--;
+ }
+ public void setColorToModel(Color color) {
+ getColorSelectionModel().setSelectedColor(color);
+
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
+ private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+
+ brightnessLabel = new javax.swing.JLabel();
+ brightnessSlider = new javax.swing.JSlider();
+ brightnessFieldPanel = new javax.swing.JPanel();
+ brightnessField = new javax.swing.JTextField();
+ brightnessFieldLabel = new javax.swing.JLabel();
+ springPanel = new javax.swing.JPanel();
+ percentPanel = new javax.swing.JPanel();
+ zeroPercentButton = new javax.swing.JButton();
+ twentyFivePercentButton = new javax.swing.JButton();
+ fiftyPercentButton = new javax.swing.JButton();
+ seventyFivePercentButton = new javax.swing.JButton();
+ hundredPercentButton = new javax.swing.JButton();
+
+ setLayout(new java.awt.GridBagLayout());
+
+ brightnessLabel.setText(UIManager.getString("ColorChooser.hsbBrightnessText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(brightnessLabel, gridBagConstraints);
+
+ brightnessSlider.setMajorTickSpacing(100);
+ brightnessSlider.setMinorTickSpacing(50);
+ brightnessSlider.setPaintTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridwidth = 5;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weightx = 1.0;
+ add(brightnessSlider, gridBagConstraints);
+
+ brightnessFieldPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 0, 0));
+
+ brightnessField.setColumns(3);
+ brightnessField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ brightnessField.setText("0");
+ brightnessField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ brightnessFieldFocusLost(evt);
+ }
+ });
+
+ brightnessFieldPanel.add(brightnessField);
+
+ brightnessFieldLabel.setText("%");
+ brightnessFieldPanel.add(brightnessFieldLabel);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 5;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(brightnessFieldPanel, gridBagConstraints);
+
+ springPanel.setLayout(new java.awt.BorderLayout());
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 100;
+ gridBagConstraints.weighty = 1.0;
+ add(springPanel, gridBagConstraints);
+
+ percentPanel.setLayout(new java.awt.GridBagLayout());
+
+ zeroPercentButton.setBackground(new java.awt.Color(0, 0, 0));
+ zeroPercentButton.setToolTipText("0 %");
+ zeroPercentButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ percentActionPerformed(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ percentPanel.add(zeroPercentButton, gridBagConstraints);
+
+ twentyFivePercentButton.setBackground(new java.awt.Color(64, 64, 64));
+ twentyFivePercentButton.setToolTipText("25 %");
+ twentyFivePercentButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ percentActionPerformed(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.weightx = 1.0;
+ percentPanel.add(twentyFivePercentButton, gridBagConstraints);
+
+ fiftyPercentButton.setBackground(new java.awt.Color(128, 128, 128));
+ fiftyPercentButton.setToolTipText("50 %");
+ fiftyPercentButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ percentActionPerformed(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridy = 2;
+ percentPanel.add(fiftyPercentButton, gridBagConstraints);
+
+ seventyFivePercentButton.setBackground(new java.awt.Color(192, 192, 192));
+ seventyFivePercentButton.setToolTipText("75 %");
+ seventyFivePercentButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ percentActionPerformed(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.weightx = 1.0;
+ percentPanel.add(seventyFivePercentButton, gridBagConstraints);
+
+ hundredPercentButton.setBackground(new java.awt.Color(255, 255, 255));
+ hundredPercentButton.setToolTipText("100 %");
+ hundredPercentButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ percentActionPerformed(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
+ percentPanel.add(hundredPercentButton, gridBagConstraints);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.gridwidth = 5;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ add(percentPanel, gridBagConstraints);
+
+ }// </editor-fold>//GEN-END:initComponents
+
+ private void percentActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_percentActionPerformed
+ setColorToModel(((JButton) evt.getSource()).getBackground());
+ }//GEN-LAST:event_percentActionPerformed
+
+ private void brightnessFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_brightnessFieldFocusLost
+ brightnessField.setText(Integer.toString(ccModel.getBoundedRangeModel(0).getValue()));
+ }//GEN-LAST:event_brightnessFieldFocusLost
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JTextField brightnessField;
+ private javax.swing.JLabel brightnessFieldLabel;
+ private javax.swing.JPanel brightnessFieldPanel;
+ private javax.swing.JLabel brightnessLabel;
+ private javax.swing.JSlider brightnessSlider;
+ private javax.swing.JButton fiftyPercentButton;
+ private javax.swing.JButton hundredPercentButton;
+ private javax.swing.JPanel percentPanel;
+ private javax.swing.JButton seventyFivePercentButton;
+ private javax.swing.JPanel springPanel;
+ private javax.swing.JButton twentyFivePercentButton;
+ private javax.swing.JButton zeroPercentButton;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/GrayColorSliderModel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/GrayColorSliderModel.java
new file mode 100644
index 0000000..65eea70
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/GrayColorSliderModel.java
@@ -0,0 +1,56 @@
+/*
+ * @(#)GrayColorSliderModel.java 1.0 May 22, 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import javax.swing.*;
+/**
+ * A ColorSliderModel for a gray color model (brightness).
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 May 22, 2005 Created.
+ */
+public class GrayColorSliderModel extends ColorSliderModel {
+
+ /**
+ * Creates a new instance.
+ */
+ public GrayColorSliderModel() {
+ super(new DefaultBoundedRangeModel[] {
+ new DefaultBoundedRangeModel(0, 0, 0, 100)
+ });
+ }
+
+ @Override
+ public int getRGB() {
+ int br = (int) (components[0].getValue() * 2.55f);
+ return 0xff000000 | (br << 16) | (br << 8) | (br);
+ }
+
+ @Override
+ public void setRGB(int rgb) {
+ components[0].setValue((int)
+ (
+ (((rgb & 0xff0000) >> 16) + ((rgb & 0x00ff00) >> 8) + (rgb & 0x0000ff))
+ / 3f / 2.55f
+ )
+ );
+ }
+
+ @Override
+ public int toRGB(int[] values) {
+ int br = (int) (values[0] * 2.55f);
+ return 0xff000000 | (br << 16) | (br << 8) | (br);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HSBChooser.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HSBChooser.form
new file mode 100644
index 0000000..409d783
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HSBChooser.form
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <AuxValues>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,110"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="hueLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.hsbHueText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="hueSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="359"/>
+ <Property name="maximum" type="int" value="359"/>
+ <Property name="minorTickSpacing" type="int" value="180"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JPanel" name="hueFieldPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout">
+ <Property name="horizontalGap" type="int" value="0"/>
+ <Property name="verticalGap" type="int" value="0"/>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JTextField" name="hueField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="hueFieldFocusLost"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JLabel" name="hueFieldLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="°"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Component class="javax.swing.JLabel" name="saturationLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.hsbSaturationText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="saturationSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="100"/>
+ <Property name="minorTickSpacing" type="int" value="50"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JPanel" name="saturationFieldPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout">
+ <Property name="horizontalGap" type="int" value="0"/>
+ <Property name="verticalGap" type="int" value="0"/>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JTextField" name="saturationField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="saturationFieldFocusLost"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JLabel" name="saturationFieldLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="%"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Component class="javax.swing.JLabel" name="brightnessLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.hsbBrightnessText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="brightnessSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="100"/>
+ <Property name="minorTickSpacing" type="int" value="50"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JPanel" name="brightnessFieldPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="4" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout">
+ <Property name="horizontalGap" type="int" value="0"/>
+ <Property name="verticalGap" type="int" value="0"/>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JTextField" name="brightnessField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="brightnessFieldFocusLost"/>
+ </Events>
+ </Component>
+ <Component class="javax.swing.JLabel" name="brightnessFieldLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" value="%"/>
+ </Properties>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Container class="javax.swing.JPanel" name="springPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="100" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="1.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HSBChooser.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HSBChooser.java
new file mode 100644
index 0000000..f95322c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HSBChooser.java
@@ -0,0 +1,323 @@
+/*
+ * @(#)HSBChooser.java 1.3 2006-04-23
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entehue into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+import javax.swing.border.*;
+import javax.swing.colorchooser.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.*;
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+/**
+ * A ColorChooser with HSB sliders.
+ *
+ * @author Werner Randelshofer
+ * @version 1.3 2006-04-23 Retrieve labels from UIManager.
+ * <br>1.2 2005-11-22 Moved handler for text fields into separate class.
+ * <br>1.2.1 2005-11-07 Get "Labels" resource bundle from UIManager.
+ * <br>1.2 2005-09-05 Get font, spacing and icon from UIManager.
+ * <br>1.1.1 2005-04-23 Localized form.
+ * <br>1.0 29 March 2005 Created.
+ */
+public class HSBChooser
+extends AbstractColorChooserPanel
+implements UIResource {
+ private ColorSliderModel ccModel = new HSBColorSliderModel();
+
+ /** Creates new form. */
+ public HSBChooser() {
+ initComponents();
+
+ //
+ Font font = UIManager.getFont("ColorChooser.font");
+ hueLabel.setFont(font);
+ hueSlider.setFont(font);
+ hueField.setFont(font);
+ hueFieldLabel.setFont(font);
+ saturationLabel.setFont(font);
+ saturationSlider.setFont(font);
+ saturationField.setFont(font);
+ saturationFieldLabel.setFont(font);
+ brightnessLabel.setFont(font);
+ brightnessSlider.setFont(font);
+ brightnessField.setFont(font);
+ brightnessFieldLabel.setFont(font);
+ //
+ int textSliderGap = UIManager.getInt("ColorChooser.textSliderGap");
+ if (textSliderGap != 0) {
+ Border fieldBorder = new EmptyBorder(0,textSliderGap,0,0);
+ hueFieldPanel.setBorder(fieldBorder);
+ saturationFieldPanel.setBorder(fieldBorder);
+ brightnessFieldPanel.setBorder(fieldBorder);
+ }
+
+ ccModel.configureColorSlider(0, hueSlider);
+ ccModel.configureColorSlider(1, saturationSlider);
+ ccModel.configureColorSlider(2, brightnessSlider);
+ hueField.setText(Integer.toString(hueSlider.getValue()));
+ saturationField.setText(Integer.toString(saturationSlider.getValue()));
+ brightnessField.setText(Integer.toString(brightnessSlider.getValue()));
+ Insets borderMargin = (Insets) UIManager.getInsets("Component.visualMargin").clone();
+ borderMargin.left = 3 - borderMargin.left;
+ hueFieldLabel.putClientProperty("Quaqua.Component.visualMargin",borderMargin);
+ saturationFieldLabel.putClientProperty("Quaqua.Component.visualMargin",borderMargin);
+ brightnessFieldLabel.putClientProperty("Quaqua.Component.visualMargin",borderMargin);
+
+ new ColorSliderTextFieldHandler(hueField, ccModel, 0);
+ new ColorSliderTextFieldHandler(saturationField, ccModel, 1);
+ new ColorSliderTextFieldHandler(brightnessField, ccModel, 2);
+
+ ccModel.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent evt) {
+ setColorToModel(ccModel.getColor());
+ }
+ });
+ hueField.setMinimumSize(hueField.getPreferredSize());
+ saturationField.setMinimumSize(saturationField.getPreferredSize());
+ brightnessField.setMinimumSize(brightnessField.getPreferredSize());
+ VisualMargin bm = new VisualMargin(false,false,true,false);
+ hueLabel.setBorder(bm);
+ saturationLabel.setBorder(bm);
+ brightnessLabel.setBorder(bm);
+ }
+
+ @Override
+ protected void buildChooser() {
+ }
+
+ @Override
+ public String getDisplayName() {
+ return UIManager.getString("ColorChooser.hsbSliders");
+ }
+
+ @Override
+ public Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.colorSlidersIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ @Override
+ public void updateChooser() {
+ ccModel.setColor(getColorFromModel());
+ }
+ public void setColorToModel(Color color) {
+ getColorSelectionModel().setSelectedColor(color);
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
+ private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+
+ hueLabel = new javax.swing.JLabel();
+ hueSlider = new javax.swing.JSlider();
+ hueFieldPanel = new javax.swing.JPanel();
+ hueField = new javax.swing.JTextField();
+ hueFieldLabel = new javax.swing.JLabel();
+ saturationLabel = new javax.swing.JLabel();
+ saturationSlider = new javax.swing.JSlider();
+ saturationFieldPanel = new javax.swing.JPanel();
+ saturationField = new javax.swing.JTextField();
+ saturationFieldLabel = new javax.swing.JLabel();
+ brightnessLabel = new javax.swing.JLabel();
+ brightnessSlider = new javax.swing.JSlider();
+ brightnessFieldPanel = new javax.swing.JPanel();
+ brightnessField = new javax.swing.JTextField();
+ brightnessFieldLabel = new javax.swing.JLabel();
+ springPanel = new javax.swing.JPanel();
+
+ setLayout(new java.awt.GridBagLayout());
+
+ hueLabel.setText(UIManager.getString("ColorChooser.hsbHueText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(hueLabel, gridBagConstraints);
+
+ hueSlider.setMajorTickSpacing(359);
+ hueSlider.setMaximum(359);
+ hueSlider.setMinorTickSpacing(180);
+ hueSlider.setPaintTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weightx = 1.0;
+ add(hueSlider, gridBagConstraints);
+
+ hueFieldPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 0, 0));
+
+ hueField.setColumns(3);
+ hueField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ hueField.setText("0");
+ hueField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ hueFieldFocusLost(evt);
+ }
+ });
+
+ hueFieldPanel.add(hueField);
+
+ hueFieldLabel.setText("\u00b0");
+ hueFieldPanel.add(hueFieldLabel);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(hueFieldPanel, gridBagConstraints);
+
+ saturationLabel.setText(UIManager.getString("ColorChooser.hsbSaturationText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(saturationLabel, gridBagConstraints);
+
+ saturationSlider.setMajorTickSpacing(100);
+ saturationSlider.setMinorTickSpacing(50);
+ saturationSlider.setPaintTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weightx = 1.0;
+ add(saturationSlider, gridBagConstraints);
+
+ saturationFieldPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 0, 0));
+
+ saturationField.setColumns(3);
+ saturationField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ saturationField.setText("0");
+ saturationField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ saturationFieldFocusLost(evt);
+ }
+ });
+
+ saturationFieldPanel.add(saturationField);
+
+ saturationFieldLabel.setText("%");
+ saturationFieldPanel.add(saturationFieldLabel);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(saturationFieldPanel, gridBagConstraints);
+
+ brightnessLabel.setText(UIManager.getString("ColorChooser.hsbBrightnessText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(brightnessLabel, gridBagConstraints);
+
+ brightnessSlider.setMajorTickSpacing(100);
+ brightnessSlider.setMinorTickSpacing(50);
+ brightnessSlider.setPaintTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weightx = 1.0;
+ add(brightnessSlider, gridBagConstraints);
+
+ brightnessFieldPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 0, 0));
+
+ brightnessField.setColumns(3);
+ brightnessField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ brightnessField.setText("0");
+ brightnessField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ brightnessFieldFocusLost(evt);
+ }
+ });
+
+ brightnessFieldPanel.add(brightnessField);
+
+ brightnessFieldLabel.setText("%");
+ brightnessFieldPanel.add(brightnessFieldLabel);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 4;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(brightnessFieldPanel, gridBagConstraints);
+
+ springPanel.setLayout(new java.awt.BorderLayout());
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 100;
+ gridBagConstraints.weighty = 1.0;
+ add(springPanel, gridBagConstraints);
+
+ }// </editor-fold>//GEN-END:initComponents
+
+ private void brightnessFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_brightnessFieldFocusLost
+ brightnessField.setText(Integer.toString(ccModel.getBoundedRangeModel(2).getValue()));
+ }//GEN-LAST:event_brightnessFieldFocusLost
+
+ private void saturationFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_saturationFieldFocusLost
+ saturationField.setText(Integer.toString(ccModel.getBoundedRangeModel(1).getValue()));
+ }//GEN-LAST:event_saturationFieldFocusLost
+
+ private void hueFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_hueFieldFocusLost
+ hueField.setText(Integer.toString(ccModel.getBoundedRangeModel(0).getValue()));
+ }//GEN-LAST:event_hueFieldFocusLost
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JTextField brightnessField;
+ private javax.swing.JLabel brightnessFieldLabel;
+ private javax.swing.JPanel brightnessFieldPanel;
+ private javax.swing.JLabel brightnessLabel;
+ private javax.swing.JSlider brightnessSlider;
+ private javax.swing.JTextField hueField;
+ private javax.swing.JLabel hueFieldLabel;
+ private javax.swing.JPanel hueFieldPanel;
+ private javax.swing.JLabel hueLabel;
+ private javax.swing.JSlider hueSlider;
+ private javax.swing.JTextField saturationField;
+ private javax.swing.JLabel saturationFieldLabel;
+ private javax.swing.JPanel saturationFieldPanel;
+ private javax.swing.JLabel saturationLabel;
+ private javax.swing.JSlider saturationSlider;
+ private javax.swing.JPanel springPanel;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HSBColorSliderModel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HSBColorSliderModel.java
new file mode 100644
index 0000000..2690b98
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HSBColorSliderModel.java
@@ -0,0 +1,62 @@
+/*
+ * @(#)HSBColorSliderModel.java 1.0 May 22, 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import javax.swing.*;
+/**
+ * ColorSliderModel for the HSB color model (hue, saturation, brightness).
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 May 22, 2005 Created.
+ */
+public class HSBColorSliderModel extends ColorSliderModel {
+
+ /**
+ * Creates a new instance.
+ */
+ public HSBColorSliderModel() {
+ super(new DefaultBoundedRangeModel[] {
+ new DefaultBoundedRangeModel(0, 0, 0, 359),
+ new DefaultBoundedRangeModel(0, 0, 0, 100),
+ new DefaultBoundedRangeModel(0, 0, 0, 100)
+ });
+ }
+
+ @Override
+ public int getRGB() {
+ return Color.HSBtoRGB(
+ components[0].getValue() / 360f,
+ components[1].getValue() / 100f,
+ components[2].getValue() / 100f
+ );
+ }
+
+ @Override
+ public void setRGB(int rgb) {
+ float[] hsb = Color.RGBtoHSB((rgb & 0xff0000) >>> 16, (rgb & 0xff00) >>> 8, rgb & 0xff, new float[3]);
+ components[0].setValue((int) (hsb[0] * 360f));
+ components[1].setValue((int) (hsb[1] * 100f));
+ components[2].setValue((int) (hsb[2] * 100f));
+ }
+
+ @Override
+ public int toRGB(int[] values) {
+ return Color.HSBtoRGB(
+ values[0] / 360f,
+ values[1] / 100f,
+ values[2] / 100f
+ );
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HTMLChooser.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HTMLChooser.form
new file mode 100644
index 0000000..ccf3165
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HTMLChooser.form
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <AuxValues>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,105"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="redLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.rgbRedText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="redSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="255"/>
+ <Property name="maximum" type="int" value="255"/>
+ <Property name="minorTickSpacing" type="int" value="51"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ <Property name="snapToTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JTextField" name="redField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="redFieldFocusLost"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="-1" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JLabel" name="greenLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.rgbGreenText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JTextField" name="greenField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="greenFieldFocusLost"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="-1" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="greenSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="255"/>
+ <Property name="maximum" type="int" value="255"/>
+ <Property name="minorTickSpacing" type="int" value="51"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ <Property name="snapToTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JLabel" name="blueLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.rgbBlueText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="blueSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="255"/>
+ <Property name="maximum" type="int" value="255"/>
+ <Property name="minorTickSpacing" type="int" value="51"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ <Property name="snapToTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JTextField" name="blueField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="blueFieldFocusLost"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="-1" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JPanel" name="htmlPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="0" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="12" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="13" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout">
+ <Property name="horizontalGap" type="int" value="0"/>
+ </Layout>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="htmlLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.htmlText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
+ <Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
+ <EmptyBorder bottom="0" left="0" right="4" top="0"/>
+ </Border>
+ </Property>
+ </Properties>
+ </Component>
+ <Component class="javax.swing.JTextField" name="htmlField">
+ <Properties>
+ <Property name="columns" type="int" value="7"/>
+ <Property name="text" type="java.lang.String" value="#000000"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="htmlFieldFocusLost"/>
+ </Events>
+ </Component>
+ </SubComponents>
+ </Container>
+ <Component class="javax.swing.JCheckBox" name="webSaveCheckBox">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.htmlChooseOnlyWebSaveColorsText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Events>
+ <EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="webSaveChanged"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="0" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="17" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JPanel" name="springPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="100" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="1.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HTMLChooser.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HTMLChooser.java
new file mode 100644
index 0000000..e941586
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HTMLChooser.java
@@ -0,0 +1,467 @@
+/*
+ * @(#)HTMLChooser.java 1.5 2006-04-23
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+import javax.swing.border.*;
+import javax.swing.colorchooser.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.*;
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+
+import java.util.*;
+/**
+ * HTMLChooser.
+ *
+ * @author Werner Randelshofer
+ * @version 1.5 2006-04-23 Retrieve labels from UIManager.
+ * <br>1.4 2005-11-22 Moved handler for text fields into separate class.
+ * <br>1.3.1 2005-11-07 Get "Labels" resource bundle from UIManager.
+ * <br>1.3 2005-09-05 Get font,spacing and icon from UIManager.
+ * <br>1.2 2005-08-28 Remember last selection state of "webSaveCheckBox".
+ * <br>1.1.1 2005-06-19 Sliders were not updated when a color was entered
+ * into the HTML field.
+ * <br>1.1 2005-04-18 Localized form.
+ * <br>1.0 29 March 2005 Created.
+ */
+public class HTMLChooser extends AbstractColorChooserPanel implements UIResource {
+ private HTMLColorSliderModel ccModel = new HTMLColorSliderModel();
+ private ChangeListener htmlListener;
+
+ /**
+ * This is used to remember the last selection state of the "webSaveCheckBox".
+ */
+ private static boolean lastWebSaveSelectionState = false;
+
+ /**
+ * We have to prevent us from constantly updating the color model, because
+ * the gray chooser is not able to preserve all color components.
+ */
+ private int updateRecursion;
+
+ /**
+ * W3C HTML 4.1 well known color names.
+ */
+ private final static Object[][] colorNames = {
+ {"Black", new Color(0x000000)},
+ {"Green", new Color(0x008000)},
+ {"Silver", new Color(0xC0C0C0)},
+ {"Lime", new Color(0x00FF00)},
+ {"Gray", new Color(0x808080)},
+ {"Olive", new Color(0x808000)},
+ {"White", new Color(0xFFFFFF)},
+ {"Yellow", new Color(0xFFFF00)},
+ {"Maroon", new Color(0x800000)},
+ {"Navy", new Color(0x000080)},
+ {"Red", new Color(0xFF0000)},
+ {"Blue", new Color(0x0000FF)},
+ {"Purple", new Color(0x800080)},
+ {"Teal", new Color(0x008080)},
+ {"Fuchsia", new Color(0xFF00FF)},
+ {"Aqua", new Color(0x00FF)}
+ };
+ private final static HashMap nameToColorMap = new HashMap();
+ static {
+ for (int i=0; i < colorNames.length; i++) {
+ nameToColorMap.put(((String) colorNames[i][0]).toLowerCase(), colorNames[i][1]);
+ }
+ }
+
+
+ /** Creates new form. */
+ public HTMLChooser() {
+ initComponents();
+
+ //
+ Font font = UIManager.getFont("ColorChooser.font");
+ redLabel.setFont(font);
+ redSlider.setFont(font);
+ redField.setFont(font);
+ greenLabel.setFont(font);
+ greenField.setFont(font);
+ greenSlider.setFont(font);
+ blueLabel.setFont(font);
+ blueSlider.setFont(font);
+ blueField.setFont(font);
+ htmlLabel.setFont(font);
+ htmlField.setFont(font);
+ webSaveCheckBox.setFont(font);
+ //
+ int textSliderGap = UIManager.getInt("ColorChooser.textSliderGap");
+ if (textSliderGap != 0) {
+ Insets fieldInsets = new Insets(0,textSliderGap,0,0);
+ GridBagLayout layout = (GridBagLayout) getLayout();
+ GridBagConstraints gbc;
+ gbc = layout.getConstraints(redField);
+ gbc.insets = fieldInsets;
+ layout.setConstraints(redField, gbc);
+ gbc = layout.getConstraints(greenField);
+ gbc.insets = fieldInsets;
+ layout.setConstraints(greenField, gbc);
+ gbc = layout.getConstraints(blueField);
+ gbc.insets = fieldInsets;
+ layout.setConstraints(blueField, gbc);
+ }
+
+ webSaveCheckBox.setSelected(lastWebSaveSelectionState);
+ redSlider.setSnapToTicks(lastWebSaveSelectionState);
+ greenSlider.setSnapToTicks(lastWebSaveSelectionState);
+ blueSlider.setSnapToTicks(lastWebSaveSelectionState);
+
+
+ htmlListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent evt) {
+ Color c = ccModel.getColor();
+ setColorToModel(c);
+ if (! c.equals(nameToColorMap.get(htmlField.getText().toLowerCase()))) {
+ if (! htmlField.hasFocus()) {
+ String hex = Integer.toHexString(0xffffff & c.getRGB());
+ StringBuffer buf = new StringBuffer(7);
+ buf.append('#');
+ for (int i=hex.length(); i < 6; i++) {
+ buf.append('0');
+ }
+ buf.append(hex.toUpperCase());
+ if (! htmlField.getText().equals(buf.toString())) {
+ htmlField.setText(buf.toString());
+ }
+ }
+ }
+ }
+ };
+
+ updateRecursion++;
+
+ ccModel = new HTMLColorSliderModel();
+ ccModel.setWebSaveOnly(lastWebSaveSelectionState);
+ ccModel.configureColorSlider(0, redSlider);
+ ccModel.configureColorSlider(1, greenSlider);
+ ccModel.configureColorSlider(2, blueSlider);
+ new ColorSliderTextFieldHandler(redField, ccModel, 0);
+ new ColorSliderTextFieldHandler(greenField, ccModel, 1);
+ new ColorSliderTextFieldHandler(blueField, ccModel, 2);
+ ccModel.addChangeListener(htmlListener);
+
+ redFieldFocusLost(null);
+ greenFieldFocusLost(null);
+ blueFieldFocusLost(null);
+ htmlFieldFocusLost(null);
+ updateRecursion--;
+ redField.setMinimumSize(redField.getPreferredSize());
+ greenField.setMinimumSize(greenField.getPreferredSize());
+ blueField.setMinimumSize(blueField.getPreferredSize());
+ htmlPanel.setMinimumSize(htmlPanel.getPreferredSize());
+ VisualMargin bm = new VisualMargin(false,false,true,false);
+ redLabel.setBorder(bm);
+ greenLabel.setBorder(bm);
+ blueLabel.setBorder(bm);
+ }
+ @Override
+ protected void buildChooser() {
+ }
+
+ @Override
+ public String getDisplayName() {
+ return UIManager.getString("ColorChooser.htmlSliders");
+ }
+
+ @Override
+ public Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.colorSlidersIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ @Override
+ public void updateChooser() {
+ if (updateRecursion == 0) {
+ updateRecursion++;
+ if (ccModel.isWebSaveOnly()) {
+ Color c = getColorFromModel();
+ if (c == null || ! HTMLColorSliderModel.isWebSave(c.getRGB())) {
+ webSaveCheckBox.setSelected(false);
+ }
+ }
+ ccModel.setColor(getColorFromModel());
+
+ updateRecursion--;
+ }
+ }
+
+ public void setColorToModel(Color color) {
+ if (updateRecursion == 0) {
+ updateRecursion++;
+ getColorSelectionModel().setSelectedColor(color);
+ updateRecursion--;
+ }
+ }
+
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
+ private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+
+ redLabel = new javax.swing.JLabel();
+ redSlider = new javax.swing.JSlider();
+ redField = new javax.swing.JTextField();
+ greenLabel = new javax.swing.JLabel();
+ greenField = new javax.swing.JTextField();
+ greenSlider = new javax.swing.JSlider();
+ blueLabel = new javax.swing.JLabel();
+ blueSlider = new javax.swing.JSlider();
+ blueField = new javax.swing.JTextField();
+ htmlPanel = new javax.swing.JPanel();
+ htmlLabel = new javax.swing.JLabel();
+ htmlField = new javax.swing.JTextField();
+ webSaveCheckBox = new javax.swing.JCheckBox();
+ springPanel = new javax.swing.JPanel();
+
+ setLayout(new java.awt.GridBagLayout());
+
+ redLabel.setText(UIManager.getString("ColorChooser.rgbRedText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(redLabel, gridBagConstraints);
+
+ redSlider.setMajorTickSpacing(255);
+ redSlider.setMaximum(255);
+ redSlider.setMinorTickSpacing(51);
+ redSlider.setPaintTicks(true);
+ redSlider.setSnapToTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weightx = 1.0;
+ add(redSlider, gridBagConstraints);
+
+ redField.setColumns(3);
+ redField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ redField.setText("0");
+ redField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ redFieldFocusLost(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(redField, gridBagConstraints);
+
+ greenLabel.setText(UIManager.getString("ColorChooser.rgbGreenText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(greenLabel, gridBagConstraints);
+
+ greenField.setColumns(3);
+ greenField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ greenField.setText("0");
+ greenField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ greenFieldFocusLost(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(greenField, gridBagConstraints);
+
+ greenSlider.setMajorTickSpacing(255);
+ greenSlider.setMaximum(255);
+ greenSlider.setMinorTickSpacing(51);
+ greenSlider.setPaintTicks(true);
+ greenSlider.setSnapToTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weightx = 1.0;
+ add(greenSlider, gridBagConstraints);
+
+ blueLabel.setText(UIManager.getString("ColorChooser.rgbBlueText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(blueLabel, gridBagConstraints);
+
+ blueSlider.setMajorTickSpacing(255);
+ blueSlider.setMaximum(255);
+ blueSlider.setMinorTickSpacing(51);
+ blueSlider.setPaintTicks(true);
+ blueSlider.setSnapToTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.weightx = 1.0;
+ add(blueSlider, gridBagConstraints);
+
+ blueField.setColumns(3);
+ blueField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ blueField.setText("0");
+ blueField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ blueFieldFocusLost(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(blueField, gridBagConstraints);
+
+ htmlPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 0, 5));
+
+ htmlLabel.setText(UIManager.getString("ColorChooser.htmlText"));
+ htmlLabel.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 4));
+ htmlPanel.add(htmlLabel);
+
+ htmlField.setColumns(7);
+ htmlField.setText("#000000");
+ htmlField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ htmlFieldFocusLost(evt);
+ }
+ });
+
+ htmlPanel.add(htmlField);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
+ gridBagConstraints.insets = new java.awt.Insets(12, 0, 0, 0);
+ add(htmlPanel, gridBagConstraints);
+
+ webSaveCheckBox.setText(UIManager.getString("ColorChooser.htmlChooseOnlyWebSaveColorsText"));
+ webSaveCheckBox.addItemListener(new java.awt.event.ItemListener() {
+ @Override
+ public void itemStateChanged(java.awt.event.ItemEvent evt) {
+ webSaveChanged(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ add(webSaveCheckBox, gridBagConstraints);
+
+ springPanel.setLayout(new java.awt.BorderLayout());
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 100;
+ gridBagConstraints.weighty = 1.0;
+ add(springPanel, gridBagConstraints);
+
+ }// </editor-fold>//GEN-END:initComponents
+
+ private void blueFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_blueFieldFocusLost
+ String hex = Integer.toHexString(ccModel.getBoundedRangeModel(2).getValue()).toUpperCase();
+ blueField.setText((hex.length() == 1) ? "0"+hex : hex);
+ }//GEN-LAST:event_blueFieldFocusLost
+
+ private void greenFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_greenFieldFocusLost
+ String hex = Integer.toHexString(ccModel.getBoundedRangeModel(1).getValue()).toUpperCase();
+ greenField.setText((hex.length() == 1) ? "0"+hex : hex);
+ }//GEN-LAST:event_greenFieldFocusLost
+
+ private void redFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_redFieldFocusLost
+ String hex = Integer.toHexString(ccModel.getBoundedRangeModel(0).getValue()).toUpperCase();
+ redField.setText((hex.length() == 1) ? "0"+hex : hex);
+ }//GEN-LAST:event_redFieldFocusLost
+
+ private void htmlFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_htmlFieldFocusLost
+ Color mc = ccModel.getColor();
+
+ Color fc = (Color) nameToColorMap.get(htmlField.getText().toLowerCase());
+ if (fc == null || ! fc.equals(mc)) {
+ String hex = Integer.toHexString(0xffffff & mc.getRGB());
+ StringBuffer buf = new StringBuffer(7);
+ buf.append('#');
+ for (int i=hex.length(); i < 6; i++) {
+ buf.append('0');
+ }
+ buf.append(hex.toUpperCase());
+ htmlField.setText(buf.toString());
+ }
+ }//GEN-LAST:event_htmlFieldFocusLost
+
+ private void webSaveChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_webSaveChanged
+
+
+ // TODO add your handling code here:
+ boolean b = webSaveCheckBox.isSelected();
+ redSlider.setSnapToTicks(b);
+ greenSlider.setSnapToTicks(b);
+ blueSlider.setSnapToTicks(b);
+ lastWebSaveSelectionState = b;
+ /*
+ redSlider.repaint();
+ greenSlider.repaint();
+ blueSlider.repaint();
+ */
+ ccModel.setWebSaveOnly(b);
+
+ }//GEN-LAST:event_webSaveChanged
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JTextField blueField;
+ private javax.swing.JLabel blueLabel;
+ private javax.swing.JSlider blueSlider;
+ private javax.swing.JTextField greenField;
+ private javax.swing.JLabel greenLabel;
+ private javax.swing.JSlider greenSlider;
+ private javax.swing.JTextField htmlField;
+ private javax.swing.JLabel htmlLabel;
+ private javax.swing.JPanel htmlPanel;
+ private javax.swing.JTextField redField;
+ private javax.swing.JLabel redLabel;
+ private javax.swing.JSlider redSlider;
+ private javax.swing.JPanel springPanel;
+ private javax.swing.JCheckBox webSaveCheckBox;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HTMLColorSliderModel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HTMLColorSliderModel.java
new file mode 100644
index 0000000..2892c44
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HTMLColorSliderModel.java
@@ -0,0 +1,104 @@
+/*
+ * @(#)HTMLColorSliderModel.java 1.0.1 2005-08-28
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import javax.swing.*;
+/**
+ * ColorSliderModel for the HTML color model (red, green, blue, restricted
+ * to values considered as web-save).
+ *
+ * @author Werner Randelshofer
+ * @version 1.0.1 2005-08-28 Method toWebSave generates now ARGB value instead
+ * of just an RGB value. Method isWebSave ignores the alpha channel of a color.
+ * <br>1.0 May 22, 2005 Created.
+ */
+public class HTMLColorSliderModel extends RGBColorSliderModel {
+ private boolean isWebSaveOnly = true;
+ /**
+ * Creates a new instance.
+ */
+ public HTMLColorSliderModel() {
+ }
+
+ @Override
+ public int getRGB() {
+ return getRGB(components[0].getValue(), components[1].getValue(), components[2].getValue());
+ }
+ @Override
+ public int getInterpolatedRGB(int component, float value) {
+ if (isWebSaveOnly) {
+ for (int i=0, n = getComponentCount(); i < n; i++) {
+ values[i] = Math.round(components[i].getValue() / 51f) * 51;
+ }
+ values[component] = Math.round((value * components[component].getMaximum()) / 51f) * 51;
+ return toRGB(values);
+ } else {
+ return super.getInterpolatedRGB(component, value);
+ }
+ }
+ @Override
+ protected int getRGB(int r, int g, int b) {
+ if (isWebSaveOnly) {
+ return 0xff000000 | (Math.round(r / 51f) * 51) << 16 | (Math.round(g / 51f) * 51) << 8 | Math.round(b / 51f) * 51;
+ } else {
+ return super.getRGB(r, g, b);
+ }
+ }
+
+ @Override
+ public void setRGB(int rgb) {
+ if (isWebSaveOnly) {
+ components[0].setValue((Math.round((rgb & 0xff0000) / 51f) * 51) >> 16);
+ components[1].setValue((Math.round((rgb & 0x00ff00) / 51f) * 51) >> 8);
+ components[2].setValue(Math.round((rgb & 0x0000ff) / 51f) * 51);
+ } else {
+ super.setRGB(rgb);
+ }
+ }
+
+ @Override
+ public int toRGB(int[] values) {
+ if (isWebSaveOnly) {
+ return 0xff000000
+ | (Math.round(values[0] / 51f) * 51) << 16
+ | (Math.round(values[1] / 51f) * 51) << 8
+ | (Math.round(values[2] / 51f) * 51);
+ } else {
+ return super.toRGB(values);
+ }
+ }
+
+ public void setWebSaveOnly(boolean b) {
+ isWebSaveOnly = b;
+ if (b) {
+ setRGB(getRGB());
+ }
+ fireColorChanged(-1);
+ }
+ public boolean isWebSaveOnly() {
+ return isWebSaveOnly;
+ }
+
+ public static boolean isWebSave(int rgb) {
+ return (rgb & 0xffffff) == (toWebSave(rgb) & 0xffffff);
+ }
+ public static int toWebSave(int rgb) {
+ return
+ (rgb & 0xff000000)
+ | ((Math.round(((rgb & 0xff0000) >> 16) / 51f) * 51) << 16)
+ | ((Math.round(((rgb & 0x00ff00) >> 8) / 51f) * 51) << 8)
+ | (Math.round((rgb & 0x0000ff) / 51f) * 51);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HTMLSliderTextFieldHandler.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HTMLSliderTextFieldHandler.java
new file mode 100644
index 0000000..2507068
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/HTMLSliderTextFieldHandler.java
@@ -0,0 +1,77 @@
+/*
+ * @(#)HTMLSliderTextFieldHandler.java 1.0 November 22, 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+/**
+ * This handler adjusts the value of a component in the HTML color slider model,
+ * when the user enters text into the text field.
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 November 22, 2005 Created.
+ */
+public class HTMLSliderTextFieldHandler implements DocumentListener, ChangeListener {
+ private JTextField textField;
+ private HTMLColorSliderModel ccModel;
+ private int component;
+
+ public HTMLSliderTextFieldHandler(JTextField textField, HTMLColorSliderModel ccModel, int component) {
+ this.textField = textField;
+ this.ccModel = ccModel;
+ this.component = component;
+
+ textField.getDocument().addDocumentListener(this);
+ ccModel.getBoundedRangeModel(component).addChangeListener(this);
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent evt) {
+ docChanged();
+ }
+ @Override
+ public void removeUpdate(DocumentEvent evt) {
+ docChanged();
+ }
+ @Override
+ public void insertUpdate(DocumentEvent evt) {
+ docChanged();
+ }
+ private void docChanged() {
+ if (textField.hasFocus()) {
+ BoundedRangeModel brm = ccModel.getBoundedRangeModel(component);
+ try {
+ int value = Integer.decode("#" + textField.getText());
+ if (brm.getMinimum() <= value && value <= brm.getMaximum()) {
+ brm.setValue(value);
+ }
+ } catch (NumberFormatException e) {
+ }
+ }
+ }
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (! textField.hasFocus()) {
+ int value = ccModel.getBoundedRangeModel(2).getValue();
+ if (ccModel.isWebSaveOnly()) {
+ value = Math.round(value / 51f) * 51;
+ }
+ String hex = Integer.toHexString(value).toUpperCase();
+ textField.setText((hex.length() == 1) ? "0"+hex : hex);
+ }
+ }
+}
+
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ICC_CMYKColorSliderModel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ICC_CMYKColorSliderModel.java
new file mode 100644
index 0000000..4a2c14b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/ICC_CMYKColorSliderModel.java
@@ -0,0 +1,86 @@
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+/*
+ * @(#)ICC_CMYKColorSliderModel.java 1.0 2005-05-22
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+import java.awt.*;
+import java.awt.color.*;
+import java.io.*;
+import javax.swing.*;
+/**
+ * A ColorSliderModel for CMYK color models (cyan, magenta, yellow, black) in
+ * a color space defined by a ICC color profile (International Color Consortium).
+ * <p>
+ * XXX - This does not work. I think this is because of
+ * Java bug #4760025 at
+ * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4760025
+ * but maybe I am doing something in the wrong way.
+ *
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 May 22, 2005 Created.
+ */
+public class ICC_CMYKColorSliderModel extends ColorSliderModel {
+ private ICC_ColorSpace colorSpace;
+ float[] cmyk = new float[4];
+ float[] rgb = new float[3];
+ /**
+ * Creates a new instance.
+ */
+ public ICC_CMYKColorSliderModel(InputStream iccProfile) throws IOException {
+ super(new DefaultBoundedRangeModel[] {
+ new DefaultBoundedRangeModel(0, 0, 0, 100),
+ new DefaultBoundedRangeModel(0, 0, 0, 100),
+ new DefaultBoundedRangeModel(0, 0, 0, 100),
+ new DefaultBoundedRangeModel(0, 0, 0, 100)
+ });
+ this.colorSpace = new ICC_ColorSpace(ICC_Profile.getInstance(iccProfile));
+ }
+
+ @Override
+ public int getRGB() {
+ cmyk[0] = components[0].getValue() / 100f;
+ cmyk[1] = components[1].getValue() / 100f;
+ cmyk[2] = components[2].getValue() / 100f;
+ cmyk[3] = components[3].getValue() / 100f;
+ rgb = colorSpace.toRGB(cmyk);
+ return 0xff000000 | ((int) (rgb[0] * 255f) << 16) | ((int) (rgb[1] * 255f) << 8) | (int) (rgb[2] * 255f);
+ }
+
+ @Override
+ public void setRGB(int newRGB) {
+ rgb[0] = ((newRGB & 0xff0000) >>> 16) / 255f;
+ rgb[1] = ((newRGB & 0x00ff00) >>> 8) / 255f;
+ rgb[2] = (newRGB & 0x0000ff) / 255f;
+ cmyk = colorSpace.fromRGB(rgb);
+System.out.print("rgb in:"+new Color(newRGB));
+ components[0].setValue((int) (cmyk[0] * 100f));
+ components[1].setValue((int) (cmyk[1] * 100f));
+ components[2].setValue((int) (cmyk[2] * 100f));
+ components[3].setValue((int) (cmyk[3] * 100f));
+ rgb = colorSpace.toRGB(cmyk);
+System.out.println(" out:"+new Color((int) (rgb[0]*255f), (int)(rgb[1]*255f), (int)(rgb[2]*255f)));
+ }
+
+ @Override
+ public int toRGB(int[] values) {
+ cmyk[0] = values[0] / 100f;
+ cmyk[1] = values[1] / 100f;
+ cmyk[2] = values[2] / 100f;
+ cmyk[3] = values[3] / 100f;
+ rgb = colorSpace.toRGB(cmyk);
+ return 0xff000000 | ((int) (rgb[0] * 255f) << 16) | ((int) (rgb[1] * 255f) << 8) | (int) (rgb[2] * 255f);
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/NominalCMYKColorSliderModel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/NominalCMYKColorSliderModel.java
new file mode 100644
index 0000000..d316198
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/NominalCMYKColorSliderModel.java
@@ -0,0 +1,107 @@
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+/*
+ * @(#)ICC_CMYKColorSliderModel.java 1.0 May 22, 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+import java.awt.*;
+import java.awt.color.*;
+import java.io.*;
+import javax.swing.*;
+/**
+ * A ColorSliderModel for CMYK color models (cyan, magenta, yellow, black) with
+ * nominally converted color components from/to an RGB color model.
+ * <p>
+ * This model may not be very useful. It assumes that the color components
+ * perfectly absorb the desired wavelenghts.
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 May 22, 2005 Created.
+ */
+public class NominalCMYKColorSliderModel extends ColorSliderModel {
+ /**
+ * Creates a new instance.
+ */
+ public NominalCMYKColorSliderModel() {
+ super(new DefaultBoundedRangeModel[] {
+ new DefaultBoundedRangeModel(0, 0, 0, 100),
+ new DefaultBoundedRangeModel(0, 0, 0, 100),
+ new DefaultBoundedRangeModel(0, 0, 0, 100),
+ new DefaultBoundedRangeModel(0, 0, 0, 100)
+ });
+ }
+
+ @Override
+ public int getRGB() {
+ float cyan, magenta, yellow, black;
+
+ cyan = components[0].getValue() / 100f;
+ magenta = components[1].getValue() / 100f;
+ yellow = components[2].getValue() / 100f;
+ black = components[3].getValue() / 100f;
+
+ float red, green, blue;
+ red = 1f - cyan * (1f - black) - black;
+ green = 1f - magenta * (1f - black) - black;
+ blue = 1f - yellow * (1f - black) - black;
+ return 0xff000000
+ | ((int) (red * 255) << 16)
+ | ((int) (green * 255) << 8)
+ | (int) (blue * 255);
+ }
+
+ @Override
+ public void setRGB(int rgb) {
+ float cyan, magenta, yellow, black;
+
+ cyan = 1f - ((rgb & 0xff0000) >>> 16) / 255f;
+ magenta = 1f - ((rgb & 0x00ff00) >>> 8) / 255f;
+ yellow = 1f - (rgb & 0x0000ff) / 255f;
+ if (Math.min(Math.min(cyan, magenta), yellow) >= 1f) {
+ cyan = magenta = yellow = 0f;
+ black = 1f;
+ } else {
+ black = Math.min(Math.min(cyan, magenta), yellow);
+
+ if (black > 0f) {
+ cyan = (cyan - black) / (1 - black);
+ magenta = (magenta - black) / (1 - black);
+ yellow = (yellow - black) / (1 - black);
+ }
+ }
+
+ components[0].setValue((int) (cyan * 100f));
+ components[1].setValue((int) (magenta * 100f));
+ components[2].setValue((int) (yellow * 100f));
+ components[3].setValue((int) (black * 100f));
+ }
+
+ @Override
+ public int toRGB(int[] values) {
+ float cyan, magenta, yellow, black;
+
+ cyan = values[0] / 100f;
+ magenta = values[1] / 100f;
+ yellow = values[2] / 100f;
+ black = values[3] / 100f;
+
+ float red, green, blue;
+ red = 1f - cyan * (1f - black) - black;
+ green = 1f - magenta * (1f - black) - black;
+ blue = 1f - yellow * (1f - black) - black;
+ return 0xff000000
+ | ((int) (red * 255) << 16)
+ | ((int) (green * 255) << 8)
+ | (int) (blue * 255);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/PaletteEntry.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/PaletteEntry.java
new file mode 100644
index 0000000..eb1fb13
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/PaletteEntry.java
@@ -0,0 +1,44 @@
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+/*
+ * @(#)PaletteEntry.java 1.0 19 septembre 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+import java.awt.*;
+/**
+ * PaletteEntry.
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 19 septembre 2005 Created.
+ */
+public class PaletteEntry {
+ private String name;
+ private Color color;
+
+ /**
+ * Creates a new instance.
+ */
+ public PaletteEntry(String name, Color color) {
+ this.name = name;
+ this.color = color;
+ }
+ public String getName() {
+ return name;
+ }
+ public String toString() {
+ return name;
+ }
+ public Color getColor() {
+ return color;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/PaletteEntryCellRenderer.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/PaletteEntryCellRenderer.java
new file mode 100644
index 0000000..57d39c3
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/PaletteEntryCellRenderer.java
@@ -0,0 +1,131 @@
+/*
+ * @(#)PaletteEntryCellRenderer.java 1.0 19 septembre 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.border.*;
+/**
+ * PaletteEntryCellRenderer.
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 19 septembre 2005 Created.
+ */
+public class PaletteEntryCellRenderer extends DefaultListCellRenderer {
+ /** The following colors are used to draw the marker that marks the
+ * closest color in the palette.
+ * The "closest color" is used, when the palette does not contain an
+ * exact match to the currently selected color in the color chooser.
+ * The marker is used to help the user finding the closest color in the
+ * palette.
+ */
+ private Color closestMarker1 = new Color(0xe6e6e6);
+ private Color closestMarker2 = new Color(0xededed);
+ private Color closestMarker3 = new Color(0xf0f0f0);
+
+
+ static class ColorIcon implements Icon {
+ private Color color = Color.black;
+
+ public void setColor(Color c) {
+ this.color = c;
+ }
+ public Color getColor() {
+ return color;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return 15;
+ }
+
+ @Override
+ public int getIconWidth() {
+ return 25;
+ }
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ g.setColor(getColor());
+ g.fillRect(x + 1, y + 1, getIconWidth() - 2, getIconHeight() - 2);
+ g.setColor(getColor().darker());
+ g.drawRect(x, y, getIconWidth() - 1, getIconHeight() - 1);
+ }
+ }
+
+
+ private ColorIcon icon;
+ private boolean isClosestColor;
+
+ /**
+ * Creates a new instance.
+ */
+ public PaletteEntryCellRenderer() {
+ icon = new ColorIcon();
+ setIcon(icon);
+ setOpaque(false);
+ }
+
+ @Override
+ public Component getListCellRendererComponent(
+ JList list,
+ Object value,
+ int index,
+ boolean isSelected,
+ boolean cellHasFocus) {
+
+ setComponentOrientation(list.getComponentOrientation());
+ if (isSelected) {
+ setBackground(UIManager.getColor("ColorChooser.listSelectionBackground"));
+ setForeground(UIManager.getColor("ColorChooser.listSelectionForeground"));
+ isClosestColor = false;
+ } else {
+ setBackground(list.getBackground());
+ setForeground(list.getForeground());
+ PaletteListModel model = (PaletteListModel) list.getModel();
+ isClosestColor = model.getClosestIndex() == index;
+ }
+
+ setEnabled(list.isEnabled());
+ setFont(list.getFont());
+ //setBorder((cellHasFocus) ? UIManager.getBorder("List.focusCellHighlightBorder") : noFocusBorder);
+
+ PaletteEntry entry = (PaletteEntry) value;
+ icon.setColor(entry.getColor());
+ setText(entry.getName());
+
+ return this;
+ }
+
+ @Override
+ public void paintComponent(Graphics g) {
+ int width = getWidth();
+ int height = getHeight();
+ g.setColor(getBackground());
+ g.fillRect(0,0,width,height);
+ if (isClosestColor) {
+ g.setColor(closestMarker1);
+ g.fillRect(0,0,width,2);
+ g.fillRect(0,height - 2,width,2);
+ g.setColor(closestMarker2);
+ g.fillRect(0,2,width,1);
+ g.fillRect(0,height - 3,width,1);
+ g.setColor(closestMarker3);
+ g.fillRect(0,3,width,1);
+ g.fillRect(0,height - 4,width,1);
+ }
+ super.paintComponent(g);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/PaletteListModel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/PaletteListModel.java
new file mode 100644
index 0000000..d3474c3
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/PaletteListModel.java
@@ -0,0 +1,134 @@
+/*
+ * @(#)PaletteListModel.java 1.0 19 septembre 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import javax.swing.*;
+import java.util.*;
+import java.io.*;
+/**
+ * PaletteListModel manages a list of PaletteEntry.
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 19 septembre 2005 Created.
+ */
+public class PaletteListModel extends AbstractListModel {
+ /**
+ * Name of the palette.
+ */
+ private String name;
+
+ /**
+ * Informatation about the palette, such as the copyright.
+ */
+ private String info;
+
+ private PaletteEntry[] entries;
+
+ /**
+ * Index of the color which is closest to the current color in
+ * the color chooser.
+ */
+ private int closestIndex;
+
+ /**
+ * Creates a new instance.
+ */
+ public PaletteListModel(String name, String info, PaletteEntry[] entries) {
+ this.name = name;
+ this.info = info;
+ this.entries = entries;
+ }
+
+ public void setName(String newValue) {
+ name = newValue;
+ }
+ public String getName() {
+ return name;
+ }
+
+ public void setInfo(String newValue) {
+ info = newValue;
+ }
+ public String getInfo() {
+ return info;
+ }
+
+ @Override
+ public Object getElementAt(int index) {
+ return entries[index];
+ }
+
+ @Override
+ public int getSize() {
+ return entries.length;
+ }
+
+ /**
+ * Used for displaying the name of the palette in the combo box
+ * of the ColorPalettesChooser.
+ */
+ public String toString() {
+ return getName();
+ }
+
+ /**
+ * Computes the index of the color which comes closest to the specified
+ * color.
+ * This may return -1, if there is no sufficiently close color in the
+ * color list.
+ */
+ public int computeClosestIndex(Color referenceColor) {
+ int refRGB = referenceColor.getRGB();
+
+ int closest = -1;
+
+ // Setting this to a lower value than Integer.MAX_VALUE makes this
+ // method search for closer matches.
+ //int closestDistance = Integer.MAX_VALUE;
+ int closestDistance = 1024*3;
+
+ for (int i=0, n = getSize(); i < n; i++) {
+ PaletteEntry entry = (PaletteEntry) getElementAt(i);
+ int entryRGB = entry.getColor().getRGB();
+ int rDiff = ((entryRGB & 0xff0000) - (refRGB & 0xff0000)) >> 16;
+ int gDiff = ((entryRGB & 0xff00) - (refRGB & 0xff00)) >> 8;
+ int bDiff = (entryRGB & 0xff) - (refRGB & 0xff);
+ int distance = rDiff * rDiff + gDiff * gDiff + bDiff * bDiff;
+ if (distance < closestDistance) {
+ closest = i;
+ closestDistance = distance;
+ }
+ }
+ return closest;
+ }
+
+ /**
+ * Sets the index of the color which is closest to the current color in
+ * the color chooser.
+ *
+ * @param newValue closest index or -1, if no color is close.
+ */
+ public void setClosestIndex(int newValue) {
+ closestIndex = newValue;
+ }
+ /**
+ * Returns the index of the color which is closest to the current color in
+ * the color chooser, or -1 of no such color exists.
+ */
+ public int getClosestIndex() {
+ return closestIndex;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/Quaqua15ColorPicker.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/Quaqua15ColorPicker.form
new file mode 100644
index 0000000..bb917da
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/Quaqua15ColorPicker.form
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <AuxValues>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JButton" name="pickerButton">
+ <Properties>
+ <Property name="borderPainted" type="boolean" value="false"/>
+ <Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
+ <Insets value="[0, 0, 0, 0]"/>
+ </Property>
+ </Properties>
+ <Events>
+ <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pickBegin"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="Center"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ </SubComponents>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/Quaqua15ColorPicker.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/Quaqua15ColorPicker.java
new file mode 100755
index 0000000..14f489e
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/Quaqua15ColorPicker.java
@@ -0,0 +1,340 @@
+/*
+ * @(#)Quaqua15ColorPicker.java 1.2 2006-04-23
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import java.security.AccessControlException;
+
+import javax.swing.*;
+import javax.swing.colorchooser.AbstractColorChooserPanel;
+import javax.swing.plaf.IconUIResource;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
+
+/**
+ * Quaqua15ColorPicker.
+ *
+ *
+ *
+ * @author Werner Randelshofer
+ * @version 1.2 2006-04-23 Retrieve labels from UIManager. <br>
+ * 1.1.1 2006-03-15 Forgot to create robot instance. <br>
+ * 1.1 2006-03-06 Abort picker when the user presses the Escape-Key. <br>
+ * 1.0 December 18, 2005 Created.
+ */
+public class Quaqua15ColorPicker extends AbstractColorChooserPanel {
+ /**
+ * This frame is constantly moved to the current location of the mouse. This
+ * ensures that we can trap mouse clicks while the picker cursor is showing.
+ * Also, by tying the picker cursor to this frame, we ensure that the picker
+ * cursor (the magnifying glass) is shown.
+ */
+ private Dialog pickerFrame;
+
+ private Timer pickerTimer;
+
+ /**
+ * Holds the image of the picker cursor.
+ */
+ private BufferedImage cursorImage;
+ /**
+ * Graphics object used for drawing on the cursorImage.
+ */
+ private Graphics2D cursorGraphics;
+
+ /**
+ * The picker cursor.
+ */
+ private Cursor pickerCursor;
+
+ /**
+ * The hot spot of the cursor.
+ */
+ private Point hotSpot;
+
+ /**
+ * Offset from the hot spot of the pickerCursor to the pixel that we want to
+ * pick. We can't pick the color at the hotSpot of the cursor, because this
+ * point is obscured by the pickerFrame.
+ */
+ private Point pickOffset;
+
+ /**
+ * The magnifying glass image.
+ */
+ private BufferedImage magnifierImage;
+
+ /**
+ * The robot is used for creating screen captures.
+ */
+ private Robot robot;
+
+ private Color previousColor = Color.white;
+ private Point previousLoc = new Point();
+ private Point pickLoc = new Point();
+ private Point captureOffset = new Point();
+ private Rectangle captureRect;
+ private final static Color transparentColor = new Color(0, true);
+ private Rectangle zoomRect;
+ private Rectangle glassRect;
+
+ /**
+ * Creates a new instance.
+ */
+ public Quaqua15ColorPicker() {
+ // Try immediately to create a screen capture in order to fail quickly,
+ // when
+ // we can't provide a color picker functionality.
+ try {
+ robot = new Robot();
+ robot.createScreenCapture(new Rectangle(0, 0, 1, 1));
+ } catch (AWTException e) {
+ throw new AccessControlException("Unable to capture screen");
+ }
+
+ }
+
+ /**
+ * Gets the picker frame. If the frame does not yet exist, it is created
+ * along with all the other objects that are needed to make the picker work.
+ */
+ private Dialog getPickerFrame() {
+ if (pickerFrame == null) {
+ Window owner = SwingUtilities.getWindowAncestor(this);
+ if (owner instanceof Dialog) {
+ pickerFrame = new Dialog((Dialog) owner);
+ } else if (owner instanceof Frame) {
+ pickerFrame = new Dialog((Frame) owner);
+ } else {
+ pickerFrame = new Dialog(new JFrame());
+ }
+
+ pickerFrame.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent evt) {
+ pickFinish();
+ }
+
+ @Override
+ public void mouseExited(MouseEvent evt) {
+ updatePicker();
+ }
+ });
+
+ pickerFrame.addMouseMotionListener(new MouseMotionAdapter() {
+ @Override
+ public void mouseMoved(MouseEvent evt) {
+ updatePicker();
+ }
+ });
+ pickerFrame.setSize(3, 3);
+ pickerFrame.setUndecorated(true);
+ pickerFrame.setAlwaysOnTop(true);
+
+ pickerFrame.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ switch (e.getKeyCode()) {
+ case KeyEvent.VK_ESCAPE:
+ pickCancel();
+ break;
+ case KeyEvent.VK_ENTER:
+ pickFinish();
+ break;
+ }
+ }
+ });
+
+ magnifierImage = (BufferedImage) UIManager
+ .get("ColorChooser.colorPickerMagnifier");
+ glassRect = (Rectangle) UIManager
+ .get("ColorChooser.colorPickerGlassRect");
+ zoomRect = (Rectangle) UIManager
+ .get("ColorChooser.colorPickerZoomRect");
+ hotSpot = (Point) UIManager.get("ColorChooser.colorPickerHotSpot");// new
+ // Point(29,
+ // 29);
+ captureRect = new Rectangle((Rectangle) UIManager
+ .get("ColorChooser.colorPickerCaptureRect"));
+ pickOffset = (Point) UIManager
+ .get("ColorChooser.colorPickerPickOffset");
+ captureOffset = new Point(captureRect.x, captureRect.y);
+ cursorImage = getGraphicsConfiguration().createCompatibleImage(
+ magnifierImage.getWidth(), magnifierImage.getHeight(),
+ Transparency.TRANSLUCENT);
+ cursorGraphics = cursorImage.createGraphics();
+ cursorGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+
+ pickerTimer = new Timer(5, new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ updatePicker();
+ }
+ });
+ }
+ return pickerFrame;
+ }
+
+ /**
+ * Updates the color picker.
+ */
+ protected void updatePicker() {
+ if (pickerFrame != null && pickerFrame.isShowing()) {
+ PointerInfo info = MouseInfo.getPointerInfo();
+ if (info == null) return; //pointer not on a graphics device
+ Point mouseLoc = info.getLocation();
+ pickerFrame.setLocation(mouseLoc.x - pickerFrame.getWidth() / 2,
+ mouseLoc.y - pickerFrame.getHeight() / 2);
+
+ pickLoc.x = mouseLoc.x + pickOffset.x;
+ pickLoc.y = mouseLoc.y + pickOffset.y;
+
+ if (pickLoc.x >= 0 && pickLoc.y >= 0) {
+ Color c = robot.getPixelColor(pickLoc.x, pickLoc.y);
+ if (!c.equals(previousColor) || !mouseLoc.equals(previousLoc)) {
+ previousColor = c;
+ previousLoc = mouseLoc;
+
+ captureRect.setLocation(mouseLoc.x + captureOffset.x,
+ mouseLoc.y + captureOffset.y);
+ if (captureRect.x >= 0 && captureRect.y >= 0) {
+ BufferedImage capture = robot
+ .createScreenCapture(captureRect);
+
+ // Clear the cursor graphics
+ cursorGraphics.setComposite(AlphaComposite.Src);
+ cursorGraphics.setColor(transparentColor);
+ cursorGraphics.fillRect(0, 0, cursorImage.getWidth(),
+ cursorImage.getHeight());
+
+ // Fill the area for the zoomed screen capture with a
+ // non-transparent color (any non-transparent color does
+ // the job).
+ cursorGraphics.setColor(Color.red);
+ cursorGraphics.fillOval(glassRect.x, glassRect.y,
+ glassRect.width, glassRect.height);
+
+ // Paint the screen capture with a zoom factor of 5
+ cursorGraphics.setComposite(AlphaComposite.SrcIn);
+ cursorGraphics.drawImage(capture, zoomRect.x,
+ zoomRect.y, zoomRect.width, zoomRect.height,
+ this);
+
+ // Draw the magnifying glass image
+ cursorGraphics.setComposite(AlphaComposite.SrcOver);
+ cursorGraphics.drawImage(magnifierImage, 0, 0, this);
+
+ // We need to create a new subImage. This forces that
+ // the color picker uses the new imagery.
+ BufferedImage subImage = cursorImage
+ .getSubimage(0, 0, cursorImage.getWidth(),
+ cursorImage.getHeight());
+ pickerFrame.setCursor(getToolkit().createCustomCursor(
+ cursorImage, hotSpot, "ColorPicker"));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ private void initComponents() {// GEN-BEGIN:initComponents
+ pickerButton = new javax.swing.JButton();
+
+ setLayout(new java.awt.BorderLayout());
+
+ // pickerButton.setBorderPainted(false);
+ pickerButton.setMargin(new java.awt.Insets(0, 0, 0, 0));
+ pickerButton.addActionListener(new java.awt.event.ActionListener() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ pickBegin(evt);
+ }
+ });
+
+ add(pickerButton, java.awt.BorderLayout.CENTER);
+
+ }// GEN-END:initComponents
+
+ private void pickBegin(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_pickBegin
+ getPickerFrame();
+ pickerTimer.start();
+ getPickerFrame().setVisible(true);
+ }// GEN-LAST:event_pickBegin
+
+ protected void pickFinish() {
+ pickerTimer.stop();
+ pickerFrame.setVisible(false);
+ PointerInfo info = MouseInfo.getPointerInfo();
+ if (info == null) return; //pointer not on a graphics device
+ Point loc = info.getLocation();
+ Color c = robot.getPixelColor(loc.x + pickOffset.x, loc.y
+ + pickOffset.y);
+ getColorSelectionModel().setSelectedColor(c);
+ }
+
+ protected void pickCancel() {
+ pickerTimer.stop();
+ pickerFrame.setVisible(false);
+ }
+
+ @Override
+ protected void buildChooser() {
+ initComponents();
+ pickerButton.setIcon(new TransitionAwareIcon(pickerButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ return new IconUIResource(SubstanceImageCreator
+ .getSearchIcon(15, scheme, pickerButton
+ .getComponentOrientation()
+ .isLeftToRight()));
+ }
+ }, "ColorChooser.colorPickerIcon"));
+ }
+
+ @Override
+ public String getDisplayName() {
+ return UIManager.getString("ColorChooser.colorPicker");
+ }
+
+ @Override
+ public Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.colorPickerIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ @Override
+ public void updateChooser() {
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton pickerButton;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/QuaquaColorPreviewPanel.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/QuaquaColorPreviewPanel.form
new file mode 100644
index 0000000..6a9d449
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/QuaquaColorPreviewPanel.form
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <Properties>
+ <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
+ <Dimension value="[26, 26]"/>
+ </Property>
+ </Properties>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/QuaquaColorPreviewPanel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/QuaquaColorPreviewPanel.java
new file mode 100644
index 0000000..0c3f733
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/QuaquaColorPreviewPanel.java
@@ -0,0 +1,80 @@
+/*
+ * @(#)QuaquaColorPreviewPanel.java 1.2 2005-12-18
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.*;
+
+/**
+ * QuaquaColorPreviewPanel.
+ *
+ * @author Werner Randelshofer
+ * @version 1.2 2005-12-18 Tweaked insets.
+ * <br>1.1 2005-09-20 Added tooltip. This is not what the native
+ * NSColorPicker does, but it is very useful.
+ * <br>1.0 30 March 2005 Created.
+ */
+public class QuaquaColorPreviewPanel extends JPanel implements UIResource {
+ private final static Color previewBorderColor = new Color(0x949494);
+ private final static Color previewBackgroundColor = new Color(0xffffff);
+ /** Creates new form. */
+ public QuaquaColorPreviewPanel() {
+ initComponents();
+ setBorder(new VisualMargin(3,0,3,0));
+ setToolTipText("on"); // set dummy text, to switch tooltip on
+ }
+
+ @Override
+ public void paintComponent(Graphics g) {
+ Insets insets = getInsets();
+ int x = insets.left;
+ int y = insets.top;
+ int w = getWidth() - insets.left - insets.right;
+ int h = getHeight() - insets.top - insets.bottom;
+ g.setColor(previewBackgroundColor);
+ g.fillRect(x+1,y+1,w-2,h-2);
+ g.setColor(previewBorderColor);
+ g.drawRect(x,y,w-1,h-1);
+ g.setColor(getForeground());
+ g.fillRect(x+2,y+2,w-4,h-4);
+ }
+
+ @Override
+ public String getToolTipText(MouseEvent evt) {
+ Color color = getForeground();
+ return (color == null) ? null : color.getRed()+", "+ color.getGreen() + ", " + color.getBlue();
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ private void initComponents() {//GEN-BEGIN:initComponents
+
+ setLayout(new java.awt.BorderLayout());
+
+ setPreferredSize(new java.awt.Dimension(26, 26));
+ }//GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/RGBChooser.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/RGBChooser.form
new file mode 100644
index 0000000..fa230d3
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/RGBChooser.form
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <AuxValues>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-121"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
+ <SubComponents>
+ <Component class="javax.swing.JLabel" name="redLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.rgbRedText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="redSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="255"/>
+ <Property name="maximum" type="int" value="255"/>
+ <Property name="minorTickSpacing" type="int" value="128"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JTextField" name="redField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="redFieldFocusLost"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="-1" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JLabel" name="greenLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.rgbGreenText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="greenSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="255"/>
+ <Property name="maximum" type="int" value="255"/>
+ <Property name="minorTickSpacing" type="int" value="128"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JTextField" name="greenField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="greenFieldFocusLost"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="-1" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JLabel" name="blueLabel">
+ <Properties>
+ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+ <ResourceString bundle="ch/randelshofer/quaqua/Labels.properties" key="ColorChooser.rgbBlueText" replaceFormat="UIManager.getString("{key}")"/>
+ </Property>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="16" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JSlider" name="blueSlider">
+ <Properties>
+ <Property name="majorTickSpacing" type="int" value="255"/>
+ <Property name="maximum" type="int" value="255"/>
+ <Property name="minorTickSpacing" type="int" value="128"/>
+ <Property name="paintTicks" type="boolean" value="true"/>
+ </Properties>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="-1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="1.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Component class="javax.swing.JTextField" name="blueField">
+ <Properties>
+ <Property name="columns" type="int" value="3"/>
+ <Property name="horizontalAlignment" type="int" value="11"/>
+ <Property name="text" type="java.lang.String" value="0"/>
+ </Properties>
+ <Events>
+ <EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="blueFieldFocusLost"/>
+ </Events>
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="1" gridY="-1" gridWidth="1" gridHeight="2" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="15" weightX="0.0" weightY="0.0"/>
+ </Constraint>
+ </Constraints>
+ </Component>
+ <Container class="javax.swing.JPanel" name="springPanel">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
+ <GridBagConstraints gridX="0" gridY="100" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="1.0"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/RGBChooser.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/RGBChooser.java
new file mode 100644
index 0000000..2b933d4
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/RGBChooser.java
@@ -0,0 +1,287 @@
+/*
+ * @(#)RGBChooser.java 1.3 2006-04-23
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+import javax.swing.border.*;
+import javax.swing.colorchooser.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.*;
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+/**
+ * RGBChooser.
+ *
+ * @author Werner Randelshofer
+ * @version 1.3 2006-04-23 Retrieve labels directly from UIManager.
+ * <br>1.2 2005-11-22 Moved handler for text fields into separate class.
+ * <br>1.1.1 2005-11-07 Get "Labels" resource bundle from UIManager.
+ * <br>1.1 2005-09-05 Get font, spacing and icon from UIManager.
+ * <br>1.0 29 March 2005 Created.
+ */
+public class RGBChooser extends AbstractColorChooserPanel implements UIResource {
+ private ColorSliderModel ccModel = new RGBColorSliderModel();
+
+ /** Creates new form. */
+ public RGBChooser() {
+ initComponents();
+
+ //
+ Font font = UIManager.getFont("ColorChooser.font");
+ redLabel.setFont(font);
+ redSlider.setFont(font);
+ redField.setFont(font);
+ greenLabel.setFont(font);
+ greenSlider.setFont(font);
+ greenField.setFont(font);
+ blueLabel.setFont(font);
+ blueSlider.setFont(font);
+ blueField.setFont(font);
+ //
+ int textSliderGap = UIManager.getInt("ColorChooser.textSliderGap");
+ if (textSliderGap != 0) {
+ Insets fieldInsets = new Insets(0,textSliderGap,0,0);
+ GridBagLayout layout = (GridBagLayout) getLayout();
+ GridBagConstraints gbc;
+ gbc = layout.getConstraints(redField);
+ gbc.insets = fieldInsets;
+ layout.setConstraints(redField, gbc);
+ gbc = layout.getConstraints(greenField);
+ gbc.insets = fieldInsets;
+ layout.setConstraints(greenField, gbc);
+ gbc = layout.getConstraints(blueField);
+ gbc.insets = fieldInsets;
+ layout.setConstraints(blueField, gbc);
+ }
+
+ ccModel.configureColorSlider(0, redSlider);
+ ccModel.configureColorSlider(1, greenSlider);
+ ccModel.configureColorSlider(2, blueSlider);
+
+ redField.setText(Integer.toString(redSlider.getValue()));
+ greenField.setText(Integer.toString(greenSlider.getValue()));
+ blueField.setText(Integer.toString(blueSlider.getValue()));
+
+ new ColorSliderTextFieldHandler(redField, ccModel, 0);
+ new ColorSliderTextFieldHandler(greenField, ccModel, 1);
+ new ColorSliderTextFieldHandler(blueField, ccModel, 2);
+
+ ccModel.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent evt) {
+ setColorToModel(ccModel.getColor());
+ }
+ });
+ redField.setMinimumSize(redField.getPreferredSize());
+ greenField.setMinimumSize(greenField.getPreferredSize());
+ blueField.setMinimumSize(blueField.getPreferredSize());
+ VisualMargin bm = new VisualMargin(false,false,true,false);
+ redLabel.setBorder(bm);
+ greenLabel.setBorder(bm);
+ blueLabel.setBorder(bm);
+ }
+ @Override
+ protected void buildChooser() {
+ }
+
+ @Override
+ public String getDisplayName() {
+ return UIManager.getString("ColorChooser.rgbSliders");
+ }
+
+ @Override
+ public Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.colorSlidersIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ @Override
+ public void updateChooser() {
+ ccModel.setColor(getColorFromModel());
+ }
+ public void setColorToModel(Color color) {
+ getColorSelectionModel().setSelectedColor(color);
+ }
+
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
+ private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+
+ redLabel = new javax.swing.JLabel();
+ redSlider = new javax.swing.JSlider();
+ redField = new javax.swing.JTextField();
+ greenLabel = new javax.swing.JLabel();
+ greenSlider = new javax.swing.JSlider();
+ greenField = new javax.swing.JTextField();
+ blueLabel = new javax.swing.JLabel();
+ blueSlider = new javax.swing.JSlider();
+ blueField = new javax.swing.JTextField();
+ springPanel = new javax.swing.JPanel();
+
+ setLayout(new java.awt.GridBagLayout());
+
+ redLabel.setText(UIManager.getString("ColorChooser.rgbRedText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(redLabel, gridBagConstraints);
+
+ redSlider.setMajorTickSpacing(255);
+ redSlider.setMaximum(255);
+ redSlider.setMinorTickSpacing(128);
+ redSlider.setPaintTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ gridBagConstraints.weightx = 1.0;
+ add(redSlider, gridBagConstraints);
+
+ redField.setColumns(3);
+ redField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ redField.setText("0");
+ redField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ redFieldFocusLost(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(redField, gridBagConstraints);
+
+ greenLabel.setText(UIManager.getString("ColorChooser.rgbGreenText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(greenLabel, gridBagConstraints);
+
+ greenSlider.setMajorTickSpacing(255);
+ greenSlider.setMaximum(255);
+ greenSlider.setMinorTickSpacing(128);
+ greenSlider.setPaintTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ gridBagConstraints.weightx = 1.0;
+ add(greenSlider, gridBagConstraints);
+
+ greenField.setColumns(3);
+ greenField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ greenField.setText("0");
+ greenField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ greenFieldFocusLost(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(greenField, gridBagConstraints);
+
+ blueLabel.setText(UIManager.getString("ColorChooser.rgbBlueText"));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(1, 0, 0, 0);
+ add(blueLabel, gridBagConstraints);
+
+ blueSlider.setMajorTickSpacing(255);
+ blueSlider.setMaximum(255);
+ blueSlider.setMinorTickSpacing(128);
+ blueSlider.setPaintTicks(true);
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ gridBagConstraints.weightx = 1.0;
+ add(blueSlider, gridBagConstraints);
+
+ blueField.setColumns(3);
+ blueField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ blueField.setText("0");
+ blueField.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override
+ public void focusLost(java.awt.event.FocusEvent evt) {
+ blueFieldFocusLost(evt);
+ }
+ });
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridheight = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+ add(blueField, gridBagConstraints);
+
+ springPanel.setLayout(new java.awt.BorderLayout());
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 100;
+ gridBagConstraints.weighty = 1.0;
+ add(springPanel, gridBagConstraints);
+
+ }// </editor-fold>//GEN-END:initComponents
+
+ private void blueFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_blueFieldFocusLost
+ blueField.setText(Integer.toString(ccModel.getValue(2)));
+ }//GEN-LAST:event_blueFieldFocusLost
+
+ private void greenFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_greenFieldFocusLost
+ greenField.setText(Integer.toString(ccModel.getValue(1)));
+ }//GEN-LAST:event_greenFieldFocusLost
+
+ private void redFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_redFieldFocusLost
+ redField.setText(Integer.toString(ccModel.getValue(0)));
+ }//GEN-LAST:event_redFieldFocusLost
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JTextField blueField;
+ private javax.swing.JLabel blueLabel;
+ private javax.swing.JSlider blueSlider;
+ private javax.swing.JTextField greenField;
+ private javax.swing.JLabel greenLabel;
+ private javax.swing.JSlider greenSlider;
+ private javax.swing.JTextField redField;
+ private javax.swing.JLabel redLabel;
+ private javax.swing.JSlider redSlider;
+ private javax.swing.JPanel springPanel;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/RGBColorSliderModel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/RGBColorSliderModel.java
new file mode 100644
index 0000000..9880780
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/RGBColorSliderModel.java
@@ -0,0 +1,57 @@
+/*
+ * @(#)RGBColorSliderModel.java 1.0 May 22, 2005
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import javax.swing.*;
+/**
+ * A ColorSliderModel for RGB color components (red, green, blue).
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 May 22, 2005 Created.
+ */
+public class RGBColorSliderModel extends ColorSliderModel {
+ /**
+ * Creates a new instance.
+ */
+ public RGBColorSliderModel() {
+ super(new DefaultBoundedRangeModel[] {
+ new DefaultBoundedRangeModel(255, 0, 0, 255),
+ new DefaultBoundedRangeModel(255, 0, 0, 255),
+ new DefaultBoundedRangeModel(255, 0, 0, 255)
+ });
+ }
+
+ @Override
+ public int getRGB() {
+ return getRGB(components[0].getValue(), components[1].getValue(), components[2].getValue());
+ }
+
+ protected int getRGB(int r, int g, int b) {
+ return 0xff000000 | r << 16 | g << 8 | b;
+ }
+
+ @Override
+ public void setRGB(int rgb) {
+ components[0].setValue((rgb & 0xff0000) >> 16);
+ components[1].setValue((rgb & 0x00ff00) >> 8);
+ components[2].setValue( rgb & 0x0000ff);
+ }
+
+ @Override
+ public int toRGB(int[] values) {
+ return 0xff000000 | values[0] << 16 | values[1] << 8 | values[2];
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SmallColorWellBorder.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SmallColorWellBorder.java
new file mode 100644
index 0000000..785db6f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SmallColorWellBorder.java
@@ -0,0 +1,51 @@
+/*
+ * @(#)QuaquaSmallColorWellBorder.java 1.0 2005-04-18
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.border.*;
+/**
+ * SmallColorWellBorder.
+ *
+ * @author werni
+ */
+public class SmallColorWellBorder implements Border {
+ private static Color inner = Color.white;
+ private static Color outer = new Color(0x949494);
+ /** Creates a new instance of QuaquaSquareButtonBorder */
+ public SmallColorWellBorder() {
+ }
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return new Insets(1, 1, 1, 1);
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return true;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics gr, int x, int y, int width, int height) {
+ gr.setColor(c.getBackground());
+ gr.fillRect(x + 2, y + 2, width - 4, height - 4);
+ gr.setColor(inner);
+ gr.drawRect(x + 1, y + 1, width - 3, height - 3);
+ gr.setColor(outer);
+ gr.drawRect(x, y, width - 1, height - 1);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SwatchPanel.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SwatchPanel.form
new file mode 100644
index 0000000..fafa671
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SwatchPanel.form
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SwatchPanel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SwatchPanel.java
new file mode 100644
index 0000000..35c46cf
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SwatchPanel.java
@@ -0,0 +1,176 @@
+/*
+ * @(#)SwatchPanel.java 1.0 30 March 2005
+ *
+ * Copyright (c) 2004 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+import javax.swing.border.*;
+import javax.swing.colorchooser.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.*;
+
+/**
+ * SwatchPanel.
+ *
+ * Code derived from javax.swing.colorchooser.DefaultSwatchChooserPanel.
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 30 March 2005 Created.
+ */
+public class SwatchPanel extends javax.swing.JPanel {
+ protected Color[] colors;
+ protected Dimension swatchSize = new Dimension();
+ protected Dimension defaultSwatchSize;
+ protected Dimension numSwatches;
+ protected Dimension gap;
+ private final static Color gridColor = new Color(0xaaaaaa);
+
+ /** Creates new form. */
+ public SwatchPanel() {
+ initComponents();
+
+ initValues();
+ initColors();
+ setToolTipText(""); // register for events
+ setOpaque(false);
+ //setBackground(Color.white);
+ setRequestFocusEnabled(false);
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ private void initComponents() {//GEN-BEGIN:initComponents
+
+ setLayout(new java.awt.BorderLayout());
+
+ }//GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ // End of variables declaration//GEN-END:variables
+
+
+
+ @Override
+ public boolean isFocusTraversable() {
+ return false;
+ }
+
+ protected void initValues() {
+ defaultSwatchSize = UIManager.getDimension("ColorChooser.swatchesSwatchSize");
+ swatchSize.width = defaultSwatchSize.width;
+ swatchSize.height = defaultSwatchSize.height;
+ gap = new Dimension(1, 1);
+ }
+
+ @Override
+ public void setBounds(int x, int y, int width, int height) {
+ super.setBounds(x, y, width, height);
+ if (width > getPreferredSize().width) {
+ swatchSize.width = (width - numSwatches.width * gap.width) / numSwatches.width;
+ } else {
+ swatchSize.width = defaultSwatchSize.width;
+ }
+ if (height > getPreferredSize().height) {
+ swatchSize.height = (height - numSwatches.height * gap.height) / numSwatches.height;
+ } else {
+ swatchSize.height = defaultSwatchSize.height;
+ }
+ }
+
+ public void setColors(Color[] colors) {
+ this.colors = colors;
+ }
+ public void setNumSwatches(int rows, int columns) {
+ numSwatches = new Dimension(rows, columns);
+ }
+
+ @Override
+ public void paintComponent(Graphics g) {
+ Dimension preferredSize = getSwatchesSize();
+ int xoffset = (getWidth() - preferredSize.width) / 2;
+ int yoffset = 0;// (getHeight() - preferredSize.height) / 2;
+
+ for (int row = 0; row < numSwatches.height; row++) {
+ for (int column = 0; column < numSwatches.width; column++) {
+ Color cellColor = getColorForCell(column, row);
+ g.setColor(cellColor);
+ //int x = (numSwatches.width - column - 1) * (swatchSize.width + gap.width);
+ int x = xoffset + column * (swatchSize.width + gap.width) + 1;
+ int y = yoffset + row * (swatchSize.height + gap.height) + 1;
+ g.fillRect( x, y, swatchSize.width, swatchSize.height);
+
+ g.setColor(cellColor.darker());
+ g.fillRect(x - 1, y - 1, swatchSize.width+1, 1);
+ g.fillRect(x - 1, y, 1, swatchSize.height);
+ }
+ }
+ }
+
+ public Dimension getSwatchesSize() {
+ int x = numSwatches.width * (swatchSize.width + gap.width);
+ int y = numSwatches.height * (swatchSize.height + gap.height);
+ return new Dimension( x, y );
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ int x = numSwatches.width * (defaultSwatchSize.width + gap.width);
+ int y = numSwatches.height * (defaultSwatchSize.height + gap.height);
+ return new Dimension( x, y );
+ }
+
+ protected void initColors() {
+
+
+ }
+
+ @Override
+ public String getToolTipText(MouseEvent e) {
+ Color color = getColorForLocation(e.getX(), e.getY());
+ return (color == null) ? null : color.getRed()+", "+ color.getGreen() + ", " + color.getBlue();
+ }
+
+ public Color getColorForLocation( int x, int y ) {
+ Dimension preferredSize = getSwatchesSize();
+ x -= (getWidth() - preferredSize.width) / 2;
+ //y -= (getHeight() - preferredSize.height) / 2;
+ int column;
+ if ((!this.getComponentOrientation().isLeftToRight())) {
+ column = numSwatches.width - x / (swatchSize.width + gap.width) - 1;
+ } else {
+ column = x / (swatchSize.width + gap.width);
+ }
+ int row = y / (swatchSize.height + gap.height);
+ return getColorForCell(column, row);
+ }
+
+ private Color getColorForCell( int column, int row) {
+ int index = (row * numSwatches.width) + column;
+ return (index < colors.length) ? colors[index] : null;
+ }
+
+
+
+
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SwatchesChooser.form b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SwatchesChooser.form
new file mode 100644
index 0000000..a945eff
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SwatchesChooser.form
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+ <AuxValues>
+ <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
+ <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
+ <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
+ <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
+ </AuxValues>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
+ <SubComponents>
+ <Container class="javax.swing.JScrollPane" name="scrollPane">
+ <Constraints>
+ <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
+ <BorderConstraints direction="Center"/>
+ </Constraint>
+ </Constraints>
+
+ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
+ </Container>
+ </SubComponents>
+</Form>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SwatchesChooser.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SwatchesChooser.java
new file mode 100644
index 0000000..f9ac4d8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/SwatchesChooser.java
@@ -0,0 +1,403 @@
+/*
+ * @(#)SwatchesChooser.java 1.1 2006-04-23
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+import javax.swing.border.*;
+import javax.swing.colorchooser.*;
+import javax.swing.plaf.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.*;
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util.*;
+
+/**
+ * SwatchesChooser.
+ *
+ * @author Werner Randelshofer
+ * @version 1.3 2006-04-23 Retrieve labels directly from UIManager.
+ * <br>1.0.2 2005-11-07 Get "Labels" ResourceBundle fro UIManager.
+ * <br>1.0.1 2005-09-11 Get icon from UIManager.
+ * <br>1.0 30 March 2005 Created.
+ */
+public class SwatchesChooser
+extends AbstractColorChooserPanel
+implements UIResource {
+ private SwatchPanel swatchPanel;
+
+
+ /** Creates new form. */
+ public SwatchesChooser() {
+ initComponents();
+ swatchPanel = new SwatchPanel();
+ initColors();
+ scrollPane.setViewportView(swatchPanel);
+ }
+
+ protected void initColors() {
+ int[] rawValues = initRawValues();
+ int numColors = rawValues.length / 3;
+
+ Color[] colors = new Color[numColors];
+ for (int i = 0; i < numColors ; i++) {
+ int x = i % 31;
+ int y = i / 31;
+ colors[x * 9 + y % 9] = new Color( rawValues[(i*3)], rawValues[(i*3)+1], rawValues[(i*3)+2] );
+ }
+ swatchPanel.setColors(colors);
+ swatchPanel.setNumSwatches(9, 31);
+ swatchPanel.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ Color c = swatchPanel.getColorForLocation(e.getX(), e.getY());
+ if (c != null) {
+ setColorToModel(c);
+ }
+ }
+ });
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ private void initComponents() {//GEN-BEGIN:initComponents
+ scrollPane = new javax.swing.JScrollPane();
+
+ setLayout(new java.awt.BorderLayout());
+
+ add(scrollPane, java.awt.BorderLayout.CENTER);
+
+ }//GEN-END:initComponents
+
+ @Override
+ protected void buildChooser() {
+ }
+
+ @Override
+ public String getDisplayName() {
+ return UIManager.getString("ColorChooser.colorSwatches");
+ }
+
+ @Override
+ public Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.colorSwatchesIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ public void setColorToModel(Color color) {
+ getColorSelectionModel().setSelectedColor(color);
+ }
+
+ @Override
+ public void updateChooser() {
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JScrollPane scrollPane;
+ // End of variables declaration//GEN-END:variables
+
+ private int[] initRawValues() {
+ int[] rawValues = {
+ 255, 255, 255, // first row.
+ 204, 255, 255,
+ 204, 204, 255,
+ 204, 204, 255,
+ 204, 204, 255,
+ 204, 204, 255,
+ 204, 204, 255,
+ 204, 204, 255,
+ 204, 204, 255,
+ 204, 204, 255,
+ 204, 204, 255,
+ 255, 204, 255,
+ 255, 204, 204,
+ 255, 204, 204,
+ 255, 204, 204,
+ 255, 204, 204,
+ 255, 204, 204,
+ 255, 204, 204,
+ 255, 204, 204,
+ 255, 204, 204,
+ 255, 204, 204,
+ 255, 255, 204,
+ 204, 255, 204,
+ 204, 255, 204,
+ 204, 255, 204,
+ 204, 255, 204,
+ 204, 255, 204,
+ 204, 255, 204,
+ 204, 255, 204,
+ 204, 255, 204,
+ 204, 255, 204,
+ 204, 204, 204, // second row.
+ 153, 255, 255,
+ 153, 204, 255,
+ 153, 153, 255,
+ 153, 153, 255,
+ 153, 153, 255,
+ 153, 153, 255,
+ 153, 153, 255,
+ 153, 153, 255,
+ 153, 153, 255,
+ 204, 153, 255,
+ 255, 153, 255,
+ 255, 153, 204,
+ 255, 153, 153,
+ 255, 153, 153,
+ 255, 153, 153,
+ 255, 153, 153,
+ 255, 153, 153,
+ 255, 153, 153,
+ 255, 153, 153,
+ 255, 204, 153,
+ 255, 255, 153,
+ 204, 255, 153,
+ 153, 255, 153,
+ 153, 255, 153,
+ 153, 255, 153,
+ 153, 255, 153,
+ 153, 255, 153,
+ 153, 255, 153,
+ 153, 255, 153,
+ 153, 255, 204,
+ 204, 204, 204, // third row
+ 102, 255, 255,
+ 102, 204, 255,
+ 102, 153, 255,
+ 102, 102, 255,
+ 102, 102, 255,
+ 102, 102, 255,
+ 102, 102, 255,
+ 102, 102, 255,
+ 153, 102, 255,
+ 204, 102, 255,
+ 255, 102, 255,
+ 255, 102, 204,
+ 255, 102, 153,
+ 255, 102, 102,
+ 255, 102, 102,
+ 255, 102, 102,
+ 255, 102, 102,
+ 255, 102, 102,
+ 255, 153, 102,
+ 255, 204, 102,
+ 255, 255, 102,
+ 204, 255, 102,
+ 153, 255, 102,
+ 102, 255, 102,
+ 102, 255, 102,
+ 102, 255, 102,
+ 102, 255, 102,
+ 102, 255, 102,
+ 102, 255, 153,
+ 102, 255, 204,
+ 153, 153, 153, // fourth row
+ 51, 255, 255,
+ 51, 204, 255,
+ 51, 153, 255,
+ 51, 102, 255,
+ 51, 51, 255,
+ 51, 51, 255,
+ 51, 51, 255,
+ 102, 51, 255,
+ 153, 51, 255,
+ 204, 51, 255,
+ 255, 51, 255,
+ 255, 51, 204,
+ 255, 51, 153,
+ 255, 51, 102,
+ 255, 51, 51,
+ 255, 51, 51,
+ 255, 51, 51,
+ 255, 102, 51,
+ 255, 153, 51,
+ 255, 204, 51,
+ 255, 255, 51,
+ 204, 255, 51,
+ 153, 244, 51,
+ 102, 255, 51,
+ 51, 255, 51,
+ 51, 255, 51,
+ 51, 255, 51,
+ 51, 255, 102,
+ 51, 255, 153,
+ 51, 255, 204,
+ 153, 153, 153, // Fifth row
+ 0, 255, 255,
+ 0, 204, 255,
+ 0, 153, 255,
+ 0, 102, 255,
+ 0, 51, 255,
+ 0, 0, 255,
+ 51, 0, 255,
+ 102, 0, 255,
+ 153, 0, 255,
+ 204, 0, 255,
+ 255, 0, 255,
+ 255, 0, 204,
+ 255, 0, 153,
+ 255, 0, 102,
+ 255, 0, 51,
+ 255, 0 , 0,
+ 255, 51, 0,
+ 255, 102, 0,
+ 255, 153, 0,
+ 255, 204, 0,
+ 255, 255, 0,
+ 204, 255, 0,
+ 153, 255, 0,
+ 102, 255, 0,
+ 51, 255, 0,
+ 0, 255, 0,
+ 0, 255, 51,
+ 0, 255, 102,
+ 0, 255, 153,
+ 0, 255, 204,
+ 102, 102, 102, // sixth row
+ 0, 204, 204,
+ 0, 204, 204,
+ 0, 153, 204,
+ 0, 102, 204,
+ 0, 51, 204,
+ 0, 0, 204,
+ 51, 0, 204,
+ 102, 0, 204,
+ 153, 0, 204,
+ 204, 0, 204,
+ 204, 0, 204,
+ 204, 0, 204,
+ 204, 0, 153,
+ 204, 0, 102,
+ 204, 0, 51,
+ 204, 0, 0,
+ 204, 51, 0,
+ 204, 102, 0,
+ 204, 153, 0,
+ 204, 204, 0,
+ 204, 204, 0,
+ 204, 204, 0,
+ 153, 204, 0,
+ 102, 204, 0,
+ 51, 204, 0,
+ 0, 204, 0,
+ 0, 204, 51,
+ 0, 204, 102,
+ 0, 204, 153,
+ 0, 204, 204,
+ 102, 102, 102, // seventh row
+ 0, 153, 153,
+ 0, 153, 153,
+ 0, 153, 153,
+ 0, 102, 153,
+ 0, 51, 153,
+ 0, 0, 153,
+ 51, 0, 153,
+ 102, 0, 153,
+ 153, 0, 153,
+ 153, 0, 153,
+ 153, 0, 153,
+ 153, 0, 153,
+ 153, 0, 153,
+ 153, 0, 102,
+ 153, 0, 51,
+ 153, 0, 0,
+ 153, 51, 0,
+ 153, 102, 0,
+ 153, 153, 0,
+ 153, 153, 0,
+ 153, 153, 0,
+ 153, 153, 0,
+ 153, 153, 0,
+ 102, 153, 0,
+ 51, 153, 0,
+ 0, 153, 0,
+ 0, 153, 51,
+ 0, 153, 102,
+ 0, 153, 153,
+ 0, 153, 153,
+ 51, 51, 51, // eigth row
+ 0, 102, 102,
+ 0, 102, 102,
+ 0, 102, 102,
+ 0, 102, 102,
+ 0, 51, 102,
+ 0, 0, 102,
+ 51, 0, 102,
+ 102, 0, 102,
+ 102, 0, 102,
+ 102, 0, 102,
+ 102, 0, 102,
+ 102, 0, 102,
+ 102, 0, 102,
+ 102, 0, 102,
+ 102, 0, 51,
+ 102, 0, 0,
+ 102, 51, 0,
+ 102, 102, 0,
+ 102, 102, 0,
+ 102, 102, 0,
+ 102, 102, 0,
+ 102, 102, 0,
+ 102, 102, 0,
+ 102, 102, 0,
+ 51, 102, 0,
+ 0, 102, 0,
+ 0, 102, 51,
+ 0, 102, 102,
+ 0, 102, 102,
+ 0, 102, 102,
+ 0, 0, 0, // ninth row
+ 0, 51, 51,
+ 0, 51, 51,
+ 0, 51, 51,
+ 0, 51, 51,
+ 0, 51, 51,
+ 0, 0, 51,
+ 51, 0, 51,
+ 51, 0, 51,
+ 51, 0, 51,
+ 51, 0, 51,
+ 51, 0, 51,
+ 51, 0, 51,
+ 51, 0, 51,
+ 51, 0, 51,
+ 51, 0, 51,
+ 51, 0, 0,
+ 51, 51, 0,
+ 51, 51, 0,
+ 51, 51, 0,
+ 51, 51, 0,
+ 51, 51, 0,
+ 51, 51, 0,
+ 51, 51, 0,
+ 51, 51, 0,
+ 0, 51, 0,
+ 0, 51, 51,
+ 0, 51, 51,
+ 0, 51, 51,
+ 0, 51, 51,
+ 51, 51, 51 };
+ return rawValues;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/package.html b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/package.html
new file mode 100644
index 0000000..6f13f78
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/colorchooser/package.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+ @(#)package.html 1.0 2005-05-24
+
+ Copyright (c) 2003 Werner Randelshofer
+ Staldenmattweg 2, Immensee, CH-6405, Switzerland
+ All rights reserved.
+
+ This software is the confidential and proprietary information of
+ Werner Randelshofer. ("Confidential Information"). You shall not
+ disclose such Confidential Information and shall use it only in
+ accordance with the terms of the license agreement you entered into
+ with Werner Randelshofer.
+
+ CopyrightVersion 1.0
+
+-->
+</head>
+<body>
+Contains classes used by the <code>QuaquaColorChooserUI</code>.
+</body>
+</html>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/Images.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/Images.java
new file mode 100644
index 0000000..59539c1
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/Images.java
@@ -0,0 +1,289 @@
+/*
+ * @(#)Images.java 2.0 2006-12-24
+ *
+ * Copyright (c) 2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import javax.swing.*;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.*;
+
+
+/**
+ * Image processing methods.
+ *
+ * @author Werner Randelshofer, Karl von Randow
+ * @version 2.0 2006-12-24 by Karl von Randow: On the fly conversion from Aqua
+ * Blue to Aqua Graphite appearance added.
+ * <br>1.0.2 2005-09-12 Brought my work-around for Java 1.4.1 back to
+ * live.
+ * <br>1.0.1 2005-05-21 Accidentaly used bitmask transparency
+ * instead of translucent transparency.
+ * <br>1.0 13 March 2005 Created.
+ */
+public class Images {
+
+ /** Prevent instance creation. */
+ private Images() {
+ }
+
+ private static GraphiteFilter graphiteFilter = new GraphiteFilter();
+
+
+ public static Image createImage(URL resource) {
+ Image image = Toolkit.getDefaultToolkit().createImage(resource);
+// if (Preferences.getString("AppleAquaColorVariant").equals("6")) {
+// if (canGraphite(resource)) {
+// image = toGraphite(image);
+// }
+// }
+ return image;
+ }
+
+ private static Properties canGraphite;
+
+ private static boolean canGraphite(URL resource) {
+ if (canGraphite == null) {
+ synchronized (Images.class) {
+ if (canGraphite == null) {
+ Properties p = new Properties();
+ try {
+ p.load(Images.class.getResourceAsStream("graphiteable.properties"));
+ } catch (IOException e) {
+ System.err.println("Failed to load graphiteable.properties: " + e);
+ }
+ canGraphite = p;
+ }
+ }
+ }
+ String file = resource.getFile();
+ int i = file.lastIndexOf(File.separatorChar);
+ if (i != -1) {
+ file = file.substring(i + 1);
+ }
+ return canGraphite.containsKey(file);
+ }
+
+ /**
+ * This method returns a buffered image with the contents of an image.
+ *
+ * Code derived from the Java Developers Almanac 1.4
+ * http://javaalmanac.com/egs/java.awt.image/Image2Buf.html?l=rel
+ */
+ private static Image toGraphite(Image image) {
+ return Toolkit.getDefaultToolkit().
+ createImage(new FilteredImageSource(image.getSource(), graphiteFilter));
+ }
+
+ /**
+ * Based on a code example from:
+ * http://tams-www.informatik.uni-hamburg.de/applets/hades/webdemos/00-intro/02-imageprocessing/saturation.html
+ * @author karlvr
+ *
+ */
+ public static class GraphiteFilter extends RGBImageFilter {
+ private final static float saturationAdjust = 0.179f;
+ private static float hueAdjust = 0.0052f;
+ private static float brightnessAdjust = 0.09f;
+
+
+ private float[] hsb = new float[3];
+
+ @Override
+ public int filterRGB(int x, int y, int rgb) {
+ int alpha = rgb & 0xff000000;
+ int red = (rgb >> 16) & 0xff;
+ int green = (rgb >> 8) & 0xff;
+ int blue = rgb & 0xff;
+ /*
+ float RW = (1f - saturationAdjust) * 0.3086f; // or 0.299 for YIV values
+ float RG = (1f - saturationAdjust) * 0.6084f; // or 0.587 for YIV values
+ float RB = (1f - saturationAdjust) * 0.0820f; // or 0.114 for YIV values
+ */
+ float RW = (1f - saturationAdjust) * 0.333f; // or 0.299 for YIV values
+ float RG = (1f - saturationAdjust) * 0.333f; // or 0.587 for YIV values
+ float RB = (1f - saturationAdjust) * 0.333f; // or 0.114 for YIV values
+
+ float a = RW + saturationAdjust;
+ float b = RW;
+ float c = RW;
+ float d = RG;
+ float e = RG + saturationAdjust;
+ float f = RG;
+ float g = RB;
+ float h = RB;
+ float i = RB + saturationAdjust;
+
+ int outputRed = (int) (a*red + d*green + g*blue);
+ int outputGreen = (int) (b*red + e*green + h*blue);
+ int outputBlue = (int) (c*red + f*green + i*blue);
+ return alpha | (outputRed << 16) | (outputGreen << 8) | (outputBlue);
+ }
+ }
+
+ public static BufferedImage toBufferedImage(Image image) {
+ if (image instanceof BufferedImage) {
+ return (BufferedImage)image;
+ }
+
+ // This code ensures that all the pixels in the image are loaded
+ image = new ImageIcon(image).getImage();
+
+ // Create a buffered image with a format that's compatible with the screen
+ BufferedImage bimage = null;
+
+ if (System.getProperty("java.version").startsWith("1.4.1_")) {
+ // Workaround for Java 1.4.1 on Mac OS X.
+ // For this JVM, we always create an ARGB image to prevent a class
+ // cast exception in
+ // sun.awt.image.BufImgSurfaceData.createData(BufImgSurfaceData.java:434)
+ // when we attempt to draw the buffered image.
+ bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
+ } else {
+ // Determine if the image has transparent pixels; for this method's
+ // implementation, see e661 Determining If an Image Has Transparent Pixels
+ boolean hasAlpha;
+ try {
+ hasAlpha = hasAlpha(image);
+ } catch (IllegalAccessError e) {
+ // If we can't determine this, we assume that we have an alpha,
+ // in order not to loose data.
+ hasAlpha = true;
+ }
+
+
+ GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+ try {
+ // Determine the type of transparency of the new buffered image
+ int transparency = Transparency.OPAQUE;
+ if (hasAlpha) {
+ transparency = Transparency.TRANSLUCENT;
+ }
+
+ // Create the buffered image
+ GraphicsDevice gs = ge.getDefaultScreenDevice();
+ GraphicsConfiguration gc = gs.getDefaultConfiguration();
+ bimage = gc.createCompatibleImage(
+ image.getWidth(null), image.getHeight(null), transparency);
+ } catch (Exception e) {
+ //} catch (HeadlessException e) {
+ // The system does not have a screen
+ }
+
+ if (bimage == null) {
+ // Create a buffered image using the default color model
+ int type = BufferedImage.TYPE_INT_RGB;
+ if (hasAlpha) {
+ type = BufferedImage.TYPE_INT_ARGB;
+ }
+ bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
+ }
+ }
+
+ // Copy image to buffered image
+ Graphics g = bimage.createGraphics();
+
+ // Paint the image onto the buffered image
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+
+ return bimage;
+
+ // My own implementation:
+ /*
+ if (image instanceof BufferedImage) {
+ return (BufferedImage) image;
+ } else {
+ BufferedImage bufImg;
+ Frame f = new Frame();
+ f.pack();
+ MediaTracker t = new MediaTracker(f);
+ t.addImage(image, 0);
+ try { t.waitForAll(); } catch (InterruptedException e) {}
+
+ // Workaround for Java 1.4.1 on Mac OS X.
+ if (System.getProperty("java.version").startsWith("1.4.1_")) {
+ bufImg = new BufferedImage(image.getWidth(f), image.getHeight(f), BufferedImage.TYPE_INT_ARGB);
+ } else {
+ bufImg = GraphicsEnvironment
+ .getLocalGraphicsEnvironment()
+ .getDefaultScreenDevice()
+ .getDefaultConfiguration()
+ .createCompatibleImage(image.getWidth(null), image.getHeight(null), Transparency.TRANSLUCENT);
+ }
+ Graphics2D imgGraphics = bufImg.createGraphics();
+ imgGraphics.drawImage(image, 0, 0, f);
+ imgGraphics.dispose();
+ f.dispose();
+ return bufImg;
+ }*/
+ }
+
+ /**
+ * This method returns true if the specified image has transparent pixels
+ *
+ * Code taken from the Java Developers Almanac 1.4
+ * http://javaalmanac.com/egs/java.awt.image/HasAlpha.html
+ */
+ public static boolean hasAlpha(Image image) {
+ // If buffered image, the color model is readily available
+ if (image instanceof BufferedImage) {
+ BufferedImage bimage = (BufferedImage)image;
+ return bimage.getColorModel().hasAlpha();
+ }
+
+ // Use a pixel grabber to retrieve the image's color model;
+ // grabbing a single pixel is usually sufficient
+ PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
+ try {
+ pg.grabPixels();
+ } catch (InterruptedException e) {
+ }
+
+ // Get the image's color model
+ ColorModel cm = pg.getColorModel();
+ return cm.hasAlpha();
+ }
+
+ /**
+ * Splits an image into count subimages.
+ */
+ public static BufferedImage[] split(Image image, int count, boolean isHorizontal) {
+ BufferedImage src = Images.toBufferedImage(image);
+ if (count == 1) {
+ return new BufferedImage[] { src };
+ }
+
+ BufferedImage[] parts = new BufferedImage[count];
+ for (int i=0; i < count; i++) {
+ if (isHorizontal) {
+ parts[i] = src.getSubimage(
+ src.getWidth() / count * i, 0,
+ src.getWidth() / count, src.getHeight()
+ );
+ } else {
+ parts[i] = src.getSubimage(
+ 0, src.getHeight() / count * i,
+ src.getWidth(), src.getHeight() / count
+ );
+ }
+ }
+ return parts;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/Methods.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/Methods.java
new file mode 100644
index 0000000..b82e30e
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/Methods.java
@@ -0,0 +1,515 @@
+/*
+ * @(#)Methods.java 1.3.1 2006-09-18
+ *
+ * Copyright (c) 2005-2006 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util;
+
+import java.lang.reflect.*;
+/**
+ * Methods contains convenience methods for method invocations using
+ * java.lang.reflect.
+ *
+ * @author Werner Randelshofer
+ * @version 1.3.1 2006-09-18 Fixed javadoc warnings.
+ * <br>1.2 2006-08-20 Additional invokeIfExists method added.
+ * <br>1.2 2006-05-07 Added invokeNew method.
+ * <br>1.1 2006-02-18 Added more convenience methods.
+ * <br>1.0 September 24, 2005 Created.
+ */
+public class Methods {
+ /**
+ * Prevent instance creation.
+ */
+ private Methods() {
+ }
+
+ /**
+ * Invokes the specified accessible parameterless method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ * @return The return value of the method.
+ * @return NoSuchMethodException if the method does not exist or is not
+ * accessible.
+ */
+ public static Object invoke(Object obj, String methodName)
+ throws NoSuchMethodException {
+ try {
+ Method method = obj.getClass().getMethod(methodName, new Class[0]);
+ Object result = method.invoke(obj, new Object[0]);
+ return result;
+ } catch (IllegalAccessException e) {
+ throw new NoSuchMethodException(methodName+" is not accessible");
+ } catch (InvocationTargetException e) {
+ // The method is not supposed to throw exceptions
+ throw new InternalError(e.getMessage());
+ }
+ }
+ /**
+ * Invokes the specified accessible method with a string parameter if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ * @param stringParameter The String parameter
+ * @return The return value of the method or METHOD_NOT_FOUND.
+ * @return NoSuchMethodException if the method does not exist or is not accessible.
+ */
+ public static Object invoke(Object obj, String methodName, String stringParameter)
+ throws NoSuchMethodException {
+ try {
+ Method method = obj.getClass().getMethod(methodName, new Class[] { String.class });
+ Object result = method.invoke(obj, new Object[] { stringParameter });
+ return result;
+ } catch (IllegalAccessException e) {
+ throw new NoSuchMethodException(methodName+" is not accessible");
+ } catch (InvocationTargetException e) {
+ // The method is not supposed to throw exceptions
+ throw new InternalError(e.getMessage());
+ }
+ }
+
+ /**
+ * Invokes the specified method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ * @param types The parameter types.
+ * @param values The parameter values.
+ * @return The return value of the method.
+ * @return NoSuchMethodException if the method does not exist or is not accessible.
+ */
+ public static Object invoke(Object obj, String methodName, Class[] types, Object[] values)
+ throws NoSuchMethodException {
+ try {
+ Method method = obj.getClass().getMethod(methodName, types);
+ Object result = method.invoke(obj, values);
+ return result;
+ } catch (IllegalAccessException e) {
+ throw new NoSuchMethodException(methodName+" is not accessible");
+ } catch (InvocationTargetException e) {
+ // The method is not supposed to throw exceptions
+ throw new InternalError(e.getMessage());
+ }
+ }
+ /**
+ * Invokes the specified accessible parameterless method if it exists.
+ *
+ * @param clazz The class on which to invoke the method.
+ * @param methodName The name of the method.
+ * @return The return value of the method or METHOD_NOT_FOUND.
+ * @return NoSuchMethodException if the method does not exist or is not accessible.
+ */
+ public static Object invokeStatic(Class clazz, String methodName)
+ throws NoSuchMethodException {
+ try {
+ Method method = clazz.getMethod(methodName, new Class[0]);
+ Object result = method.invoke(null, new Object[0]);
+ return result;
+ } catch (IllegalAccessException e) {
+ throw new NoSuchMethodException(methodName+" is not accessible");
+ } catch (InvocationTargetException e) {
+ // The method is not supposed to throw exceptions
+ throw new InternalError(e.getMessage());
+ }
+ }
+ /**
+ * Invokes the specified static parameterless method if it exists.
+ *
+ * @param clazz The class on which to invoke the method.
+ * @param methodName The name of the method.
+ * @return The return value of the method.
+ * @return NoSuchMethodException if the method does not exist or is not accessible.
+ */
+ public static Object invokeStatic(String clazz, String methodName)
+ throws NoSuchMethodException {
+ try {
+ return invokeStatic(Class.forName(clazz), methodName);
+ } catch (ClassNotFoundException e) {
+ throw new NoSuchMethodException("class "+clazz+" not found");
+ }
+ }
+ /**
+ * Invokes the specified static method if it exists.
+ *
+ * @param clazz The class on which to invoke the method.
+ * @param methodName The name of the method.
+ * @param types The parameter types.
+ * @param values The parameter values.
+ * @return The return value of the method.
+ * @return NoSuchMethodException if the method does not exist or is not accessible.
+ */
+ public static Object invokeStatic(Class clazz, String methodName,
+ Class[] types, Object[] values)
+ throws NoSuchMethodException {
+ try {
+ Method method = clazz.getMethod(methodName, types);
+ Object result = method.invoke(null, values);
+ return result;
+ } catch (IllegalAccessException e) {
+ throw new NoSuchMethodException(methodName+" is not accessible");
+ } catch (InvocationTargetException e) {
+ // The method is not supposed to throw exceptions
+ throw new InternalError(e.getMessage());
+ }
+ }
+ /**
+ * Invokes the specified static method if it exists.
+ *
+ * @param clazz The class on which to invoke the method.
+ * @param methodName The name of the method.
+ * @param types The parameter types.
+ * @param values The parameter values.
+ * @return The return value of the method.
+ * @return NoSuchMethodException if the method does not exist or is not accessible.
+ */
+ public static Object invokeStatic(String clazz, String methodName,
+ Class[] types, Object[] values)
+ throws NoSuchMethodException {
+ try {
+ return invokeStatic(Class.forName(clazz), methodName, types, values);
+ } catch (ClassNotFoundException e) {
+ throw new NoSuchMethodException("class "+clazz+" not found");
+ }
+ }
+ /**
+ * Invokes the specified static method if it exists.
+ *
+ * @param clazz The class on which to invoke the method.
+ * @param methodName The name of the method.
+ * @param type The parameter types.
+ * @param value The parameter values.
+ * @return The return value of the method.
+ * @return NoSuchMethodException if the method does not exist or is not accessible.
+ */
+ public static Object invokeStatic(String clazz, String methodName,
+ Class type, Object value)
+ throws NoSuchMethodException {
+ try {
+ return invokeStatic(Class.forName(clazz), methodName, new Class[] {type}, new Object[] {value});
+ } catch (ClassNotFoundException e) {
+ throw new NoSuchMethodException("class "+clazz+" not found");
+ }
+ }
+ /**
+ * Invokes the specified static method if it exists.
+ *
+ * @param clazz The class on which to invoke the method.
+ * @param methodName The name of the method.
+ * @param types The parameter types.
+ * @param values The parameter values.
+ * @param defaultValue The default value.
+ * @return The return value of the method or the default value if the method
+ * does not exist or is not accessible.
+ */
+ public static Object invokeStatic(String clazz, String methodName,
+ Class[] types, Object[] values, Object defaultValue) {
+ try {
+ return invokeStatic(Class.forName(clazz), methodName, types, values);
+ } catch (ClassNotFoundException e) {
+ return defaultValue;
+ } catch (NoSuchMethodException e) {
+ return defaultValue;
+ }
+ }
+ /**
+ * Invokes the specified static method if it exists.
+ *
+ * @param clazz The class on which to invoke the method.
+ * @param methodName The name of the method.
+ * @param type The parameter type.
+ * @param value The parameter value.
+ * @return The return value of the method or the default value if the method
+ * does not exist or is not accessible.
+ */
+ public static Object invokeStatic(Class clazz, String methodName,
+ Class type, Object value) throws NoSuchMethodException {
+ return invokeStatic(clazz, methodName, new Class[] {type}, new Object[] {value});
+ }
+
+ /**
+ * Invokes the specified getter method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ * @param defaultValue This value is returned, if the method does not exist.
+ * @return The value returned by the getter method or the default value.
+ */
+ public static int invokeGetter(Object obj, String methodName, int defaultValue) {
+ try {
+ Method method = obj.getClass().getMethod(methodName, new Class[0]);
+ Object result = method.invoke(obj, new Object[0]);
+ return (Integer) result;
+ } catch (NoSuchMethodException e) {
+ return defaultValue;
+ } catch (IllegalAccessException e) {
+ return defaultValue;
+ } catch (InvocationTargetException e) {
+ return defaultValue;
+ }
+ }
+ /**
+ * Invokes the specified getter method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ * @param defaultValue This value is returned, if the method does not exist.
+ * @return The value returned by the getter method or the default value.
+ */
+ public static long invokeGetter(Object obj, String methodName, long defaultValue) {
+ try {
+ Method method = obj.getClass().getMethod(methodName, new Class[0]);
+ Object result = method.invoke(obj, new Object[0]);
+ return (Long) result;
+ } catch (NoSuchMethodException e) {
+ return defaultValue;
+ } catch (IllegalAccessException e) {
+ return defaultValue;
+ } catch (InvocationTargetException e) {
+ return defaultValue;
+ }
+ }
+ /**
+ * Invokes the specified getter method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ * @param defaultValue This value is returned, if the method does not exist.
+ * @return The value returned by the getter method or the default value.
+ */
+ public static boolean invokeGetter(Object obj, String methodName, boolean defaultValue) {
+ try {
+ Method method = obj.getClass().getMethod(methodName, new Class[0]);
+ Object result = method.invoke(obj, new Object[0]);
+ return (Boolean) result;
+ } catch (NoSuchMethodException e) {
+ return defaultValue;
+ } catch (IllegalAccessException e) {
+ return defaultValue;
+ } catch (InvocationTargetException e) {
+ return defaultValue;
+ }
+ }
+ /**
+ * Invokes the specified getter method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ * @param defaultValue This value is returned, if the method does not exist.
+ * @return The value returned by the getter method or the default value.
+ */
+ public static Object invokeGetter(Object obj, String methodName, Object defaultValue) {
+ try {
+ Method method = obj.getClass().getMethod(methodName, new Class[0]);
+ Object result = method.invoke(obj, new Object[0]);
+ return result;
+ } catch (NoSuchMethodException e) {
+ return defaultValue;
+ } catch (IllegalAccessException e) {
+ return defaultValue;
+ } catch (InvocationTargetException e) {
+ return defaultValue;
+ }
+ }
+ /**
+ * Invokes the specified getter method if it exists.
+ *
+ * @param clazz The class on which to invoke the method.
+ * @param methodName The name of the method.
+ * @param defaultValue This value is returned, if the method does not exist.
+ * @return The value returned by the getter method or the default value.
+ */
+ public static boolean invokeStaticGetter(Class clazz, String methodName, boolean defaultValue) {
+ try {
+ Method method = clazz.getMethod(methodName, new Class[0]);
+ Object result = method.invoke(null, new Object[0]);
+ return (Boolean) result;
+ } catch (NoSuchMethodException e) {
+ return defaultValue;
+ } catch (IllegalAccessException e) {
+ return defaultValue;
+ } catch (InvocationTargetException e) {
+ return defaultValue;
+ }
+ }
+ /**
+ * Invokes the specified setter method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ */
+ public static Object invoke(Object obj, String methodName, boolean newValue)
+ throws NoSuchMethodException {
+ try {
+ Method method = obj.getClass().getMethod(methodName, new Class[] { Boolean.TYPE} );
+ return method.invoke(obj, new Object[] {newValue});
+ } catch (IllegalAccessException e) {
+ throw new NoSuchMethodException(methodName+" is not accessible");
+ } catch (InvocationTargetException e) {
+ // The method is not supposed to throw exceptions
+ throw new InternalError(e.getMessage());
+ }
+ }
+ /**
+ * Invokes the specified method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ */
+ public static Object invoke(Object obj, String methodName, int newValue)
+ throws NoSuchMethodException {
+ try {
+ Method method = obj.getClass().getMethod(methodName, new Class[] { Integer.TYPE} );
+ return method.invoke(obj, new Object[] {newValue});
+ } catch (IllegalAccessException e) {
+ throw new NoSuchMethodException(methodName+" is not accessible");
+ } catch (InvocationTargetException e) {
+ // The method is not supposed to throw exceptions
+ throw new InternalError(e.getMessage());
+ }
+ }
+ /**
+ * Invokes the specified setter method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ */
+ public static Object invoke(Object obj, String methodName, float newValue)
+ throws NoSuchMethodException {
+ try {
+ Method method = obj.getClass().getMethod(methodName, new Class[] { Float.TYPE} );
+ return method.invoke(obj, new Object[] {newValue});
+ } catch (IllegalAccessException e) {
+ throw new NoSuchMethodException(methodName+" is not accessible");
+ } catch (InvocationTargetException e) {
+ // The method is not supposed to throw exceptions
+ throw new InternalError(e.getMessage());
+ }
+ }
+ /**
+ * Invokes the specified setter method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ */
+ public static Object invoke(Object obj, String methodName, Class clazz, Object newValue)
+ throws NoSuchMethodException {
+ try {
+ Method method = obj.getClass().getMethod(methodName, new Class[] { clazz } );
+ return method.invoke(obj, new Object[] { newValue});
+ } catch (IllegalAccessException e) {
+ throw new NoSuchMethodException(methodName+" is not accessible");
+ } catch (InvocationTargetException e) {
+ // The method is not supposed to throw exceptions
+ throw new InternalError(e.getMessage());
+ }
+ }
+ /**
+ * Invokes the specified setter method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ */
+ public static void invokeIfExists(Object obj, String methodName) {
+ try {
+ invoke(obj, methodName);
+ } catch (NoSuchMethodException e) {
+ // ignore
+ }
+ }
+ /**
+ * Invokes the specified setter method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ */
+ public static void invokeIfExists(Object obj, String methodName, int newValue) {
+ try {
+ invoke(obj, methodName, newValue);
+ } catch (NoSuchMethodException e) {
+ // ignore
+ }
+ }
+ /**
+ * Invokes the specified setter method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ */
+ public static void invokeIfExists(Object obj, String methodName, float newValue) {
+ try {
+ invoke(obj, methodName, newValue);
+ } catch (NoSuchMethodException e) {
+ // ignore
+ }
+ }
+ /**
+ * Invokes the specified method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ */
+ public static void invokeIfExists(Object obj, String methodName, boolean newValue) {
+ try {
+ invoke(obj, methodName, newValue);
+ } catch (NoSuchMethodException e) {
+ // ignore
+ }
+ }
+ /**
+ * Invokes the specified setter method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ */
+ public static void invokeIfExists(Object obj, String methodName, Class parameterClass, Object newValue) {
+ try {
+ invoke(obj, methodName, parameterClass, newValue);
+ } catch (NoSuchMethodException e) {
+ // ignore
+ }
+ }
+ /**
+ * Invokes the specified setter method if it exists.
+ *
+ * @param obj The object on which to invoke the method.
+ * @param methodName The name of the method.
+ */
+ public static void invokeIfExistsWithEnum(Object obj, String methodName, String enumClassName, String enumValueName) {
+ try {
+ Class enumClass = Class.forName(enumClassName);
+ Object enumValue = invokeStatic("java.lang.Enum", "valueOf", new Class[] {Class.class, String.class},
+ new Object[] {enumClass, enumValueName}
+ );
+ invoke(obj, methodName, enumClass, enumValue);
+ } catch (ClassNotFoundException e) {
+ // ignore
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ // ignore
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Invokes the specified constructor if it exists.
+ *
+ * @param clazz The Class on which to invoke the constructor.
+ * @param types The parameter types of the constructor.
+ * @param values The parameter values of the constructor.
+ */
+ public static Object newInstance(Class clazz, Class[] types, Object[] values)
+ throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
+ return clazz.getConstructor(types).newInstance(values);
+ };
+
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/ResourceBundleUtil.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/ResourceBundleUtil.java
new file mode 100644
index 0000000..fc6c3ee
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/ResourceBundleUtil.java
@@ -0,0 +1,204 @@
+/*
+ * @(#)ResourceBundleUtil.java 1.3.3 2005-11-07
+ *
+ * Copyright (c) 2000-2005 Werner Randelshofer
+ * Staldenmattweg 2, CH-6405 Immensee, Switzerland
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util;
+
+import java.util.*;
+import javax.swing.KeyStroke;
+import javax.swing.UIManager;
+import javax.swing.ImageIcon;
+import java.text.MessageFormat;
+import java.net.URL;
+
+/**
+ * This is a convenience wrapper for accessing resources stored in a ResourceBundle.
+ *
+ * @author Werner Randelshofer, Staldenmattweg 2, CH-6405 Immensee, Switzerland
+ * @version 1,3.3 2005-11-07 Method getLocale added.
+ * <br>1.3.2 2004-05-02 Method getLAFBundle without Locale as parameter
+ * added.
+ * <br>1.3.1 2002-07-30 Method getLAFBundle now takes a Locale as
+ * an additional parameter.
+ * <br>1.3 2001-10-10 The default resource name changed from 'name_Metal'
+ * to 'name'.
+ * <br> 1.2 2001-07-23 Adaptation to JDK 1.3 in progress.
+ * <br> 1.0 2000-06-10 Created.
+ */
+public class ResourceBundleUtil {
+ /** The wrapped resource bundle. */
+ private ResourceBundle resource;
+
+ /**
+ * Creates a new ResouceBundleUtil which wraps
+ * the provided resource bundle.
+ */
+ public ResourceBundleUtil(ResourceBundle r) {
+ resource = r;
+ }
+
+
+ /**
+ * Get a String from the ResourceBundle.
+ * <br>Convenience method to save casting.
+ *
+ * @param key The key of the property.
+ * @return The value of the property. Returns the key
+ * if the property is missing.
+ */
+ public String getString(String key) {
+ try {
+ return resource.getString(key);
+ } catch (MissingResourceException e) {
+ return '-'+key+'-';
+ }
+ }
+ /**
+ * Get an image icon from the ResourceBundle.
+ * <br>Convenience method .
+ *
+ * @param key The key of the property.
+ * @return The value of the property. Returns null
+ * if the property is missing.
+ */
+ public ImageIcon getImageIcon(String key, Class baseClass) {
+ try {
+ String rsrcName = resource.getString(key);
+ if (rsrcName.equals("")) {
+ return null;
+ }
+ URL url = baseClass.getResource(rsrcName);
+ return (url == null) ? null : new ImageIcon(url);
+ } catch (MissingResourceException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Get a Mnemonic from the ResourceBundle.
+ * <br>Convenience method.
+ *
+ * @param key The key of the property.
+ * @return The first char of the value of the property.
+ * Returns '\0' if the property is missing.
+ */
+ public char getMnemonic(String key) {
+ String s = resource.getString(key);
+ return (s == null || s.length() == 0) ? '\0' : s.charAt(0);
+ }
+ /**
+ * Get a Mnemonic from the ResourceBundle.
+ * <br>Convenience method.
+ *
+ * @param key The key of the property. This method appends "Mnem" to the key.
+ * @return The first char of the value of the property.
+ * Returns '\0' if the property is missing.
+ */
+ public char getMnem(String key) {
+ String s = resource.getString(key+"Mnem");
+ return (s == null || s.length() == 0) ? '\0' : s.charAt(0);
+ }
+
+ /**
+ * Get a KeyStroke from the ResourceBundle.
+ * <BR>Convenience method.
+ *
+ * @param key The key of the property.
+ * @return <code>javax.swing.KeyStroke.getKeyStroke(value)</code>.
+ * Returns null if the property is missing.
+ */
+ public KeyStroke getKeyStroke(String key) {
+ KeyStroke ks = null;
+ try {
+ String s = resource.getString(key);
+ ks = (s == null) ? (KeyStroke) null : KeyStroke.getKeyStroke(s);
+ } catch (NoSuchElementException e) {
+ }
+ return ks;
+ }
+ /**
+ * Get a KeyStroke from the ResourceBundle.
+ * <BR>Convenience method.
+ *
+ * @param key The key of the property. This method adds "Acc" to the key.
+ * @return <code>javax.swing.KeyStroke.getKeyStroke(value)</code>.
+ * Returns null if the property is missing.
+ */
+ public KeyStroke getAcc(String key) {
+ KeyStroke ks = null;
+ try {
+ String s = resource.getString(key+"Acc");
+ ks = (s == null) ? (KeyStroke) null : KeyStroke.getKeyStroke(s);
+ } catch (NoSuchElementException e) {
+ }
+ return ks;
+ }
+
+ public String getFormatted(String key, Object argument) {
+ return MessageFormat.format(resource.getString(key), new Object[] {argument});
+ }
+ public String getFormatted(String key, Object[] arguments) {
+ return MessageFormat.format(resource.getString(key), arguments);
+ }
+
+ public Locale getLocale() {
+ return resource.getLocale();
+ }
+
+ /**
+ * Get the appropriate ResourceBundle subclass.
+ *
+ * @see java.util.ResourceBundle
+ */
+ public static ResourceBundleUtil getBundle(String baseName)
+ throws MissingResourceException {
+ return new ResourceBundleUtil(ResourceBundle.getBundle(baseName, Locale.getDefault()));
+ }
+ /**
+ * Get the appropriate ResourceBundle subclass.
+ * The baseName is extended by the Swing Look and Feel ID
+ * and by the Locale code returned by Locale.getDefault().
+ *
+ * The default Look and Feel ID is Metal.
+ *
+ * @see java.util.ResourceBundle
+ */
+ public static ResourceBundleUtil getLAFBundle(String baseName)
+ throws MissingResourceException {
+ return getLAFBundle(baseName, Locale.getDefault());
+ }
+ /**
+ * Get the appropriate ResourceBundle subclass.
+ * The baseName is extended by the Swing Look and Feel ID
+ * and by the Locale code.
+ *
+ * The default Look and Feel ID is Metal.
+ *
+ * @see java.util.ResourceBundle
+ */
+ public static ResourceBundleUtil getLAFBundle(String baseName, Locale locale)
+ throws MissingResourceException {
+ ResourceBundleUtil r;
+ try {
+ r = new ResourceBundleUtil(
+ ResourceBundle.getBundle(
+ baseName + "_" + UIManager.getLookAndFeel().getID(), locale
+ )
+ );
+ } catch (MissingResourceException e) {
+ r = new ResourceBundleUtil(
+ ResourceBundle.getBundle(baseName, locale)
+ );
+ }
+ return r;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/ShiftedIcon.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/ShiftedIcon.java
new file mode 100644
index 0000000..0408d98
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/ShiftedIcon.java
@@ -0,0 +1,59 @@
+/*
+ * @(#)ShiftedIcon.java 1.0 May 12, 2006
+ *
+ * Copyright (c) 2006 Werner Randelshofer
+ * Staldenmattweg 2, CH-6405 Immensee, Switzerland
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util;
+
+import java.awt.*;
+import javax.swing.*;
+/**
+ * ShiftedIcon renders a target icon at a different location and can return
+ * different width and height values than the target.
+ *
+ * @author Werner Randelshofer.
+ * @version 1.0 May 12, 2006 Created.
+ */
+public class ShiftedIcon implements Icon {
+ private Icon target;
+ private Rectangle shift;
+
+ /** Creates a new instance. */
+ public ShiftedIcon(Icon target, Point shift) {
+ this.target = target;
+ this.shift = new Rectangle(
+ shift.x, shift.y,
+ target.getIconWidth(),
+ target.getIconHeight()
+ );
+ }
+ public ShiftedIcon(Icon target, Rectangle shiftAndSize) {
+ this.target = target;
+ this.shift = shiftAndSize;
+ }
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ target.paintIcon(c, g, x + shift.x, y + shift.y);
+ }
+
+ @Override
+ public int getIconWidth() {
+ return shift.width;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return shift.height;
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/Worker.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/Worker.java
new file mode 100644
index 0000000..1ce308f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/Worker.java
@@ -0,0 +1,88 @@
+/*
+ * @(#)Worker.java 2.1 2005-10-16
+ *
+ * Copyright (c) 1998-2005 Werner Randelshofer
+ * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
+ * All rights reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Werner Randelshofer. ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Werner Randelshofer.
+ */
+//package ch.randelshofer.util;
+package org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.util;
+
+import java.awt.ActiveEvent;
+import javax.swing.SwingUtilities;
+
+/**
+ * This is an abstract class that you subclass to
+ * perform GUI-related work in a dedicated event dispatcher.
+ * <p>
+ * This class is similar to SwingWorker but less complex.
+ * Like a SwingWorker it can run using an an internal
+ * worker thread but it can also be like a Runnable object.
+ *
+ * @author Werner Randelshofer
+ * @version 2.1 2005-10-16 Method start() added.
+ * <br>2.0 2005-09-27 Revised.
+ * <br>1.1.1 2001-08-24 Call finished() within finally block.
+ * <br>1.1 2001-08-24 Reworked for JDK 1.3.
+ * <br>1.0 1998-10-07 Created.
+ */
+public abstract class Worker implements Runnable {
+ private Object value; // see getValue(), setValue()
+
+ /**
+ * Calls #construct on the current thread and invokes
+ * #finished on the AWT event dispatcher thread.
+ */
+ @Override
+ public final void run() {
+ final Runnable doFinished = new Runnable() {
+ @Override
+ public void run() { finished(getValue()); }
+ };
+ try {
+ setValue(construct());
+ } catch (Throwable e) {
+ e.printStackTrace();
+ } finally {
+ SwingUtilities.invokeLater(doFinished);
+ }
+ }
+
+ /**
+ * Compute the value to be returned by the <code>get</code> method.
+ */
+ public abstract Object construct();
+ /**
+ * Called on the event dispatching thread (not on the worker thread)
+ * after the <code>construct</code> method has returned.
+ *
+ * @param value The return value of the construct method.
+ */
+ public abstract void finished(Object value);
+ /**
+ * Get the value produced by the worker thread, or null if it
+ * hasn't been constructed yet.
+ */
+ protected synchronized Object getValue() {
+ return value;
+ }
+ /**
+ * Set the value produced by worker thread
+ */
+ private synchronized void setValue(Object x) {
+ value = x;
+ }
+
+ /**
+ * Starts the Worker on an internal worker thread.
+ */
+ public void start() {
+ new Thread(this).start();
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/package.html b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/package.html
new file mode 100644
index 0000000..b257a03
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/util/package.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+ @(#)package.html 1.0 2005-01-12
+
+ Copyright (c) 2005 Werner Randelshofer
+ Staldenmattweg 2, Immensee, CH-6405, Switzerland
+ All rights reserved.
+
+ This software is the confidential and proprietary information of
+ Werner Randelshofer. ("Confidential Information"). You shall not
+ disclose such Confidential Information and shall use it only in
+ accordance with the terms of the license agreement you entered into
+ with Werner Randelshofer.
+
+ CopyrightVersion 1.0
+
+-->
+</head>
+<body>
+Contains utility classes which are used by the Quaqua Look and Feel.
+</body>
+</html>
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/xoetrope/editor/color/ColorWheelPanel.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/xoetrope/editor/color/ColorWheelPanel.java
new file mode 100644
index 0000000..2e99638
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/xoetrope/editor/color/ColorWheelPanel.java
@@ -0,0 +1,1493 @@
+package org.pushingpixels.substance.internal.contrib.xoetrope.editor.color;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.font.TextAttribute;
+import java.awt.geom.*;
+import java.awt.image.BufferedImage;
+import java.text.AttributedString;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.colorchooser.AbstractColorChooserPanel;
+import javax.swing.event.*;
+
+/**
+ * A color wheel showing a Red, Yellow, Blue color model traditionally used by
+ * graphic artists. $Revision: 2254 $
+ */
+public class ColorWheelPanel extends AbstractColorChooserPanel implements
+ ActionListener, MouseListener, MouseMotionListener, MouseWheelListener,
+ ChangeListener {
+ public static final int MONOCHROMATIC_SCHEME = 0;
+ public static final int CONTRASTING_SCHEME = 1;
+ public static final int SOFT_CONTRAST_SCHEME = 2;
+ public static final int DOUBLE_CONTRAST_SCHEME = 3;
+ public static final int ANALOGIC_SCHEME = 4;
+
+ public static final int CTRL_ADJUST = 0;
+ public static final int ALWAYS_ADJUST = 1;
+ public static final int NEVER_ADJUST = 2;
+
+ protected JTextField hueEdit, satEdit, brightEdit, baseColorEdit;
+ protected BufferedImage pickerImage;
+ protected ColorWheel imagePicker;
+ protected JPanel fixedPanel;
+ protected JButton resetBtn;
+ protected JSlider brightnessSlider, saturationSlider;
+ protected JLabel baseColorLabel;
+ protected Ellipse2D innerCircle, outerCircle, borderCircle;
+ protected JCheckBox useWebColors, decimalRGB;
+ protected Font font9pt;
+
+ protected ModelColor chooserColor;
+ protected ModelColor[] selectedIttenColours;
+
+ private float values[] = new float[3];
+ private double h, s, b;
+
+ private int colorScheme = 0;
+
+ private boolean busy = false;
+ private boolean displayScheme = false;
+ private boolean hasChooser = false;
+
+ private ArrayList<ChangeListener> changeListeners;
+ private static double[] arcDelta = { -7.5, -7.5, -7.5, -7.5, -7.5, -1.0,
+ 4.0, 7.5 };
+
+ private double ringThickness;
+ private GeneralPath[] paths;
+ private static ResourceBundle labelBundle;
+
+ // Rollover related variables
+ private GeneralPath rolloverPath, selectedPath;
+ private boolean showRollovers;
+ private Color rolloverColor, selectedColor;
+ private Color systemColor;
+
+ private String fontFamily;
+
+ private int adjustWheel;
+ private boolean adjustRollover;
+ private boolean ctrlKeyDown;
+ private double saturationMultipler, brightnessMultipler;
+
+ /**
+ * Creates a new instance of ColorWheelPanel
+ */
+ public ColorWheelPanel() {
+ saturationMultipler = brightnessMultipler = 1.0;
+ changeListeners = new ArrayList<ChangeListener>();
+ adjustWheel = CTRL_ADJUST;
+ adjustRollover = true;
+ ctrlKeyDown = false;
+
+ font9pt = UIManager.getFont("ColorChooser.smallFont");
+ if (font9pt == null)
+ font9pt = new Font("Arial", 0, 9);
+
+ fontFamily = font9pt.getFamily();
+
+ showRollovers = true;
+ innerCircle = new Ellipse2D.Double(96, 96, 36, 36);
+ outerCircle = new Ellipse2D.Double(6, 6, 214, 214);
+ borderCircle = new Ellipse2D.Double(0, 0, 227, 227);
+
+ fixedPanel = new JPanel();
+ fixedPanel.setLayout(null);
+ fixedPanel.setOpaque(false);
+ // fixedPanel.setBackground( Color.white );
+ fixedPanel.setBounds(0, 0, 255, 328);
+ fixedPanel.setPreferredSize(new Dimension(255, 328));
+
+ setLayout(new LayoutManager() {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ Dimension fpp = fixedPanel.getPreferredSize();
+ int dx = (parent.getWidth() - fpp.width) / 2;
+ int dy = (parent.getHeight() - fpp.height) / 2;
+ fixedPanel.setBounds(dx, dy, fpp.width, fpp.height);
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return preferredLayoutSize(parent);
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ return fixedPanel.getPreferredSize();
+ }
+
+ });
+ imagePicker = new ColorWheel();
+ imagePicker.setBounds(0, 0, 228, 228);
+ imagePicker.addMouseListener(this);
+ imagePicker.addMouseMotionListener(this);
+ imagePicker.setOpaque(false);
+ imagePicker.addMouseWheelListener(this);
+ fixedPanel.add(imagePicker);
+
+ brightnessSlider = new JSlider(JSlider.VERTICAL);
+ brightnessSlider.setBounds(230, 0, 25, 108);
+ brightnessSlider.setMinimum(0);
+ brightnessSlider.setMaximum(100);
+ brightnessSlider.setValue(100);
+ brightnessSlider.setOpaque(false);
+ // brightnessSlider.setBackground( Color.white );
+ brightnessSlider.setPaintLabels(true);
+ brightnessSlider.addChangeListener(this);
+ brightnessSlider.addMouseWheelListener(this);
+ brightnessSlider.addMouseMotionListener(this);
+ brightnessSlider.setToolTipText(getLabel("Xoetrope.ctrlDrag",
+ "CTRL+drag to adjust the color wheel"));
+ fixedPanel.add(brightnessSlider);
+
+ resetBtn = new JButton();
+ resetBtn.setBounds(237, 109, 10, 10);
+ resetBtn.setBackground(getBackground());
+ resetBtn.addActionListener(this);
+ resetBtn.setToolTipText(getLabel("Xoetrope.reset",
+ "Reset the color wheel sauturation and brightness"));
+ fixedPanel.add(resetBtn);
+
+ saturationSlider = new JSlider(JSlider.VERTICAL);
+ saturationSlider.setBounds(230, 120, 25, 110);
+ saturationSlider.setMinimum(0);
+ saturationSlider.setMaximum(100);
+ saturationSlider.setValue(100);
+ saturationSlider.setOpaque(false);
+ // saturationSlider.setBackground( Color.white );
+ saturationSlider.setInverted(true);
+ saturationSlider.setPaintLabels(true);
+ saturationSlider.addChangeListener(this);
+ saturationSlider.addMouseWheelListener(this);
+ saturationSlider.addMouseMotionListener(this);
+ saturationSlider.setToolTipText(getLabel("Xoetrope.ctrlDrag",
+ "CTRL+drag to adjust the color wheel"));
+ fixedPanel.add(saturationSlider);
+
+ useWebColors = new JCheckBox(getLabel("Xoetrope.webSafeColors",
+ "Use web safe colors"));
+ useWebColors.setBounds(8, 248, 160, 18);
+ useWebColors.addActionListener(this);
+ useWebColors.setOpaque(false);
+ useWebColors.setFont(font9pt);
+ fixedPanel.add(useWebColors);
+
+ decimalRGB = new JCheckBox(getLabel("Xoetrope.decimalRGB",
+ "Decimal RGB"));
+ decimalRGB.setBounds(173, 248, 88, 18);
+ decimalRGB.addActionListener(this);
+ decimalRGB.setOpaque(false);
+ decimalRGB.setFont(font9pt);
+ fixedPanel.add(decimalRGB);
+
+ baseColorLabel = new JLabel();
+ baseColorLabel.setBounds(10, 268, 160, 18);
+ baseColorLabel.setBackground(Color.red);
+ baseColorLabel.setOpaque(true);
+ baseColorLabel.setToolTipText(getLabel("Xoetrope.systemColorsTooltip",
+ "Right click for system colours"));
+ fixedPanel.add(baseColorLabel);
+ baseColorLabel.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent me) {
+ showSystemColorList(me.getPoint());
+ }
+ });
+
+ baseColorEdit = new JTextField();
+ baseColorEdit.setBounds(180, 268, 75, 18);
+ baseColorEdit.setOpaque(true);
+ fixedPanel.add(baseColorEdit);
+ baseColorEdit.addActionListener(this);
+
+ hueEdit = new JTextField();
+ hueEdit.setBounds(10, 288, 75, 20);
+ fixedPanel.add(hueEdit);
+ hueEdit.setText("0");
+ hueEdit.getDocument().addDocumentListener(
+ new ColorDocumentListener(hueEdit));
+
+ JLabel hueLabel = new JLabel(getLabel("Xoetrope.hue", "Hue")
+ + " \u00B0");
+ hueLabel.setBounds(10, 308, 75, 20);
+ hueLabel.setFont(font9pt);
+ fixedPanel.add(hueLabel);
+
+ satEdit = new JTextField();
+ satEdit.setBounds(95, 288, 75, 20);
+ fixedPanel.add(satEdit);
+ satEdit.setText("0");
+ satEdit.getDocument().addDocumentListener(
+ new ColorDocumentListener(satEdit));
+
+ JLabel satLabel = new JLabel(getLabel("Xoetrope.saturation",
+ "Saturation")
+ + " %");
+ satLabel.setBounds(95, 308, 75, 20);
+ satLabel.setFont(font9pt);
+ fixedPanel.add(satLabel);
+
+ brightEdit = new JTextField();
+ brightEdit.setBounds(180, 288, 75, 20);
+ fixedPanel.add(brightEdit);
+ brightEdit.setText("0");
+ brightEdit.getDocument().addDocumentListener(
+ new ColorDocumentListener(brightEdit));
+
+ JLabel brightLabel = new JLabel(getLabel("Xoetrope.brightness",
+ "Brightness")
+ + " %");
+ brightLabel.setBounds(180, 308, 75, 20);
+ brightLabel.setFont(font9pt);
+ fixedPanel.add(brightLabel);
+
+ add(fixedPanel);
+ }
+
+ /**
+ * Set the reference to the selected colours for the colour scheme
+ *
+ * @param clrs
+ * the colors
+ */
+ public void setSelectedColors(ModelColor[] clrs) {
+ selectedIttenColours = clrs;
+ }
+
+ /**
+ * Add a listener for changes in the selected color
+ *
+ * @param l
+ * the change listener to add
+ */
+ public void addChangeListener(ChangeListener l) {
+ changeListeners.add(l);
+ }
+
+ /**
+ * Remove a change listener
+ *
+ * @param l
+ * the change listener to remove
+ */
+ public void removeChangeListener(ChangeListener l) {
+ changeListeners.remove(l);
+ }
+
+ /**
+ * Has the user selected the use decimal rgb checkbox?
+ *
+ * @return true if decimal rgb values are to be shown
+ */
+ public boolean useDecimalRGB() {
+ return decimalRGB.isSelected();
+ }
+
+ /**
+ * Has the user selected the use web safe colors checkbox?
+ *
+ * @return true if only web safe colors are to be shown
+ */
+ public boolean useWebColors() {
+ return useWebColors.isSelected();
+ }
+
+ /**
+ * Set the display of the color scheme markers.
+ *
+ * @param disp
+ * true to display the color scheme markers.
+ */
+ public void setDisplayScheme(boolean disp) {
+ displayScheme = disp;
+ }
+
+ /**
+ * Get the selected colors hue
+ *
+ * @return the selected hue in the range 0-255
+ */
+ public int getHue() {
+ try {
+ return Integer.parseInt(hueEdit.getText());
+ } catch (NumberFormatException e) {
+ }
+
+ return 128;
+ }
+
+ /**
+ * Set the selected hue
+ *
+ * @param h
+ * the selected hue in the range 0-255
+ */
+ public void setHue(int h) {
+ try {
+ if (h < 0)
+ h = 360 + h;
+
+ int selHue = Math.max(0, Math.min(h, 360));
+ hueEdit.setText(Integer.toString(selHue));
+ resetColor();
+ } catch (NumberFormatException e) {
+ }
+ }
+
+ /**
+ * Get the selected colors saturation
+ *
+ * @return the selected saturation in the range 0-255
+ */
+ public int getSaturation() {
+ try {
+ return Integer.parseInt(satEdit.getText());
+ } catch (NumberFormatException e) {
+ }
+
+ return 128;
+ }
+
+ /**
+ * Get the selected colors brightness
+ *
+ * @return the selected brightness in the range 0-255
+ */
+ public int getBrightness() {
+ try {
+ return Integer.parseInt(brightEdit.getText());
+ } catch (NumberFormatException e) {
+ }
+
+ return 128;
+ }
+
+ /**
+ * Set the Itten color scheme to use
+ *
+ * @param scheme
+ * <ul>
+ * <li>-1 for no scheme display</li>
+ * <li>0 for a monchromatic color scheme: MONOCHROMATIC_SCHEME</li>
+ * <li>1 for a contrasting color scheme: CONTRASTING_SCHEME</li>
+ * <li>2 for a soft-contrasting color scheme:
+ * SOFT_CONTRAST_SCHEME</li>
+ * <li>3 for a double contrasting color scheme:
+ * DOUBLE_CONTRAST_SCHEME</li>
+ * <li>4 for a analogical color scheme: ANALOGIC_SCHEME</li>
+ * </ul>
+ */
+ public void setColorScheme(int scheme) {
+ colorScheme = scheme;
+ }
+
+ /**
+ * Change the hue to match the angle identified by the point (in the inner
+ * circle).
+ *
+ * @param pt
+ * the point within the inner circle
+ */
+ boolean moveHue(Point pt) {
+ if ((borderCircle.contains(pt) && !outerCircle.contains(pt))
+ || innerCircle.contains(pt)) {
+ int h = getAngle(pt);
+ hueEdit.setText(Integer.toString(h));
+ selectedPath = null;
+ resetColor();
+ return true;
+ }
+ return false;
+ }
+
+ private int getAngle(Point pt) {
+ int eX = (pt.x > 0) ? pt.x : 96;
+ int eY = (pt.y > 0) ? pt.y : 96;
+ int x = eX - 112;
+ int y = eY - 114;
+ return (int) (Math
+ .round(((Math.atan2(-x, y) * 180.0 / Math.PI) + 180.0) % 360.0));
+ }
+
+ public void setColor(Color c) {
+ systemColor = null;
+
+ if (c != null) {
+ int r = c.getRed();
+ int g = c.getGreen();
+ int b = c.getBlue();
+ if (useWebColors.isSelected()) {
+ r = Math.round(r / 51) * 51;
+ g = Math.round(g / 51) * 51;
+ b = Math.round(b / 51) * 51;
+ }
+ chooserColor = new ModelColor(r, g, b);
+ }
+ // else
+ c = new Color(chooserColor.R, chooserColor.G, chooserColor.B);
+
+ float[] oldValues = values;
+ values = c.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), values);
+ if (values[1] == 0.0F) {
+ s = values[1];
+ b = values[2];
+ } else if (values[2] == 0.0F) {
+ b = values[2];
+ } else {
+ h = values[0];
+ s = values[1];
+ b = values[2];
+ }
+ h = Math.min(Math.max(h, 0.0), 1.0);
+ s = Math.min(Math.max(s, 0.0), 1.0);
+ b = Math.min(Math.max(b, 0.0), 1.0);
+
+ if (values[1] != 0.0F) {
+ if (values[1] != 0.0F)
+ setHue();
+ setSaturation();
+ }
+ setBrightness();
+
+ busy = true;
+ brightnessSlider.setValue(Integer.parseInt(brightEdit.getText()));
+ saturationSlider.setValue(Integer.parseInt(satEdit.getText()));
+ busy = false;
+
+ baseColorLabel.setBackground(new Color(chooserColor.R, chooserColor.G,
+ chooserColor.B));
+
+ if ((0.5 * c.getRed() + c.getGreen() + 0.3 * c.getBlue()) < 220.0)
+ baseColorLabel.setForeground(Color.white);
+ else
+ baseColorLabel.setForeground(Color.black);
+
+ String colorStr;
+ if (decimalRGB.isSelected()) {
+ // Output decimal values
+ colorStr = " " + Integer.toString(c.getRed()) + "."
+ + Integer.toString(c.getGreen()) + "."
+ + Integer.toString(c.getBlue());
+ } else {
+ // Output HEX values
+ colorStr = " " + ModelColor.toHexString(c.getRed())
+ + ModelColor.toHexString(c.getGreen())
+ + ModelColor.toHexString(c.getBlue());
+ }
+ baseColorLabel.setText(colorStr);
+ baseColorEdit.setText(colorStr);
+
+ ChangeEvent evt = new ChangeEvent(this);
+ int numListeners = changeListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ ChangeListener l = changeListeners.get(i);
+ l.stateChanged(evt);
+ }
+
+ if (hasChooser)
+ getColorSelectionModel().setSelectedColor(c);
+ }
+
+ /**
+ * Get the selected color
+ *
+ * @return the color
+ */
+ public Color getColor() {
+ return new Color(chooserColor.R, chooserColor.G, chooserColor.B);
+ }
+
+ /**
+ * Get the chooser color
+ *
+ * @return
+ * the chooser color
+ */
+ public ModelColor getChooserColour() {
+ return chooserColor;
+ }
+
+ /**
+ * Set the value of the hue edit to match the current color
+ */
+ private void setHue() {
+ hueEdit.setText(Integer.toString(chooserColor.getHue()));
+ }
+
+ /**
+ * Set the value of the saturartion edit to match the current color
+ */
+ private void setSaturation() {
+ satEdit.setText(Integer.toString((int) (100.0 * chooserColor.S)));
+ }
+
+ /**
+ * Set the value of the brightness edit to match the current color
+ */
+ private void setBrightness() {
+ brightEdit.setText(Integer.toString((int) (100.0 * chooserColor.V)));
+ }
+
+ /**
+ * Respond to action events for the edit fields
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Object source = e.getSource();
+ if (source == resetBtn)
+ resetColorWheel();
+ else if (source instanceof JMenuItem) {
+ // A popup menu item has been selected
+ Color sysColor = getSystemColor(((JMenuItem) source).getText());
+ if (sysColor != null)
+ setColor(sysColor);
+
+ resetColor();
+ systemColor = sysColor;
+ if (hasChooser) {
+ hasChooser = false;
+ getColorSelectionModel().setSelectedColor(systemColor);
+ hasChooser = true;
+ }
+ return;
+ } else if (source == useWebColors) {
+ boolean snap = useWebColors.isSelected();
+ chooserColor.setWebSnap(snap);
+ if (snap)
+ resetColor();
+ else {
+ // Drop through to the next block to reset the internal color values
+ source = baseColorEdit;
+ }
+ } else if (source == baseColorEdit) {
+ String hex = baseColorEdit.getText().trim();
+ if (hex.length() == 0)
+ resetColor();
+ else if (decimalRGB.isSelected()) {
+ int pos = 0;
+ try {
+ int r = 255;
+ int g = 0;
+ int b = 0;
+ int pos2 = hex.indexOf('.', pos);
+ if (pos2 > 0) {
+ r = Integer.parseInt(hex.substring(pos, pos2));
+ pos = ++pos2;
+ pos2 = hex.indexOf('.', pos);
+ if (pos2 > 0) {
+ g = Integer.parseInt(hex.substring(pos, pos2));
+ pos = ++pos2;
+ if (pos2 < hex.length())
+ b = Integer.parseInt(hex.substring(pos));
+ }
+ }
+ setColor(new Color(r, g, b));
+ } catch (NumberFormatException nfe) {
+ setColor(Color.red);
+ baseColorEdit.setText("255.0.0");
+ }
+ } else {
+ for (int i = hex.length(); i < 6; i++)
+ hex += "0";
+ try {
+ setColor(new Color(ModelColor.hex2dec(hex.substring(0, 2)),
+ ModelColor.hex2dec(hex.substring(2, 4)), ModelColor
+ .hex2dec(hex.substring(4, 6))));
+ } catch (NumberFormatException nfe) {
+ setColor(Color.red);
+ baseColorEdit.setText("FF0000");
+ }
+ }
+ } else
+ resetColor();
+ }
+
+ /**
+ * Reset the displayed color to the color specified by the edit fields
+ */
+ private void resetColor() {
+ if (chooserColor != null) {
+ if (!busy) {
+ busy = true;
+ int h = 0;
+ try {
+ h = Integer.parseInt(hueEdit.getText());
+ selectedPath = null;
+ } catch (NumberFormatException nfe) {
+ hueEdit.setText("0");
+ }
+ if (h >= 360) {
+ h = h % 360;
+ hueEdit.setText(Integer.toString(h));
+ }
+ if (h < 0) {
+ h = (int) ((h + (Math.floor(-h / 360) + 1) * 360) % 360);
+ hueEdit.setText(Integer.toString(h));
+ }
+
+ double s = 1.0;
+ try {
+ s = Integer.parseInt(satEdit.getText()) / 100.0;
+ } catch (NumberFormatException nfe) {
+ satEdit.setText("100");
+ }
+ if (s > 1 || s < 0) {
+ s = (s < 0) ? 0 : 1;
+ satEdit.setText(Integer.toString((int) (s * 100.0)));
+ }
+
+ double v = 1.0;
+ try {
+ v = Integer.parseInt(brightEdit.getText()) / 100.0;
+ } catch (NumberFormatException nfe) {
+ brightEdit.setText("100");
+ }
+
+ if (v > 1 || v < 0) {
+ v = (v < 0) ? 0 : 1;
+ brightEdit.setText(Integer.toString((int) (v * 100.0)));
+ }
+
+ if (shouldAdjustWheel()) {
+ saturationMultipler = s;
+ brightnessMultipler = v;
+ }
+
+ if (selectedIttenColours != null)
+ selectedIttenColours[0].setHSV(h, s, v);
+ chooserColor.setHSV(h, s, v);
+ busy = false;
+ }
+ setColor(null);
+ }
+ }
+
+ /**
+ * Invoked when the mouse button has been clicked (pressed and released) on
+ * a component.
+ */
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ Object src = e.getSource();
+ if (src == imagePicker) {
+ Point pt = e.getPoint();
+
+ if (borderCircle.contains(pt)) {
+ selectedColor = rolloverColor;
+ selectedPath = rolloverPath;
+ if (!moveHue(pt)) {
+ if (outerCircle.contains(pt)) {
+ int width = imagePicker.getWidth();
+ int center = width / 2;
+ int dx = Math.abs(pt.x - center);
+ int dy = Math.abs(pt.y - center);
+ double dr = Math.pow((dx * dx + dy * dy), 0.5);
+ dr -= ringThickness * 1.5;
+ int bandIdx = (int) (dr / ringThickness);
+
+ int hue = 0;
+ int bandOffset = bandIdx * ModelColor.NUM_SEGMENTS;
+ for (int i = 0; i < ModelColor.NUM_SEGMENTS; i++) {
+ if (paths[bandOffset + i].contains(pt))
+ hue = i * 15;
+ }
+
+ int hueInc = (hue / 15) % 2;
+ // hue -= hue % 15;
+ ModelColor mc = new ModelColor(hue,
+ ModelColor.SATURATION_BANDS[bandIdx],
+ ModelColor.BRIGHTNESS_BANDS[bandIdx + 1
+ - hueInc]);
+ mc = new ModelColor(mc.H, saturationMultipler * mc.S,
+ brightnessMultipler * mc.V);
+ Color pixelColor = new Color(mc.getRed(),
+ mc.getGreen(), mc.getBlue());
+ if (!pixelColor.equals(Color.white))
+ setColor(pixelColor);
+ }
+ }
+ }
+ }
+ // repaint for synchronizing the hue marker
+ if (displayScheme)
+ imagePicker.repaint();
+ }
+
+ /**
+ * Invoked when a mouse button has been pressed on a component.
+ */
+ @Override
+ public void mousePressed(MouseEvent e) {
+ imagePicker.repaint();
+ }
+
+ /**
+ * Invoked when a mouse button has been released on a component.
+ */
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ /**
+ * Invoked when the mouse enters a component.
+ */
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ /**
+ * Invoked when the mouse exits a component.
+ */
+ @Override
+ public void mouseExited(MouseEvent e) {
+ rolloverPath = null;
+ repaint();
+ }
+
+ /**
+ * Invoked when the mouse exits a component.
+ */
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ GeneralPath oldPath = rolloverPath;
+ rolloverPath = null;
+ if (e.getSource() == imagePicker) {
+ Point pt = e.getPoint();
+ if (paths != null) {
+ int numPaths = paths.length;
+ for (int i = 0; i < numPaths; i++) {
+ if (paths[i].contains(pt.x, pt.y)) {
+ rolloverPath = paths[i];
+ ModelColor[][] baseColors = ModelColor.getBaseColors();
+ int ring = i / ModelColor.NUM_SEGMENTS;
+ ModelColor modelColor = baseColors[i
+ % ModelColor.NUM_SEGMENTS][ring];
+ if (adjustRollover)
+ modelColor = new ModelColor(modelColor.H,
+ saturationMultipler * modelColor.S,
+ brightnessMultipler * modelColor.V);
+
+ rolloverColor = new Color(modelColor.getRed(),
+ modelColor.getGreen(), modelColor.getBlue());
+ if (ring < 4)
+ rolloverColor = rolloverColor.darker();
+ else
+ rolloverColor = rolloverColor.brighter().brighter();
+ break;
+ }
+ }
+ }
+ }
+
+ if (rolloverPath != oldPath)
+ repaint();
+ }
+
+ /**
+ * Move the sliders in rsponse to the mouse wheel
+ */
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ Object src = e.getSource();
+ ctrlKeyDown = e.isControlDown();
+ int notches = e.getWheelRotation();
+
+ if (src == brightnessSlider) {
+ brightnessSlider
+ .setValue(brightnessSlider.getValue() - 2 * notches);
+ } else if (src == saturationSlider) {
+ saturationSlider
+ .setValue(saturationSlider.getValue() + 2 * notches);
+ } else if (src == imagePicker) {
+ setHue(getHue() + 2 * notches);
+ }
+
+ ctrlKeyDown = false;
+ }
+
+ /**
+ * Invoked when the mouse exits a component.
+ */
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ ctrlKeyDown = e.isControlDown();
+ }
+
+ /**
+ * Invoked when the target of the listener has changed its state.
+ *
+ * @param e
+ * a ChangeEvent object
+ */
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ Object source = e.getSource();
+ if (source == saturationSlider) {
+ satEdit.setText(Integer.toString(saturationSlider.getValue()));
+ resetColor();
+ } else if (source == brightnessSlider) {
+ brightEdit.setText(Integer.toString(brightnessSlider.getValue()));
+ resetColor();
+ }
+
+ if (hasChooser) {
+ getColorSelectionModel().setSelectedColor(
+ new Color(chooserColor.getRed(), chooserColor.getGreen(),
+ chooserColor.getBlue()));
+ }
+
+ ctrlKeyDown = false;
+ }
+
+ @Override
+ protected void buildChooser() {
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Xoetrope Color Wheel";
+ }
+
+ @Override
+ public Icon getLargeDisplayIcon() {
+ return UIManager.getIcon("ColorChooser.colorWheelIcon");
+ }
+
+ @Override
+ public Icon getSmallDisplayIcon() {
+ return getLargeDisplayIcon();
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(255, 328);
+ }
+
+ @Override
+ public void updateChooser() {
+ if (hasChooser) {
+ Color selected = getColorFromModel();
+ if (selected == null) {
+ setSelectedColors(new ModelColor[0]);
+ setColor(null);
+ } else {
+ ModelColor selectedModelColor = new ModelColor(selected.getRed(),
+ selected.getGreen(), selected.getBlue());
+ setSelectedColors(new ModelColor[] { selectedModelColor });
+ setColor(selected);
+ }
+ }
+ }
+
+ @Override
+ public void installChooserPanel(JColorChooser enclosingChooser) {
+ hasChooser = (enclosingChooser != null);
+
+ super.installChooserPanel(enclosingChooser);
+ // if runs in the color chooser, set the hue marker
+ this.setDisplayScheme(true);
+ }
+
+ // -ColorWheel inner
+ // class------------------------------------------------------
+ /**
+ * A class that wraps the image of the color wheel and draws markers for the
+ * selected color scheme
+ */
+ class ColorWheel extends JLabel {
+ public ColorWheel() {
+ }
+
+ /**
+ * Draw markers for the selected color scheme
+ */
+ @Override
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ paintWheel((Graphics2D) g);
+
+ if (displayScheme) {
+ double x, y;
+
+ int selIdx = colorScheme;// > 0 ? 1 : 0;
+ int numColours = Math.min(selIdx + 1, 4);
+ for (int i = 0; i < numColours; i++) {
+ double r = (selectedIttenColours[i].H - 90.0) / 360.0 * 2.0
+ * Math.PI;
+ x = Math.round(111.0 + 110.0 * Math.cos(r));
+ y = Math.round(111.0 + 110.0 * Math.sin(r));
+ g.setColor(Color.gray);
+ g.fillOval((int) x, (int) y, 4, 4);
+ g.setColor(Color.darkGray);
+ g.drawOval((int) x, (int) y, 4, 4);
+ }
+ }
+ }
+
+ public void paintWheel(Graphics2D g2d) {
+ // Store the paths for detecting the area with the mouse click.
+ if (paths == null)
+ paths = new GeneralPath[ModelColor.NUM_COLOR_RINGS
+ * ModelColor.NUM_SEGMENTS];
+
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+
+ ModelColor[][] baseColors = ModelColor.getBaseColors();
+ int idx = 0;
+ double width = getWidth() - 1;
+ double center = width / 2.0;
+ ringThickness = width / ((ModelColor.NUM_COLOR_RINGS + 2) * 2);
+ double fontHeight = ringThickness / 2.0;
+ double inset = ringThickness / 2.0;
+
+ // Paint the outer band
+ g2d.setColor(new Color(228, 228, 228));
+ Arc2D.Double innerArc = new Arc2D.Double(inset, inset, width
+ - inset - inset, width - inset - inset, 0.0, 360.0,
+ Arc2D.OPEN);
+ Arc2D.Double outerArc = new Arc2D.Double(0.0, 0.0, width, width,
+ 360.0, -360.0, Arc2D.OPEN);
+ GeneralPath gp = new GeneralPath();
+ gp.append(innerArc, true);
+ gp.append(outerArc, true);
+ gp.closePath();
+ g2d.fill(gp);
+
+ g2d.setColor(Color.black);
+ g2d.setStroke(new BasicStroke(0.3F));
+ g2d.draw(outerArc);
+
+ // Paint the inner yellow arc
+ g2d.setColor(new Color(255, 253, 220));
+ innerArc = new Arc2D.Double(center - ringThickness / 2.0, center
+ - ringThickness / 2.0, ringThickness, ringThickness, -30.0,
+ 180.0, Arc2D.OPEN);
+ outerArc = new Arc2D.Double(center - ringThickness, center
+ - ringThickness, ringThickness * 2, ringThickness * 2,
+ 150.0, -180.0, Arc2D.OPEN);
+ gp = new GeneralPath();
+ gp.append(innerArc, true);
+ gp.append(outerArc, true);
+ gp.closePath();
+ g2d.fill(gp);
+
+ // Paint the inner blue arc
+ g2d.setColor(new Color(202, 230, 252));
+ innerArc = new Arc2D.Double(center - ringThickness / 2.0, center
+ - ringThickness / 2.0, ringThickness, ringThickness, 150.0,
+ 180.0, Arc2D.OPEN);
+ outerArc = new Arc2D.Double(center - ringThickness, center
+ - ringThickness, ringThickness * 2, ringThickness * 2,
+ 330.0, -180.0, Arc2D.OPEN);
+ gp = new GeneralPath();
+ gp.append(innerArc, true);
+ gp.append(outerArc, true);
+ gp.closePath();
+ g2d.fill(gp);
+
+ // Draw the 'dial'
+ g2d.setColor(Color.black);
+ AffineTransform identityTransform = g2d.getTransform();
+ AffineTransform at = ((AffineTransform) identityTransform.clone());
+ at.translate(center, center);
+ at.rotate(Math.PI / 6.0);
+ g2d.setTransform(at);
+
+ gp = new GeneralPath();
+ gp.moveTo((float) (-ringThickness / 2.0), 0.0F);
+ gp.lineTo((float) (-ringThickness * 1.2), 0.0F);
+ gp.lineTo((float) (-ringThickness * 1.2), (float) (-fontHeight));
+ gp.lineTo((float) (-ringThickness * 1.4),
+ (float) (-fontHeight + ringThickness * 0.2));
+ gp.moveTo((float) (-ringThickness * 1.2), (float) (-fontHeight));
+ gp.lineTo((float) (-ringThickness),
+ (float) (-fontHeight + ringThickness * 0.2));
+ g2d.draw(gp);
+
+ gp = new GeneralPath();
+ gp.moveTo((float) (ringThickness / 2.0), 0.0F);
+ gp.lineTo((float) (ringThickness * 1.2), 0.0F);
+ gp.lineTo((float) (ringThickness * 1.2), (float) (fontHeight));
+ gp.lineTo((float) (ringThickness * 1.4),
+ (float) (fontHeight - ringThickness * 0.2));
+ gp.moveTo((float) (ringThickness * 1.2), (float) (fontHeight));
+ gp.lineTo((float) (ringThickness),
+ (float) (fontHeight - ringThickness * 0.2));
+ g2d.draw(gp);
+
+ // Draw the tick marks
+ double r1 = center;
+ double r2 = r1 - fontHeight;
+ double r3 = r1 - ringThickness / 2.3;
+ double r4 = r1 + ringThickness / 2.7;
+
+ // The angles for cos and sin are in radians
+ double inc = Math.PI / 12.0;
+ // double fullArc = Math.PI * 2.0;
+ g2d.setColor(Color.black);
+ for (int i = 0; i < ModelColor.NUM_SEGMENTS; i++) {
+ double angle = i * inc;
+ double sin = Math.sin(angle);
+ double cos = Math.cos(angle);
+ gp = new GeneralPath();
+ if ((width > 200) && (i % 2 == 0)) {
+ AttributedString as = new AttributedString(""
+ + (((i * 15) + 90) % 360) + "�");
+ as.addAttribute(TextAttribute.FAMILY, fontFamily);
+ as.addAttribute(TextAttribute.SIZE, (float) (fontHeight));
+ as.addAttribute(TextAttribute.FOREGROUND, Color.black);
+ at = ((AffineTransform) identityTransform.clone());
+ at.translate((center + fontHeight / 5.0 + r3 * cos),
+ (center + r3 * sin));
+ at.rotate(angle + Math.PI / 2.0);
+ g2d.setTransform(at);
+ g2d.drawString(as.getIterator(), 0.0F, 0.0F);
+ } else {
+ g2d.setTransform(identityTransform);
+ gp.moveTo((float) (center + r1 * cos), (float) (center + r1
+ * sin));
+ gp.lineTo((float) (center + r2 * cos), (float) (center + r2
+ * sin));
+ g2d.draw(gp);
+ }
+ }
+
+ // Paint the rings / star
+ // int pathIdx = 0;
+ for (int i = 0; i < ModelColor.NUM_COLOR_RINGS; i++) {
+ double outerX = inset + (ModelColor.NUM_COLOR_RINGS - (i + 1))
+ * ringThickness;
+ double outerW = width - outerX - outerX;
+ double innerX = outerX + ringThickness;
+ double innerW = outerW - 2 * ringThickness;
+ for (int j = 0; j < ModelColor.NUM_SEGMENTS; j++) {
+ ModelColor modelColor = baseColors[j][i];
+ modelColor = new ModelColor(modelColor.H,
+ saturationMultipler * modelColor.S,
+ brightnessMultipler * modelColor.V);
+ Color c = new Color(modelColor.getRed(), modelColor
+ .getGreen(), modelColor.getBlue());
+ g2d.setColor(c);
+ double startAngle = ((82.5 - (j * 15.0)) + 360) % 360.0;
+
+ double delta1 = j % 2 == 0 ? arcDelta[i] : -arcDelta[i];
+ double delta2 = j % 2 == 0 ? arcDelta[i + 1]
+ : -arcDelta[i + 1];
+ innerArc = new Arc2D.Double(innerX, innerX, innerW, innerW,
+ startAngle + delta1, 15.0 - 2.0 * delta1,
+ Arc2D.OPEN);
+ outerArc = new Arc2D.Double(outerX, outerX, outerW, outerW,
+ startAngle + 15.0 - delta2, -15.0 + 2.0 * delta2,
+ Arc2D.OPEN);
+ gp = new GeneralPath();
+ gp.append(innerArc, true);
+ gp.append(outerArc, true);
+ gp.closePath();
+
+ g2d.fill(gp);
+ paths[idx++] = gp;
+ }
+ }
+
+ // Paint the labels
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+ g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ if (width > 200) {
+ double angle = (Math.PI / 6.0) - (Math.PI / 2.0);
+ double angle2 = angle - 0.055;
+ double sin = Math.sin(angle2);
+ double cos = Math.cos(angle2);
+
+ AttributedString as = new AttributedString(getLabel(
+ "Xoetrope.warm", "WARM"));
+ as.addAttribute(TextAttribute.FAMILY, fontFamily);
+ as.addAttribute(TextAttribute.SIZE,
+ (float) (ringThickness / 1.5));
+ as.addAttribute(TextAttribute.FOREGROUND, new Color(92, 0, 0));
+ at = ((AffineTransform) identityTransform.clone());
+ at.translate((center + fontHeight / 5.0 + r4 * cos),
+ (center + r4 * sin));
+ at.rotate(angle + Math.PI / 2.0 + 0.05);
+ g2d.setTransform(at);
+ g2d.drawString(as.getIterator(), 0.0F, 0.0F);
+
+ angle += Math.PI;
+ sin = Math.sin(angle);
+ cos = Math.cos(angle);
+ as = new AttributedString(getLabel("Xoetrope.cold", "COLD"));
+ as.addAttribute(TextAttribute.FAMILY, fontFamily);
+ as.addAttribute(TextAttribute.SIZE,
+ (float) (ringThickness / 1.5));
+ as.addAttribute(TextAttribute.FOREGROUND, new Color(0, 0, 92));
+ at = ((AffineTransform) identityTransform.clone());
+ at.translate((center + fontHeight / 5.0 + r4 * cos),
+ (center + r4 * sin));
+ at.rotate(angle + Math.PI / 2.0 + 0.05);
+ g2d.setTransform(at);
+ g2d.drawString(as.getIterator(), 0.0F, 0.0F);
+
+ angle = Math.PI;
+ sin = Math.sin(angle);
+ cos = Math.cos(angle);
+ as = new AttributedString(getLabel("Xoetrope.saturation",
+ "Saturation"));
+ as.addAttribute(TextAttribute.FAMILY, fontFamily);
+ as.addAttribute(TextAttribute.SIZE,
+ (float) (ringThickness / 1.3));
+ as.addAttribute(TextAttribute.FOREGROUND, UIManager
+ .getColor("Label.foreground"));
+ at = ((AffineTransform) identityTransform.clone());
+ at.translate((width - fontHeight), (width));
+ at.rotate(angle + Math.PI / 2.0);
+ g2d.setTransform(at);
+ g2d.drawString(as.getIterator(), 0.0F, 0.0F);
+
+ String brightnessText = getLabel("Xoetrope.brightness",
+ "Brightness");
+ as = new AttributedString(brightnessText);
+ as.addAttribute(TextAttribute.FAMILY, fontFamily);
+ as.addAttribute(TextAttribute.SIZE,
+ (float) (ringThickness / 1.3));
+ as.addAttribute(TextAttribute.FOREGROUND, UIManager
+ .getColor("Label.foreground"));
+ at = ((AffineTransform) identityTransform.clone());
+ at.translate((width - fontHeight), (ringThickness
+ * brightnessText.length() / 2.3));
+ at.rotate(angle + Math.PI / 2.0);
+ g2d.setTransform(at);
+ g2d.drawString(as.getIterator(), 0.0F, 0.0F);
+ }
+ g2d.setTransform(identityTransform);
+
+ if (showRollovers) {
+ g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ if (rolloverPath != null) {
+ g2d.setColor(rolloverColor);
+ g2d.setStroke(new BasicStroke(1.5F));
+ g2d.draw(rolloverPath);
+ }
+ if (selectedPath != null) {
+ g2d.setColor(selectedColor);
+ g2d.setStroke(new BasicStroke(1.0F));
+ g2d.draw(selectedPath);
+ }
+ }
+ }
+ }
+
+ // End ColorWheel inner class
+ // ------------------------------------------------
+
+ // ----------------------------------------------------------------------------
+ private class ColorDocumentListener implements DocumentListener {
+ private JTextField originator;
+
+ private static final String MARKER = "Xoetrope.XUI.ColorWheel.DocumentEvent";
+
+ public ColorDocumentListener(JTextField originator) {
+ this.originator = originator;
+ }
+
+ /**
+ * This method is called after an insert into the document
+ */
+ @Override
+ public void insertUpdate(DocumentEvent evt) {
+ synchronize(evt);
+ }
+
+ /**
+ * This method is called after a removal from the document
+ */
+ @Override
+ public void removeUpdate(DocumentEvent evt) {
+ synchronize(evt);
+ }
+
+ /**
+ * This method is called after one or more attributes have changed. This
+ * method is not called when characters are inserted with attributes.
+ */
+ @Override
+ public void changedUpdate(DocumentEvent evt) {
+ synchronize(evt);
+ }
+
+ public void synchronize(DocumentEvent evt) {
+ boolean _hasAllValues = true;
+ if (hueEdit.getText().length() == 0)
+ _hasAllValues = false;
+ if (brightEdit.getText().length() == 0)
+ _hasAllValues = false;
+ if (satEdit.getText().length() == 0)
+ _hasAllValues = false;
+
+ final boolean hasAllValues = _hasAllValues;
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ useWebColors.setEnabled(hasAllValues);
+ decimalRGB.setEnabled(hasAllValues);
+ }
+ });
+
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // the below use of client property is to prevent
+ // infinite event loop (resetColor evetually changes
+ // the text boxes)
+ if (hasAllValues && originator.hasFocus()) {
+ if (Boolean.TRUE.equals(originator
+ .getClientProperty(MARKER))) {
+ originator.putClientProperty(MARKER, null);
+ } else {
+ originator.putClientProperty(MARKER, Boolean.TRUE);
+ resetColor();
+ }
+ }
+ // repaint for synchronizing the hue marker
+ if (displayScheme)
+ imagePicker.repaint();
+ }
+ });
+ }
+ }
+
+ // ----------------------------------------------------------------------------
+ public static void setLabelBundle(ResourceBundle labelBundle) {
+ ColorWheelPanel.labelBundle = labelBundle;
+ }
+
+ private static String getLabel(String labelName, String defaultValue) {
+ if (ColorWheelPanel.labelBundle == null)
+ return defaultValue;
+
+ try {
+ return ColorWheelPanel.labelBundle.getString(labelName);
+ } catch (MissingResourceException mre) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Show a popup menu with the list of system colors
+ *
+ * @param p
+ * the location to display the popup
+ */
+ private void showSystemColorList(Point p) {
+ JPopupMenu popupMenu = new JPopupMenu();
+ String[] systemColors = { "activeCaption", "desktop",
+ "activeCaptionText", "activeCaptionBorder", "inactiveCaption",
+ "inactiveCaptionText", "inactiveCaptionBorder", "window",
+ "windowBorder", "windowText", "menu", "menuText", "text",
+ "textText", "textHighlight", "textHighlightText",
+ "textInactiveText", "control", "controlText",
+ "controlHighlight", "controlLtHighlight", "controlShadow",
+ "controlDkShadow", "scrollbar", "info", "infoText", "white",
+ "lightGray", "gray", "darkGray", "black", "red", "pink",
+ "orange", "yellow", "green", "magenta", "cyan", "blue" };
+
+ for (int i = 0; i < systemColors.length; i++) {
+ if (systemColors[i].equals("white")) {
+ popupMenu.addSeparator();
+ continue;
+ }
+
+ JMenuItem mi = new JMenuItem(systemColors[i]);
+ mi.addActionListener(this);
+
+ BufferedImage image = new BufferedImage(8, 8,
+ BufferedImage.TYPE_INT_RGB);
+ Graphics g = image.getGraphics();
+ g.setColor(getSystemColor(systemColors[i]));
+ g.fillRect(0, 0, 8, 8);
+ g.setColor(SystemColor.windowBorder);
+ g.drawRect(0, 0, 7, 7);
+ g.dispose();
+ Icon icon = new ImageIcon(image);
+ mi.setIcon(icon);
+ popupMenu.add(mi);
+ }
+
+ popupMenu.show(this, p.x, p.y);
+ }
+
+ /**
+ * Get the named system color
+ *
+ * @param temp
+ * the color name
+ * @return the color value or null if the name is not recognized
+ */
+ public Color getSystemColor(String temp) {
+ Color clr = null;
+ if (temp.equals("activeCaption"))
+ clr = SystemColor.activeCaption;
+ else if (temp.equals("desktop"))
+ clr = SystemColor.desktop;
+ else if (temp.equals("activeCaptionText"))
+ clr = SystemColor.activeCaptionText;
+ else if (temp.equals("activeCaptionBorder"))
+ clr = SystemColor.activeCaptionBorder;
+ else if (temp.equals("inactiveCaption"))
+ clr = SystemColor.inactiveCaption;
+ else if (temp.equals("inactiveCaptionText"))
+ clr = SystemColor.inactiveCaptionText;
+ else if (temp.equals("inactiveCaptionBorder"))
+ clr = SystemColor.inactiveCaptionBorder;
+ else if (temp.equals("window"))
+ clr = SystemColor.window;
+ else if (temp.equals("windowBorder"))
+ clr = SystemColor.windowBorder;
+ else if (temp.equals("windowText"))
+ clr = SystemColor.windowText;
+ else if (temp.equals("menu"))
+ clr = SystemColor.menu;
+ else if (temp.equals("menuText"))
+ clr = SystemColor.menuText;
+ else if (temp.equals("text"))
+ clr = SystemColor.text;
+ else if (temp.equals("textText"))
+ clr = SystemColor.textText;
+ else if (temp.equals("textHighlight"))
+ clr = SystemColor.textHighlight;
+ else if (temp.equals("textHighlightText"))
+ clr = SystemColor.textHighlightText;
+ else if (temp.equals("textInactiveText"))
+ clr = SystemColor.textInactiveText;
+ else if (temp.equals("control"))
+ clr = SystemColor.control;
+ else if (temp.equals("controlText"))
+ clr = SystemColor.controlText;
+ else if (temp.equals("controlHighlight"))
+ clr = SystemColor.controlHighlight;
+ else if (temp.equals("controlLtHighlight"))
+ clr = SystemColor.controlLtHighlight;
+ else if (temp.equals("controlShadow"))
+ clr = SystemColor.controlShadow;
+ else if (temp.equals("controlDkShadow"))
+ clr = SystemColor.controlDkShadow;
+ else if (temp.equals("scrollbar"))
+ clr = SystemColor.scrollbar;
+ else if (temp.equals("info"))
+ clr = SystemColor.info;
+ else if (temp.equals("infoText"))
+ clr = SystemColor.infoText;
+
+ else if (temp.equals("white"))
+ clr = Color.white;
+ else if (temp.equals("lightGray"))
+ clr = Color.lightGray;
+ else if (temp.equals("gray"))
+ clr = Color.gray;
+ else if (temp.equals("darkGray"))
+ clr = Color.darkGray;
+ else if (temp.equals("black"))
+ clr = Color.black;
+ else if (temp.equals("red"))
+ clr = Color.red;
+ else if (temp.equals("pink"))
+ clr = Color.pink;
+ else if (temp.equals("orange"))
+ clr = Color.orange;
+ else if (temp.equals("yellow"))
+ clr = Color.yellow;
+ else if (temp.equals("green"))
+ clr = Color.green;
+ else if (temp.equals("magenta"))
+ clr = Color.magenta;
+ else if (temp.equals("cyan"))
+ clr = Color.green;
+ else if (temp.equals("blue"))
+ clr = Color.blue;
+
+ return clr;
+ }
+
+ /**
+ * Should the color wheeel's colors be adjusted
+ *
+ * @return true if the colors should change to match the brightness and
+ * saturation
+ */
+ private boolean shouldAdjustWheel() {
+ if (adjustWheel == NEVER_ADJUST)
+ return false;
+ else if (adjustWheel == ALWAYS_ADJUST)
+ return true;
+ else if (ctrlKeyDown)
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Get the adjust color wheel flag.
+ *
+ * @return the adjustment mode
+ */
+ public int getAdjustWheel() {
+ return adjustWheel;
+ }
+
+ /**
+ * Set the adjust color wheel flag.
+ *
+ * @param state
+ * the color wheel's new adjustment mode ( CTRL_ADJUST |
+ * ALWAYS_ADJUST | NEVER_ADJUST );
+ */
+ public void setAdjustWheel(int state) {
+ adjustWheel = state;
+ }
+
+ /**
+ * Get the adjust rollover color flag.
+ *
+ * @return the adjustment mode
+ */
+ public boolean getRollover() {
+ return adjustRollover;
+ }
+
+ /**
+ * Set the adjust rollover color flag.
+ *
+ * @param state
+ * the rollover's new adjustment mode ( true | false );
+ */
+ public void setRollover(boolean state) {
+ adjustRollover = state;
+ }
+
+ /**
+ * Reset the brightness and saturation multipliers for the ColorWheel.
+ */
+ public void resetColorWheel() {
+ saturationMultipler = brightnessMultipler = 1.0;
+ resetColor();
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/contrib/xoetrope/editor/color/ModelColor.java b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/xoetrope/editor/color/ModelColor.java
new file mode 100644
index 0000000..6b7f8d7
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/contrib/xoetrope/editor/color/ModelColor.java
@@ -0,0 +1,409 @@
+package org.pushingpixels.substance.internal.contrib.xoetrope.editor.color;
+
+import java.awt.Color;
+
+/**
+ * A RYB Color model $Revision: 2254 $
+ */
+public class ModelColor {
+ public static final int NUM_COLOR_RINGS = 7;
+ public static final int NUM_SEGMENTS = 24;
+ protected boolean webSnap = false;
+
+ private static ModelColor[][] baseColors;
+
+ protected int R, G, B;
+ protected double H, S, V;
+ protected double rgbMinValue, rgbMaxValue;
+
+ public static double[] SATURATION_BANDS = { 0.4, 0.6, 0.8, 1.0, 1.0, 1.0,
+ 1.0 };
+ public static double[] BRIGHTNESS_BANDS = { 1.0, 1.0, 1.0, 1.0, 1.0, 0.85,
+ 0.65, 0.45 };
+
+ /**
+ * Create a new ModelColor
+ *
+ * @param R
+ * the red value in the range 0-255
+ * @param G
+ * the green value in the range 0-255
+ * @param B
+ * the blue value in the range 0-255
+ */
+ public ModelColor(int R, int G, int B) {
+ this(R, G, B, -1);
+ }
+
+ public ModelColor(double H, double S, double V) {
+ setHSV(H, S, V);
+ }
+
+ /**
+ * Create a new ModelColor
+ *
+ * @param R
+ * the red value in the range 0-255
+ * @param G
+ * the green value in the range 0-255
+ * @param B
+ * the blue value in the range 0-255
+ * @param H
+ * the hue value in the range 0-255
+ */
+ public ModelColor(int R, int G, int B, int H) {
+ initColorWheel();
+ setRGB(R, G, B, H);
+ }
+
+ /**
+ * Creates a copy of an ModelColor
+ *
+ * @param c
+ * the ModelColor to copy
+ */
+ public ModelColor(ModelColor c) {
+ initColorWheel();
+ R = c.R;
+ G = c.G;
+ B = c.B;
+
+ H = c.H;
+ S = c.S;
+ V = c.V;
+ getRgbMinMaxValues();
+ }
+
+ /**
+ * Get the red value
+ *
+ * @return the red value in the range 0-255
+ */
+ public int getRed() {
+ return R;
+ }
+
+ /**
+ * Get the green value
+ *
+ * @return the green value in the range 0-255
+ */
+ public int getGreen() {
+ return G;
+ }
+
+ /**
+ * Get the blue value
+ *
+ * @return the blue value in the range 0-255
+ */
+ public int getBlue() {
+ return B;
+ }
+
+ /**
+ * Get the hue value
+ *
+ * @return the hue value in the range 0-255
+ */
+ public int getHue() {
+ return (int) Math.floor(H + 0.5);
+ }
+
+ /**
+ * Get the hue value
+ *
+ * @return the hue value in the range 0-255
+ */
+ public double getH() {
+ return H;
+ }
+
+ /**
+ * Get the saturation value
+ *
+ * @return the saturation value in the range 0.0-1.0
+ */
+ public double getSaturation() {
+ return S;
+ }
+
+ /**
+ * Get the brightness value
+ *
+ * @return the brightness value in the range 0.0-1.0
+ */
+ public double getBrightness() {
+ return V;
+ }
+
+ /**
+ * Set an RGB color
+ *
+ * @param R
+ * the red value in the range 0-255
+ * @param G
+ * the green value in the range 0-255
+ * @param B
+ * the blue value in the range 0-255
+ */
+ public void setRGB(int R, int G, int B) {
+ setRGB(R, G, B, -1);
+ }
+
+ /**
+ * Set an RGB color
+ *
+ * @param r
+ * the red value in the range 0-255
+ * @param g
+ * the green value in the range 0-255
+ * @param b
+ * the blue value in the range 0-255
+ * @param h
+ * the hue value in the range 0-255
+ */
+ public void setRGB(int r, int g, int b, int h) {
+ if (webSnap) {
+ R = Math.round(r / 51) * 51;
+ G = Math.round(g / 51) * 51;
+ B = Math.round(b / 51) * 51;
+ } else {
+ R = r;
+ G = g;
+ B = b;
+ }
+
+ checkRange();
+ getRgbMinMaxValues();
+ if (h > -1) {
+ H = h;
+ S = (rgbMaxValue > 0) ? (rgbMaxValue - rgbMinValue) / rgbMaxValue
+ : 0;
+ V = rgbMaxValue / 255;
+ } else if ((R == G) && (G == B)) {
+ H = 0;
+ S = 0;
+ V = rgbMaxValue / 255;
+ } else
+ calculateHSVfromRGB();
+ }
+
+ /**
+ * Set the color to a HSV value
+ *
+ * @param _h
+ * the hue
+ * @param _s
+ * the saturation
+ * @param _v
+ * the value
+ */
+ public void setHSV(double _h, double _s, double _v) {
+ if (Math.abs(H - _h) > 0.499999)
+ H = _h;
+ if (Math.abs(S - _s) > 0.009999)
+ S = _s;
+ if (Math.abs(V - _v) > 0.009999)
+ V = _v;
+
+ if ((S > 1.0) || (S < 0.0))
+ S = (S < 0.0) ? 0.0 : 1.0;
+ if ((V > 1.0) || (V < 0.0))
+ V = (V < 0.0) ? 0.0 : 1.0;
+
+ calculateRGBfromHSV();
+ }
+
+ /**
+ * Check that the RGB values are in range
+ */
+ private void checkRange() {
+ if (R > 255)
+ R = 255;
+ if (G > 255)
+ G = 255;
+ if (B > 255)
+ B = 255;
+
+ if (webSnap) {
+ R = Math.round(R / 51) * 51;
+ G = Math.round(G / 51) * 51;
+ B = Math.round(B / 51) * 51;
+ }
+ }
+
+ /**
+ * Get the minimum and maximum of the RGB values.
+ */
+ private void getRgbMinMaxValues() {
+ rgbMaxValue = Math.max(Math.max(R, G), B);
+ rgbMinValue = Math.min(Math.min(R, G), B);
+ }
+
+ private void calculateHSVfromRGB() {
+ // Find the two base colors
+ boolean exactMatch = false;
+ outer: for (int j = 0; j < NUM_COLOR_RINGS; j++) {
+ if (j == 6)
+ j = 6;
+ for (int i = 0; i < 360; i += 15) {
+ ModelColor c = baseColors[i / 15][j];
+ if ((R == c.R) && (G == c.G) && (B == c.B)) {
+ exactMatch = true;
+ H = c.H;
+ S = (rgbMaxValue > 0) ? 1.0 * (rgbMaxValue - rgbMinValue)
+ / rgbMaxValue : 0.0;
+ V = rgbMaxValue / 255.0;
+ break outer;
+ }
+ }
+ }
+
+ if (!exactMatch) {
+ // Red-Green-Blue to Red-Yellow-Blue
+ // degrees
+ // R = 0 R = 0
+ // Y = 60 Y = 120
+ // G = 120 G = 180
+ // B == 240 B = 240
+ // Fractional values = degrees / 360.0
+ float values[] = new float[3];
+ values = Color.RGBtoHSB(R, G, B, values);
+ float h = values[0];
+ S = (rgbMaxValue > 0) ? 1.0 * (rgbMaxValue - rgbMinValue)
+ / rgbMaxValue : 0.0;
+ V = rgbMaxValue / 255.0;
+ if (h < 0.1666668f)
+ h *= 2.0f;
+ else if (h < 0.3333334f)
+ h += 0.1666667f;
+ else if (h < 0.6666668f)
+ h = h + 0.1666667f * ((0.6666667f - h) / 0.3333334f);
+ H = 360.0 * h;
+ }
+ }
+
+ private void calculateRGBfromHSV() {
+ // Red-Green-Blue to Red-Yellow-Blue
+ // degrees degrees
+ // R = 0 R = 0
+ // Y = 60 Y = 120
+ // G = 120 G = 180
+ // B == 240 B = 240
+ // Fractional values = degrees / 360.0
+ double h = H / 360.0;
+ if (h < 0.3333334)
+ h /= 2.0;
+ else if (h < 0.5000001)
+ h -= 0.1666667;
+ else if (h < 0.6666668)
+ h = 2.0 * h - 0.6666667;
+
+ Color c = new Color(Color.HSBtoRGB((float) h, (float) S, (float) V));
+ R = c.getRed();
+ G = c.getGreen();
+ B = c.getBlue();
+
+ checkRange();
+ getRgbMinMaxValues();
+ }
+
+ /**
+ * Rotate this color by a specified amount
+ *
+ * @param angle
+ * the angle by which to move this color (hue)
+ */
+ public void rotate(int angle) {
+ if ((S > 0) && (V > 0)) {
+ double newHue = (H + angle) % 360.0;
+ setHSV(newHue, S, V);// S * S1 / S2, V * V1 / V2 );
+ }
+ }
+
+ /**
+ * Set the snap to web colors setting
+ *
+ * @param snapTo
+ * true to snap to web colors
+ */
+ public void setWebSnap(boolean snapTo) {
+ webSnap = snapTo;
+ }
+
+ /**
+ * Convert a decimal rgb value to a hexadecimal value
+ *
+ * @param n
+ * the decimal value
+ * @return the hex value
+ */
+ public static String dec2hex(int n) {
+ String s = Integer.toHexString(n);
+ if (s.length() < 2)
+ s = "0" + s;
+ return s.toUpperCase();
+ }
+
+ /**
+ * Convert a hexadecimal rgb value to a decimal value
+ *
+ * @param n
+ * the hex value
+ * @return the decimal value
+ */
+ public static int hex2dec(String n) {
+ if (n.length() == 0)
+ return 0;
+ return Integer.parseInt(n, 16);
+ }
+
+ /**
+ * Get a two letter hex string for the decimal value in the range 0-255
+ */
+ public static String toHexString(int value) {
+ String hex = Integer.toHexString(value).toUpperCase();
+ if (hex.length() < 2)
+ return "0" + hex;
+
+ return hex;
+ }
+
+ /**
+ * Get a grayscale hex value for an rgb color
+ *
+ * @param r
+ * the red value
+ * @param g
+ * the green value
+ * @param b
+ * the blue value
+ * @return the hex value
+ */
+ public static String col2Gray(double r, double g, double b) {
+ double lum = Math.round(r * 0.299 + g * 0.587 + b * 0.114);
+ return dec2hex((int) lum) + dec2hex((int) lum) + dec2hex((int) lum);
+ }
+
+ public static ModelColor[][] getBaseColors() {
+ initColorWheel();
+ return baseColors;
+ }
+
+ private static void initColorWheel() {
+ if (baseColors == null) {
+ baseColors = new ModelColor[24][NUM_COLOR_RINGS];
+
+ int idx = 0;
+ for (int i = 0; i < NUM_COLOR_RINGS; i++) {
+ for (int j = 0; j < 24; j++) {
+ baseColors[j][i] = new ModelColor(15.0 * j,
+ SATURATION_BANDS[i], BRIGHTNESS_BANDS[i + 1
+ - (j % 2)]);
+ idx++;
+ }
+ }
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/fonts/DefaultGnomeFontPolicy.java b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/DefaultGnomeFontPolicy.java
new file mode 100644
index 0000000..9d336cf
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/DefaultGnomeFontPolicy.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.fonts;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.lang.reflect.Method;
+import java.util.StringTokenizer;
+
+import javax.swing.UIDefaults;
+import javax.swing.plaf.FontUIResource;
+
+import org.pushingpixels.substance.api.fonts.FontPolicy;
+import org.pushingpixels.substance.api.fonts.FontSet;
+
+/**
+ * The default font policy for Gnome desktops.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DefaultGnomeFontPolicy implements FontPolicy {
+ /**
+ * Font scale.
+ */
+ private static double fontScale;
+
+ static {
+ GraphicsEnvironment ge = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsConfiguration gc = ge.getDefaultScreenDevice()
+ .getDefaultConfiguration();
+ AffineTransform at = gc.getNormalizingTransform();
+ fontScale = at.getScaleY();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.fonts.FontPolicy#getFontSet(java.lang.String,
+ * javax.swing.UIDefaults)
+ */
+ @Override
+ public FontSet getFontSet(String lafName, UIDefaults table) {
+ Object defaultGtkFontName = Toolkit.getDefaultToolkit()
+ .getDesktopProperty("gnome.Gtk/FontName");
+ String family = "";
+ int style = Font.PLAIN;
+ int size = 10;
+ if (defaultGtkFontName instanceof String) {
+ String pangoName = (String) defaultGtkFontName;
+ StringTokenizer tok = new StringTokenizer(pangoName);
+ while (tok.hasMoreTokens()) {
+ String word = tok.nextToken();
+ boolean allDigits = true;
+ for (int i = 0; i < word.length(); i++) {
+ if (!Character.isDigit(word.charAt(i))) {
+ allDigits = false;
+ break;
+ }
+ }
+
+ if (word.equalsIgnoreCase("italic")) {
+ style |= Font.ITALIC;
+ } else if (word.equalsIgnoreCase("bold")) {
+ style |= Font.BOLD;
+ } else if (allDigits) {
+ try {
+ size = Integer.parseInt(word);
+ } catch (NumberFormatException nfe) {
+ size = 10;
+ }
+ } else {
+ if (family.length() > 0) {
+ family += " ";
+ }
+ family += word;
+ }
+ }
+ }
+
+ double dsize = size * getPointsToPixelsRatio();
+
+ size = (int) (dsize + 0.5);
+ if (size < 1) {
+ size = 1;
+ }
+
+ if (family.length() == 0)
+ family = "sans";
+ // Font controlFont = new Font(family, style, size);
+
+ Font controlFont = null;
+ // make some black magic with sun-private classes
+ // to better map the logical font name (such as sans)
+ // to an actual font (such as DejaVu Sans).
+ String fcFamilyLC = family.toLowerCase();
+ try {
+ Class fontManagerClass = Class.forName("sun.font.FontManager");
+ Method mapFcMethod = fontManagerClass.getMethod("mapFcName",
+ new Class[] { String.class });
+ Object mapFcMethodResult = mapFcMethod.invoke(null, fcFamilyLC);
+ if (mapFcMethodResult != null) {
+ Method getFontConfigFUIRMethod = fontManagerClass.getMethod(
+ "getFontConfigFUIR", new Class[] { String.class,
+ int.class, int.class });
+ controlFont = (Font) getFontConfigFUIRMethod.invoke(null,
+ fcFamilyLC, style, size);
+ } else {
+ Font font = new FontUIResource(family, style, size);
+ Method getCompositeFontUIResourceMethod = fontManagerClass
+ .getMethod("getCompositeFontUIResource",
+ new Class[] { Font.class });
+ controlFont = (Font) getCompositeFontUIResourceMethod.invoke(
+ null, font);
+ }
+ } catch (Throwable t) {
+ controlFont = new Font(family, style, size);
+ }
+
+ return FontSets.createDefaultFontSet(controlFont);
+ }
+
+ public static double getPointsToPixelsRatio() {
+ // for details behind the computations, look in
+ // com.sun.java.swing.plaf.gtk.PangoFonts
+ int dpi = 96;
+ Object value = Toolkit.getDefaultToolkit().getDesktopProperty(
+ "gnome.Xft/DPI");
+ if (value instanceof Integer) {
+ dpi = (Integer) value / 1024;
+ if (dpi == -1) {
+ dpi = 96;
+ }
+ if (dpi < 50) {
+ dpi = 50;
+ }
+ return dpi / 72.0;
+ } else {
+ return fontScale;
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/fonts/DefaultKDEFontPolicy.java b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/DefaultKDEFontPolicy.java
new file mode 100644
index 0000000..6e3140f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/DefaultKDEFontPolicy.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.fonts;
+
+import java.awt.Font;
+import java.io.*;
+import java.util.regex.Pattern;
+
+import javax.swing.UIDefaults;
+
+import org.pushingpixels.substance.api.fonts.FontPolicy;
+import org.pushingpixels.substance.api.fonts.FontSet;
+
+/**
+ * The default font policy for KDE desktops.
+ *
+ * @author <a href="mailto:paranoid.tiberiumlabs at gmail.com">Paranoid</a>
+ */
+public class DefaultKDEFontPolicy implements FontPolicy {
+
+ /**
+ * method to check if current user session is KDE
+ *
+ * @return true
+ * if KDE session is currently running.
+ */
+ public static boolean isKDERunning() {
+ // KDE_FULL_SESSION=true
+ return "true".equals(System.getenv("KDE_FULL_SESSION"));
+ }
+
+ /**
+ * Checks for KDE4 flags in current env. There are few possible flags:<br/>
+ * {@code KDE_SESSION_VERSION=4<br/> DESKTOP_SESSION=kde4<br/>} If distro
+ * for some readon don't set this flags - KDE3 is possibli
+ * running.<br/><br/>
+ *
+ * There is one more way to determine current KDE version. We can run
+ * {@code "konsole --version"} command and parse output, but KDE3 don't have
+ * own env flags, so this command will run every time we using KDE3.
+ *
+ * @return {@code true} if KDE4 env flags found, {@code false} otherwise.
+ */
+ private static boolean isKDE4Running() {
+ if (!isKDERunning()) {
+ throw new IllegalStateException("KDE is not running");
+ }
+ return "4".equals(System.getenv("KDE_SESSION_VERSION"))
+ || "kde4".equals(System.getenv("DESKTOP_SESSION"));
+ }
+
+ private static final String SANS_SERIF = "SansSerif";
+ private static FontSet fontSet = null;
+
+ @Override
+ public synchronized FontSet getFontSet(String lafName, UIDefaults table) {
+ if (fontSet == null) {
+ fontSet = getInternalFontSet(lafName, table);
+ }
+ return fontSet;
+ }
+
+ private FontSet getInternalFontSet(String lafName, UIDefaults table) {
+
+ // size is the most important, then family and then style
+ int commonSize = 10;
+ int menuSize = 10;
+ int titleSize = 10;
+ int commonStyle = Font.PLAIN;
+ int menuStyle = Font.PLAIN;
+ int titleStyle = Font.BOLD;
+ String commonFamily = SANS_SERIF;
+ String menuFamily = SANS_SERIF;
+ String titleFamily = SANS_SERIF;
+
+ // size is the most important, then family and then style
+
+ /*
+ * KDE4 can set common, menu, small and window title, while KDE3 can set
+ * only common, menu and window title. But in KDE4 config files small
+ * font is named "smallestReadableFont"
+ */
+
+ // <editor-fold defaultstate="collapsed" desc=" reading fonts ">
+ try {
+
+ String kdeglobals = getFileContent("kdeglobals");
+
+ Pattern pattern = Pattern.compile(",");
+
+ try {
+
+ String fontParam = getIniParam(kdeglobals, "[General]", "font");
+ String[] fontParams = pattern.split(fontParam);
+ commonFamily = fontParams[0];
+ commonSize = Integer.parseInt(fontParams[1]);
+ boolean bold = "75".equals(fontParams[4]);
+ boolean italic = "1".equals(fontParams[5]);
+ if (bold && italic) {
+ commonStyle = Font.BOLD + Font.ITALIC;
+ } else if (italic) {
+ commonStyle = Font.ITALIC;
+ } else if (bold) {
+ commonStyle = Font.BOLD;
+ } else {
+ commonStyle = Font.PLAIN;
+ }
+
+ } catch (Exception commonReadException) {
+ commonFamily = SANS_SERIF;
+ commonSize = 10;
+ commonStyle = Font.PLAIN;
+ }
+
+ try {
+
+ String menuFontParam = getIniParam(kdeglobals, "[General]",
+ "menuFont");
+ String[] menuFontParams = pattern.split(menuFontParam);
+ menuFamily = menuFontParams[0];
+ menuSize = Integer.parseInt(menuFontParams[1]);
+ boolean bold = "75".equals(menuFontParams[4]);
+ boolean italic = "1".equals(menuFontParams[5]);
+ if (bold && italic) {
+ menuStyle = Font.BOLD + Font.ITALIC;
+ } else if (italic) {
+ menuStyle = Font.ITALIC;
+ } else if (bold) {
+ menuStyle = Font.BOLD;
+ } else {
+ menuStyle = Font.PLAIN;
+ }
+
+ } catch (Exception menuReadException) {
+ menuFamily = SANS_SERIF;
+ menuSize = 10;
+ menuStyle = Font.PLAIN;
+ }
+
+ try {
+
+ String activeFontParam = getIniParam(kdeglobals, "[WM]",
+ "activeFont");
+ String[] activeFontParams = pattern.split(activeFontParam);
+ titleFamily = activeFontParams[0];
+ titleSize = Integer.parseInt(activeFontParams[1]);
+ boolean bold = "75".equals(activeFontParams[4]);
+ boolean italic = "1".equals(activeFontParams[5]);
+ if (bold && italic) {
+ titleStyle = Font.BOLD + Font.ITALIC;
+ } else if (italic) {
+ titleStyle = Font.ITALIC;
+ } else if (bold) {
+ titleStyle = Font.BOLD;
+ } else {
+ titleStyle = Font.PLAIN;
+ }
+
+ } catch (Exception titleReadException) {
+ titleFamily = SANS_SERIF;
+ titleSize = 10;
+ titleStyle = Font.BOLD;
+ }
+
+ } catch (Exception kdeglobalsReadException) {
+
+ commonFamily = SANS_SERIF;
+ commonSize = 10;
+ commonStyle = Font.PLAIN;
+
+ menuFamily = SANS_SERIF;
+ menuSize = 10;
+ menuStyle = Font.PLAIN;
+
+ titleFamily = SANS_SERIF;
+ titleSize = 10;
+ titleStyle = Font.BOLD;
+
+ }
+
+ // </editor-fold>
+
+ // <editor-fold defaultstate="collapsed" desc=" dpi settings ">
+
+ double dcommonSize = commonSize;
+ double dmenuSize = menuSize;
+ double dtitleSize = titleSize;
+
+ int dpi;
+ try {
+ String dpiParam = getIniParam(getFileContent("kcmfonts"),
+ "[General]", "forceFontDPI");
+ dpi = Integer.parseInt(dpiParam);
+ } catch (Exception dpiReadException) {
+ dpi = 96;
+ }
+ // kcmfonts common for both KDE3 and KDE4
+ if (dpi <= 0) {
+ dpi = 96;
+ }
+ if (dpi < 50) {
+ dpi = 50;
+ }
+ dcommonSize = ((commonSize * dpi) / 72.0);
+ dmenuSize = ((menuSize * dpi) / 72.0);
+ dtitleSize = ((titleSize * dpi) / 72.0);
+
+ commonSize = (int) (dcommonSize + 0.5);
+ if (commonSize < 1) {
+ commonSize = 1;
+ }
+
+ menuSize = (int) (dmenuSize + 0.5);
+ if (menuSize < 1) {
+ menuSize = 1;
+ }
+
+ titleSize = (int) (dtitleSize + 0.5);
+ if (titleSize < 1) {
+ titleSize = 1;
+ }
+
+ // </editor-fold>
+
+ Font commonFont = new Font(commonFamily, commonStyle, commonSize);
+ Font menuFont = new Font(menuFamily, menuStyle, menuSize);
+ Font titleFont = new Font(titleFamily, titleStyle, titleSize);
+
+ return FontSets.createDefaultFontSet(commonFont, menuFont, commonFont,
+ commonFont, commonFont, titleFont);
+ }
+
+ private String getIniParam(String content, String category, String param)
+ throws Exception {
+ // checking if our param in our category - we don't need same params
+ // from other categories
+ int categoryIndex = content.indexOf(category);
+
+ // fix for defect 483 - https://substance.dev.java.net/issues/show_bug.cgi?id=483
+ // lineSeparator +
+ String lineSeparator = System.getProperty("line.separator");// JDK7: System.lineSeparator()
+ int nextCategoryIndex = content.indexOf(lineSeparator + '[', categoryIndex + 1);
+
+ if (nextCategoryIndex < 0) {
+ nextCategoryIndex = content.length();
+ }
+ int paramIndex = content.indexOf(param, categoryIndex);
+ if (paramIndex >= 0 && paramIndex < nextCategoryIndex) {
+
+ // now paramString contains full string of our parameter
+ int lineEnd = content.indexOf(lineSeparator,
+ paramIndex);
+ if (lineEnd <= 0) {
+ lineEnd = content.length();
+ }
+ String paramString = content.substring(paramIndex, lineEnd);
+
+ // getting just our value, we don't need other symbols
+ int equalityIndex = paramString.indexOf('=');
+ /*
+ * paramString.indexOf('#'); paramString.indexOf(';');
+ *
+ * don't know do we need to remove trailing comments after this
+ * symbols? have nothing similar in my system...
+ */
+ String result = paramString.substring(equalityIndex + 1).trim();
+ if (result.length() > 0) {
+ return result;
+ }
+ }
+ throw new Exception("No such param in current category");
+ }
+
+ private String getFileContent(String fileName) throws IOException {
+
+ /*
+ * preparing KDE home folder. KDE4 may be stored in .kde4 folder, and
+ * may be in .kde.
+ *
+ * we check for .kde4 folder, if it exists - we will use it. Otherwise
+ * we will left kdeHome empty, and it will be set to .kde in both cases
+ * of KDE3 and KDE4 with .kde folder.
+ */
+ String userHome = System.getProperty("user.home");
+ String kdeHome = null;
+ if (isKDE4Running()) {
+ File kdeHomeFolder = new File(userHome, ".kde4");
+ if (kdeHomeFolder.exists()) {
+ kdeHome = ".kde4";
+ }
+ }
+ if (kdeHome == null) {
+ kdeHome = ".kde";
+ }
+
+ char fs = File.separatorChar;
+ fileName = userHome + fs + kdeHome + "/share/config/" + fileName;
+ if (fs != '/') {
+ fileName = fileName.replace('/', fs);
+ }
+
+ // creating file from user home, using system's file separator:
+ BufferedReader in = new BufferedReader(new FileReader(fileName));
+ StringBuilder sb = new StringBuilder();
+ // size same as inside BufferedReader code
+ char[] buffer = new char[8192];
+ int read = 0;
+ while ((read = in.read(buffer)) >= 0) {
+ sb.append(buffer, 0, read);
+ }
+ in.close();
+
+ return sb.toString();
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/fonts/DefaultMacFontPolicy.java b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/DefaultMacFontPolicy.java
new file mode 100644
index 0000000..f87e3c0
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/DefaultMacFontPolicy.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.fonts;
+
+import java.awt.Font;
+
+import javax.swing.UIDefaults;
+
+import org.pushingpixels.substance.api.fonts.FontPolicy;
+import org.pushingpixels.substance.api.fonts.FontSet;
+
+/**
+ * The default font policy for Mac desktops.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DefaultMacFontPolicy implements FontPolicy {
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.fonts.FontPolicy#getFontSet(java.lang.String,
+ * javax.swing.UIDefaults)
+ */
+ @Override
+ public FontSet getFontSet(String lafName, UIDefaults table) {
+ Font controlFont = new Font("Lucida Grande", Font.PLAIN, 13);
+ Font menuFont = table == null ? new Font("Lucida Grande", Font.PLAIN,
+ 14) : table.getFont("Menu.font");
+ Font titleFont = menuFont;
+ Font messageFont = table == null ? controlFont : table
+ .getFont("OptionPane.font");
+ Font smallFont = table == null ? controlFont.deriveFont(controlFont
+ .getSize2D() - 2f) : table.getFont("ToolTip.font");
+ Font windowTitleFont = table == null ? titleFont : table
+ .getFont("InternalFrame.titleFont");
+ return FontSets.createDefaultFontSet(controlFont, menuFont, titleFont,
+ messageFont, smallFont, windowTitleFont);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/fonts/FontPolicies.java b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/FontPolicies.java
new file mode 100644
index 0000000..6387583
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/FontPolicies.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (c) 2001-2006 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.substance.internal.fonts;
+
+import java.awt.Font;
+
+import javax.swing.UIDefaults;
+
+import org.pushingpixels.lafwidget.utils.LookUtils;
+import org.pushingpixels.substance.api.fonts.FontPolicy;
+import org.pushingpixels.substance.api.fonts.FontSet;
+
+/**
+ * Provides predefined FontPolicy implementations.
+ * <p>
+ *
+ * <strong>Note:</strong> The available policies work well on Windows. On other
+ * platforms the fonts specified by the runtime environment are chosen. I plan
+ * to provide more logic or options for other platforms, for example that a
+ * Linux system checks for a Tahoma or Segoe UI.
+ * <p>
+ *
+ * TODO: Add a check for a custom font policy set in the System properties.
+ * <p>
+ *
+ * TODO: Add policies that emulate different Windows setups: default XP on 96dpi
+ * with normal fonts ("XP-normal-96"), Vista on 120dpi with large fonts
+ * ("Vista-large-120"), etc.
+ *
+ * @author Karsten Lentzsch
+ *
+ * @see FontPolicy
+ * @see FontSet
+ * @see FontSets
+ * @see Fonts
+ *
+ * @since 2.0
+ */
+public final class FontPolicies {
+
+ private FontPolicies() {
+ // Override default constructor; prevents instantation.
+ }
+
+ // Getting a FontPolicy *********************************************
+
+ /**
+ * Returns a font policy that in turn always returns the specified FontSet.
+ * The FontSet will be fixed, but the FontSet itself may return different
+ * fonts in different environments.
+ *
+ * @param fontSet
+ * the FontSet to be return by this policy
+ * @return a font policy that returns the specified FontSet.
+ */
+ public static FontPolicy createFixedPolicy(FontSet fontSet) {
+ return new FixedPolicy(fontSet);
+ }
+
+ /**
+ * Returns a font policy that checks for a custom FontPolicy and a custom
+ * FontSet specified in the System settings or UIManager. If no custom
+ * settings are available, the given default policy will be used to look up
+ * the FontSet.
+ *
+ * @param defaultPolicy
+ * the policy used if there are no custom settings
+ * @return a FontPolicy that checks for custom settings before the default
+ * policy is returned.
+ */
+ public static FontPolicy customSettingsPolicy(FontPolicy defaultPolicy) {
+ return new CustomSettingsPolicy(defaultPolicy);
+ }
+
+ // /**
+ // * Returns the default platform independent font choice policy.<p>
+ // *
+ // * The current implementation just returns the logical fonts.
+ // * A future version shall check for available good fonts
+ // * and shall use them before it falls back to the logical fonts.
+ // *
+ // * @return the default platform independent font choice policy.
+ // */
+ // public static FontPolicy getDefaultCrossPlatformPolicy() {
+ // return new DefaultCrossPlatformPolicy();
+ // }
+
+ /**
+ * Returns the default font policy for Plastic on the Windows platform. It
+ * differs from the default Windows policy in that it uses a bold font for
+ * TitledBorders, titles, and titled separators.
+ *
+ * @return the default font policy for Plastic on the Windows platform.
+ */
+ public static FontPolicy getDefaultPlasticOnWindowsPolicy() {
+ return new DefaultPlasticOnWindowsPolicy();
+ }
+
+ /**
+ * Returns the default Plastic FontPolicy that may vary with the platform
+ * and environment. On Windows, the PlasticOnWindowsPolicy is returned that
+ * is much like the defualt WindowsPolicy but uses a bold title font. On
+ * other Platforms, the logical fonts policy is returned that uses the
+ * logical fonts as specified by the Java runtime environment.
+ *
+ * @return a Windows-like policy on Windows, a logical fonts policy on all
+ * other platforms
+ */
+ public static FontPolicy getDefaultPlasticPolicy() {
+ if (LookUtils.IS_OS_WINDOWS) {
+ return getDefaultPlasticOnWindowsPolicy();
+ }
+ return getLogicalFontsPolicy();
+ // return getDefaultCrossPlatformPolicy();
+ }
+
+ /**
+ * Returns the default font policy for the Windows platform. It aims to
+ * return a FontSet that is close to the native guidelines and useful for
+ * the current Java environment.
+ * <p>
+ *
+ * The control font scales with the platform screen resolution
+ * (96dpi/101dpi/120dpi/144dpi/...) and honors the desktop font settings
+ * (normal/large/extra large).
+ *
+ * @return the default font policy for the Windows platform.
+ */
+ public static FontPolicy getDefaultWindowsPolicy() {
+ return new DefaultWindowsPolicy();
+ }
+
+ /**
+ * Returns a font policy that returns the logical fonts as specified by the
+ * Java runtime environment.
+ *
+ * @return a font policy that returns logical fonts.
+ */
+ public static FontPolicy getLogicalFontsPolicy() {
+ return createFixedPolicy(FontSets.getLogicalFontSet());
+ }
+
+ /**
+ * Returns a font policy for getting a Plastic appearance that aims to be
+ * visual backward compatible with the JGoodies Looks version 1.x. It uses a
+ * font choice similar to the choice implemented by the Plastic L&fs in
+ * the JGoodies Looks version 1.x.
+ *
+ * @return a font policy that aims to reproduce the Plastic font choice in
+ * the JGoodies Looks 1.x.
+ */
+ public static FontPolicy getLooks1xPlasticPolicy() {
+ Font controlFont = Fonts.getDefaultGUIFontWesternModernWindowsNormal();
+ Font menuFont = controlFont;
+ Font titleFont = controlFont.deriveFont(Font.BOLD);
+ FontSet fontSet = FontSets.createDefaultFontSet(controlFont, menuFont,
+ titleFont);
+ return createFixedPolicy(fontSet);
+ }
+
+ /**
+ * Returns a font policy for getting a Windows appearance that aims to be
+ * visual backward compatible with the JGoodies Looks version 1.x. It uses a
+ * font choice similar to the choice implemented by the Windows L&f in
+ * the JGoodies Looks version 1.x.
+ *
+ * @return a font policy that aims to reproduce the Windows font choice in
+ * the JGoodies Looks 1.x.
+ */
+ public static FontPolicy getLooks1xWindowsPolicy() {
+ return new Looks1xWindowsPolicy();
+ }
+
+ /**
+ * Returns a font policy intended for API users that want to move Plastic
+ * code from the Looks 1.x to the Looks 2.0. On Windows, it uses the Looks
+ * 2.0 Plastic fonts, on other platforms it uses the Looks 1.x Plastic
+ * fonts.
+ *
+ * @return the recent Plastic font policy on Windows, the JGoodies Looks 1.x
+ * on other Platforms.
+ */
+ public static FontPolicy getTransitionalPlasticPolicy() {
+ return LookUtils.IS_OS_WINDOWS ? getDefaultPlasticOnWindowsPolicy()
+ : getLooks1xPlasticPolicy();
+ }
+
+ // Utility Methods ********************************************************
+
+ /**
+ * Looks up and returns a custom FontSet for the given Look&Feel name,
+ * or <code>null</code> if no custom font set has been defined for this
+ * Look&Feel.
+ *
+ * @param lafName
+ * the name of the Look&Feel, one of <code>"Plastic"</code> or
+ * <code>"Windows"</code>
+ * @return a custom FontPolicy - if any - or otherwise <code>null</code>
+ */
+ private static FontSet getCustomFontSet(String lafName) {
+ String controlFontKey = lafName + ".controlFont";
+ String menuFontKey = lafName + ".menuFont";
+ String decodedControlFont = LookUtils.getSystemProperty(controlFontKey);
+ if (decodedControlFont == null)
+ return null;
+ Font controlFont = Font.decode(decodedControlFont);
+ String decodedMenuFont = LookUtils.getSystemProperty(menuFontKey);
+ Font menuFont = decodedMenuFont != null ? Font.decode(decodedMenuFont)
+ : null;
+ Font titleFont = "Plastic".equals(lafName) ? controlFont
+ .deriveFont(Font.BOLD) : controlFont;
+ return FontSets.createDefaultFontSet(controlFont, menuFont, titleFont);
+ }
+
+ /**
+ * Looks up and returns a custom FontPolicy for the given Look&Feel
+ * name, or <code>null</code> if no custom policy has been defined for this
+ * Look&Feel.
+ *
+ * @param lafName
+ * the name of the Look&Feel, one of <code>"Plastic"</code> or
+ * <code>"Windows"</code>
+ * @return a custom FontPolicy - if any - or otherwise <code>null</code>
+ */
+ private static FontPolicy getCustomPolicy(String lafName) {
+ // TODO: Look up predefined font choice policies
+ return null;
+ }
+
+ private static final class CustomSettingsPolicy implements FontPolicy {
+
+ private final FontPolicy wrappedPolicy;
+
+ CustomSettingsPolicy(FontPolicy wrappedPolicy) {
+ this.wrappedPolicy = wrappedPolicy;
+ }
+
+ @Override
+ public FontSet getFontSet(String lafName, UIDefaults table) {
+ FontPolicy customPolicy = getCustomPolicy(lafName);
+ if (customPolicy != null) {
+ return customPolicy.getFontSet(null, table);
+ }
+ FontSet customFontSet = getCustomFontSet(lafName);
+ if (customFontSet != null) {
+ return customFontSet;
+ }
+ return wrappedPolicy.getFontSet(lafName, table);
+ }
+ }
+
+ // private static final class DefaultCrossPlatformPolicy implements
+ // FontPolicy {
+ //
+ // public FontSet getFontSet(String lafName, UIDefaults table) {
+ // // TODO: If Tahoma or Segoe UI is available, return them
+ // // in a size appropriate for the screen resolution.
+ // // Otherwise return the logical font set.
+ // return FontSets.getLogicalFontSet();
+ // }
+ // }
+
+ /**
+ * Implements the default font lookup for the Plastic L&f family when
+ * running in a Windows environment.
+ */
+ private static final class DefaultPlasticOnWindowsPolicy implements
+ FontPolicy {
+
+ @Override
+ public FontSet getFontSet(String lafName, UIDefaults table) {
+ Font windowsControlFont = Fonts.getWindowsControlFont();
+ Font controlFont;
+ if (windowsControlFont != null) {
+ controlFont = windowsControlFont;
+ } else if (table != null) {
+ controlFont = table.getFont("Button.font");
+ } else {
+ controlFont = new Font("Dialog", Font.PLAIN, 12);
+ }
+
+ Font menuFont = table == null ? controlFont : table
+ .getFont("Menu.font");
+ Font titleFont = controlFont.deriveFont(Font.BOLD);
+
+ return FontSets.createDefaultFontSet(controlFont, menuFont,
+ titleFont);
+ }
+ }
+
+ /**
+ * Implements the default font lookup on the Windows platform.
+ */
+ private static final class DefaultWindowsPolicy implements FontPolicy {
+
+ @Override
+ public FontSet getFontSet(String lafName, UIDefaults table) {
+ Font windowsControlFont = Fonts.getWindowsControlFont();
+ Font controlFont;
+ if (windowsControlFont != null) {
+ controlFont = windowsControlFont;
+ } else if (table != null) {
+ controlFont = table.getFont("Button.font");
+ } else {
+ controlFont = new Font("Dialog", Font.PLAIN, 12);
+ }
+ Font menuFont = table == null ? controlFont : table
+ .getFont("Menu.font");
+ Font titleFont = controlFont;
+ Font messageFont = table == null ? controlFont : table
+ .getFont("OptionPane.font");
+ Font smallFont = table == null ? controlFont.deriveFont(controlFont
+ .getSize2D() - 2f) : table.getFont("ToolTip.font");
+ Font windowTitleFont = table == null ? controlFont : table
+ .getFont("InternalFrame.titleFont");
+ return FontSets.createDefaultFontSet(controlFont, menuFont,
+ titleFont, messageFont, smallFont, windowTitleFont);
+ }
+ }
+
+ /**
+ * A FontPolicy that returns a fixed FontSet and that ignores the laf name
+ * and UIDefaults table.
+ */
+ private static final class FixedPolicy implements FontPolicy {
+
+ private final FontSet fontSet;
+
+ FixedPolicy(FontSet fontSet) {
+ this.fontSet = fontSet;
+ }
+
+ @Override
+ public FontSet getFontSet(String lafName, UIDefaults table) {
+ return fontSet;
+ }
+ }
+
+ /**
+ * Aims to mimic the font choice as used in the JGoodies Looks 1.x.
+ */
+ private static final class Looks1xWindowsPolicy implements FontPolicy {
+
+ @Override
+ public FontSet getFontSet(String lafName, UIDefaults table) {
+ Font windowsControlFont = Fonts.getLooks1xWindowsControlFont();
+ Font controlFont;
+ if (windowsControlFont != null) {
+ controlFont = windowsControlFont;
+ } else if (table != null) {
+ controlFont = table.getFont("Button.font");
+ } else {
+ controlFont = new Font("Dialog", Font.PLAIN, 12);
+ }
+ return FontSets.createDefaultFontSet(controlFont);
+ }
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/fonts/FontSets.java b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/FontSets.java
new file mode 100644
index 0000000..f77a739
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/FontSets.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2001-2006 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.substance.internal.fonts;
+
+import java.awt.Font;
+
+import javax.swing.plaf.FontUIResource;
+
+import org.pushingpixels.substance.api.fonts.FontPolicy;
+import org.pushingpixels.substance.api.fonts.FontSet;
+
+/**
+ * Provides predefined FontSet implementations.
+ *
+ * @author Karsten Lentzsch
+ *
+ * @see FontSet
+ * @see FontPolicy
+ * @see FontPolicies
+ *
+ * @since 2.0
+ */
+public final class FontSets {
+
+ private static FontSet logicalFontSet;
+
+ private FontSets() {
+ // Override default constructor; prevents instantation.
+ }
+
+ // Default FontSets *******************************************************
+
+ /**
+ * Creates and returns a FontSet that is based only on the given control
+ * font. The small font will be derived from the control font; all other
+ * fonts returned are the control font.
+ *
+ * @param controlFont
+ * the font used for all controls
+ *
+ * @return a FontSet based on the given fonts
+ *
+ * @throws NullPointerException
+ * if the control font is <code>null</code>
+ */
+ public static FontSet createDefaultFontSet(Font controlFont) {
+ return createDefaultFontSet(controlFont, null);
+ }
+
+ /**
+ * Creates and returns a FontSet that is based on the given control font and
+ * menu font. The small font will be derived from the control font; all
+ * other fonts return, except the menu font, are the control font.
+ *
+ * @param controlFont
+ * the font used for all controls
+ * @param menuFont
+ * the font used for the menu bar and menu items
+ *
+ * @return a FontSet based on the given fonts
+ *
+ * @throws NullPointerException
+ * if the control font is <code>null</code>
+ */
+ public static FontSet createDefaultFontSet(Font controlFont, Font menuFont) {
+ return createDefaultFontSet(controlFont, menuFont, null, null, null,
+ null);
+ }
+
+ /**
+ * Creates and returns a FontSet that is based on the given control font and
+ * menu font. The small font will be derived from the control font; all
+ * other fonts return, except the menu font, are the control font.
+ *
+ * @param controlFont
+ * the font used for all controls
+ * @param menuFont
+ * the font used for the menu bar and menu items
+ * @param titleFont
+ * used for TitledBorder, titles and titled separators
+ *
+ * @return a FontSet based on the given fonts
+ *
+ * @throws NullPointerException
+ * if the control font is <code>null</code>
+ */
+ public static FontSet createDefaultFontSet(Font controlFont, Font menuFont,
+ Font titleFont) {
+ return createDefaultFontSet(controlFont, menuFont, titleFont, null,
+ null, null);
+ }
+
+ /**
+ * Creates and returns a FontSet for the given fonts. If a font is
+ * <code>null</code>, it uses the control font as fallback. If the small
+ * font is <code>null</code> it will be derived from the control font.
+ *
+ * @param controlFont
+ * used for all controls
+ * @param menuFont
+ * used for the menu bar and menu items
+ * @param titleFont
+ * used for TitledBorder, titles and titled separators
+ * @param messageFont
+ * used for OptionPanes
+ * @param smallFont
+ * used for tool tips and similar components
+ * @param windowTitleFont
+ * used for internal frame window titles
+ *
+ * @return a FontSet based on the given fonts
+ *
+ * @throws NullPointerException
+ * if the control font is <code>null</code>
+ */
+ public static FontSet createDefaultFontSet(Font controlFont, Font menuFont,
+ Font titleFont, Font messageFont, Font smallFont,
+ Font windowTitleFont) {
+ return new DefaultFontSet(controlFont, menuFont, titleFont,
+ messageFont, smallFont, windowTitleFont);
+ }
+
+ // Logical FontSet ********************************************************
+
+ /**
+ * Lazily creates and returns the FontSet that returns the logical fonts
+ * specified by the Java runtime environment.
+ *
+ * @return a FontSets that uses the logical fonts specified by the Java
+ * environment
+ */
+ public static FontSet getLogicalFontSet() {
+ if (logicalFontSet == null) {
+ logicalFontSet = new LogicalFontSet();
+ }
+ return logicalFontSet;
+ }
+
+ // Helper Code ************************************************************
+
+ private static final class DefaultFontSet implements FontSet {
+
+ private final FontUIResource controlFont;
+ private final FontUIResource menuFont;
+ private final FontUIResource titleFont;
+ private final FontUIResource messageFont;
+ private final FontUIResource smallFont;
+ private final FontUIResource windowTitleFont;
+
+ // Instance Creation --------------------------------------------------
+
+ /**
+ * Constructs a DefaultFontSet for the given fonts. If a font is
+ * <code>null</code>, it uses the control font as fallback. If the small
+ * font is <code>null</code> it will be derived from the control font.
+ *
+ * @param controlFont
+ * used for all controls
+ * @param menuFont
+ * used for the menu bar and menu items
+ * @param titleFont
+ * used for TitledBorder, titles and titled separators
+ * @param messageFont
+ * used for OptionPanes
+ * @param smallFont
+ * used for tool tips and similar components
+ * @param windowTitleFont
+ * used for internal frame window titles
+ *
+ * @throws NullPointerException
+ * if the control font is <code>null</code>
+ */
+ public DefaultFontSet(Font controlFont, Font menuFont, Font titleFont,
+ Font messageFont, Font smallFont, Font windowTitleFont) {
+ this.controlFont = new FontUIResource(controlFont);
+ this.menuFont = menuFont != null ? new FontUIResource(menuFont)
+ : this.controlFont;
+ this.titleFont = titleFont != null ? new FontUIResource(titleFont)
+ : this.controlFont; // new
+ // FontUIResource(controlFont.deriveFont
+ // (Font.BOLD))
+ this.messageFont = messageFont != null ? new FontUIResource(
+ messageFont) : this.controlFont;
+ this.smallFont = new FontUIResource(smallFont != null ? smallFont
+ : controlFont.deriveFont(controlFont.getSize2D() - 2f));
+ this.windowTitleFont = windowTitleFont != null ? new FontUIResource(
+ windowTitleFont)
+ : this.titleFont;
+ }
+
+ // FontSet API --------------------------------------------------------
+
+ @Override
+ public FontUIResource getControlFont() {
+ return controlFont;
+ }
+
+ @Override
+ public FontUIResource getMenuFont() {
+ return menuFont;
+ }
+
+ @Override
+ public FontUIResource getTitleFont() {
+ return titleFont;
+ }
+
+ @Override
+ public FontUIResource getWindowTitleFont() {
+ return windowTitleFont;
+ }
+
+ @Override
+ public FontUIResource getSmallFont() {
+ return smallFont;
+ }
+
+ @Override
+ public FontUIResource getMessageFont() {
+ return messageFont;
+ }
+
+ }
+
+ /**
+ * Looks up and returns the logical fonts as specified by the Java runtime
+ * environment.
+ */
+ private static final class LogicalFontSet implements FontSet {
+
+ private FontUIResource controlFont;
+ private FontUIResource titleFont;
+ private FontUIResource systemFont;
+ private FontUIResource smallFont;
+
+ @Override
+ public FontUIResource getControlFont() {
+ if (controlFont == null) {
+ controlFont = new FontUIResource(Font.getFont(
+ "swing.plaf.metal.controlFont", new Font("Dialog",
+ Font.PLAIN, 12)));
+
+ }
+ return controlFont;
+ }
+
+ @Override
+ public FontUIResource getMenuFont() {
+ return getControlFont();
+ }
+
+ @Override
+ public FontUIResource getTitleFont() {
+ if (titleFont == null) {
+ titleFont = new FontUIResource(getControlFont().deriveFont(
+ Font.BOLD));
+ }
+ return titleFont;
+ }
+
+ @Override
+ public FontUIResource getSmallFont() {
+ if (smallFont == null) {
+ smallFont = new FontUIResource(Font.getFont(
+ "swing.plaf.metal.smallFont", new Font("Dialog",
+ Font.PLAIN, 10)));
+ }
+ return smallFont;
+ }
+
+ @Override
+ public FontUIResource getMessageFont() {
+ if (systemFont == null) {
+ systemFont = new FontUIResource(Font.getFont(
+ "swing.plaf.metal.systemFont", new Font("Dialog",
+ Font.PLAIN, 12)));
+ }
+ return systemFont;
+ }
+
+ @Override
+ public FontUIResource getWindowTitleFont() {
+ return getTitleFont();
+ }
+
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/fonts/Fonts.java b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/Fonts.java
new file mode 100644
index 0000000..997ecb7
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/Fonts.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (c) 2001-2006 JGoodies Karsten Lentzsch. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of JGoodies Karsten Lentzsch nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.substance.internal.fonts;
+
+import java.awt.Font;
+import java.awt.Toolkit;
+import java.util.Locale;
+
+import org.pushingpixels.lafwidget.utils.LookUtils;
+import org.pushingpixels.substance.api.fonts.FontPolicy;
+import org.pushingpixels.substance.api.fonts.FontSet;
+
+/**
+ * Provides static access to popular Windows fonts. The sizes of the font
+ * constants are specified in <em>typographic points</em>, approximately 1/72 of
+ * an inch.
+ * <p>
+ *
+ * TODO: Consider changing the visibility of the package private methods to
+ * public. As an alternative we may provide a FontPolicy that can emulate the
+ * font choice on Windows XP/2000 and Windows Vista for different software
+ * resolutions (96dpi/120dpi) and desktop font size settings (Normal/Large/Extra
+ * Large).
+ *
+ * @author Karsten Lentzsch
+ *
+ * @see FontSet
+ * @see FontSets
+ * @see FontPolicy
+ * @see FontPolicies
+ *
+ * @since 2.0
+ */
+public final class Fonts {
+
+ /**
+ * The name of the default dialog font on western Windows XP.
+ */
+ public static final String TAHOMA_NAME = "Tahoma";
+
+ /**
+ * The name of the default dialog font on western Windows Vista.
+ */
+ public static final String SEGOE_UI_NAME = "Segoe UI";
+
+ // Physical Fonts *********************************************************
+
+ /**
+ * This is the default font on western XP with 96dpi and normal fonts.
+ * Ascent=11, descent=3, height=14, dbuX=6, dbuY=12, 14dluY=21px.
+ */
+ public static final Font TAHOMA_11PT = new Font(TAHOMA_NAME, Font.PLAIN, 11);
+
+ /**
+ * Ascent=13, descent=3, height=16, dbuX=8, dbuY=13, 14dluY=22.75px.
+ */
+ public static final Font TAHOMA_13PT = new Font(TAHOMA_NAME, Font.PLAIN, 13);
+
+ /**
+ * Ascent=14, descent=3, height=17, dbuX=8, dbuY=14, 14dluY=24.5px.
+ */
+ public static final Font TAHOMA_14PT = new Font(TAHOMA_NAME, Font.PLAIN, 14);
+
+ /**
+ * This is Segoe UI 9pt, the default font on western Vista with 96dpi.
+ * Ascent=13, descent=4, height=17, dbuX=7, dbuY=13, 13dluY=21.125px.
+ */
+ public static final Font SEGOE_UI_12PT = new Font(SEGOE_UI_NAME,
+ Font.PLAIN, 12);
+
+ /**
+ * Ascent=14, descent=4, height=18, dbuX=8, dbuY=14, 13dluY=22.75px.
+ */
+ public static final Font SEGOE_UI_13PT = new Font(SEGOE_UI_NAME,
+ Font.PLAIN, 13);
+
+ /**
+ * Ascent=16, descent=5, height=21, dbuX=9, dbuY=16, 13dluY=26px.
+ */
+ public static final Font SEGOE_UI_15PT = new Font(SEGOE_UI_NAME,
+ Font.PLAIN, 15);
+
+ // Default Windows Fonts **************************************************
+
+ /**
+ * The default icon font on western Windows XP with 96dpi and the dialog
+ * font desktop setting "Normal".
+ */
+ public static final Font WINDOWS_XP_96DPI_NORMAL = TAHOMA_11PT;
+
+ /**
+ * The default GUI font on western Windows XP with 96dpi and the dialog font
+ * desktop setting "Normal".
+ */
+ public static final Font WINDOWS_XP_96DPI_DEFAULT_GUI = TAHOMA_11PT;
+
+ /**
+ * The default icon font on western Windows XP with 96dpi and the dialog
+ * font desktop setting "Large".
+ */
+ public static final Font WINDOWS_XP_96DPI_LARGE = TAHOMA_13PT;
+
+ /**
+ * The default icon font on western Windows XP with 120dpi and the dialog
+ * font desktop setting "Normal".
+ */
+ public static final Font WINDOWS_XP_120DPI_NORMAL = TAHOMA_14PT;
+
+ /**
+ * The default GUI font on western Windows XP with 120dpi and the dialog
+ * font desktop setting "Normal".
+ */
+ public static final Font WINDOWS_XP_120DPI_DEFAULT_GUI = TAHOMA_13PT;
+
+ /**
+ * The default icon font on western Windows Vista with 96dpi and the dialog
+ * font desktop setting "Normal".
+ */
+ public static final Font WINDOWS_VISTA_96DPI_NORMAL = SEGOE_UI_12PT;
+
+ /**
+ * The default icon font on western Windows Vista with 96dpi and the dialog
+ * font desktop setting "Large".
+ */
+ public static final Font WINDOWS_VISTA_96DPI_LARGE = SEGOE_UI_15PT;
+
+ /**
+ * The default icon font on western Windows Vista with 101dpi and the dialog
+ * font desktop setting "Normal".
+ * <P>
+ *
+ * TODO: Check if this shall be removed or not.
+ */
+ static final Font WINDOWS_VISTA_101DPI_NORMAL = SEGOE_UI_13PT;
+
+ /**
+ * The default icon font on western Windows Vista with 120dpi and the dialog
+ * font desktop setting "Normal".
+ */
+ public static final Font WINDOWS_VISTA_120DPI_NORMAL = SEGOE_UI_15PT;
+
+ // Desktop Property Font Keys *********************************************
+
+ /**
+ * The desktop property key used to lookup the DEFAULTGUI font. This font
+ * scales with the software resolution only but works in western and
+ * non-western Windows environments.
+ *
+ * @see #getWindowsControlFont()
+ */
+ static final String WINDOWS_DEFAULT_GUI_FONT_KEY = "win.defaultGUI.font";
+
+ /**
+ * The desktop property key used to lookup Windows' icon font. This font
+ * scales with the software resolution and the desktop font size setting
+ * (Normal/Large/Extra Large). However, in some non-western Windows
+ * environments this font cannot display the locale's glyphs.
+ * <p>
+ *
+ * Implementation Note: Windows uses the icon font to label icons in the
+ * Windows Explorer and other places. It seems to me that this works in
+ * non-western environments due to font chaining.
+ *
+ * @see #getWindowsControlFont()
+ */
+ static final String WINDOWS_ICON_FONT_KEY = "win.icon.font";
+
+ // Instance Creation ******************************************************
+
+ private Fonts() {
+ // Override default constructor; prevents instantation.
+ }
+
+ // Font Lookup ************************************************************
+
+ static Font getDefaultGUIFontWesternModernWindowsNormal() {
+ return LookUtils.IS_LOW_RESOLUTION ? WINDOWS_XP_96DPI_DEFAULT_GUI
+ : WINDOWS_XP_120DPI_DEFAULT_GUI;
+ }
+
+ static Font getDefaultIconFontWesternModernWindowsNormal() {
+ return LookUtils.IS_LOW_RESOLUTION ? WINDOWS_XP_96DPI_NORMAL
+ : WINDOWS_XP_120DPI_NORMAL;
+ }
+
+ static Font getDefaultIconFontWesternWindowsVistaNormal() {
+ return LookUtils.IS_LOW_RESOLUTION ? WINDOWS_VISTA_96DPI_NORMAL
+ : WINDOWS_VISTA_120DPI_NORMAL;
+ }
+
+ /**
+ * Returns the Windows control font used by the JGoodies Looks version 1.x.
+ * It is intended for visual backward compatibility only. The font returned
+ * is the default GUI font that scales with the resolution (96dpi, 120dpi,
+ * etc) but not with the desktop font size settings (normal, large, extra
+ * large).
+ * <p>
+ *
+ * On Windows Vista, the font may be completely wrong.
+ *
+ * @return the Windows default GUI font that scales with the resolution, but
+ * not the desktop font size setting
+ *
+ * @throws UnsupportedOperationException
+ * on non-Windows platforms
+ */
+ static Font getLooks1xWindowsControlFont() {
+ if (!LookUtils.IS_OS_WINDOWS)
+ throw new UnsupportedOperationException();
+
+ return getDesktopFont(WINDOWS_DEFAULT_GUI_FONT_KEY);
+ }
+
+ /**
+ * Looks up and returns the Windows control font. Returns the Windows icon
+ * title font unless it is inappropriate for the Windows version, Java
+ * renderer, or locale.
+ * <p>
+ *
+ * The icon title font scales with the resolution (96dpi, 101dpi, 120dpi,
+ * etc) and the desktop font size settings (normal, large, extra large).
+ * Older versions may return a poor font. Also, since Java 1.4 and Java 5
+ * render the Windows Vista icon font Segoe UI poorly, we return the default
+ * GUI font in these environments.
+ * <p>
+ *
+ * The last check is, if the icon font can display text in the default
+ * locale. Therefore we test if the locale's localized display name can be
+ * displayed by the icon font. For example, Tahoma can display "English",
+ * "Deutsch", but not the display name for "Chinese" in Chinese.
+ *
+ * @return the Windows control font
+ *
+ * @throws UnsupportedOperationException
+ * on non-Windows platforms
+ */
+ public static Font getWindowsControlFont() {
+ if (!LookUtils.IS_OS_WINDOWS)
+ throw new UnsupportedOperationException();
+
+ Font defaultGUIFont = getDefaultGUIFont();
+ // Return the default GUI font on older Windows versions.
+ if (LookUtils.IS_OS_WINDOWS_95 || LookUtils.IS_OS_WINDOWS_98
+ || LookUtils.IS_OS_WINDOWS_NT || LookUtils.IS_OS_WINDOWS_ME)
+ return defaultGUIFont;
+
+ // Java 1.4 and Java 5 raster the Segoe UI poorly,
+ // so we use the older Tahoma, if it can display the localized text.
+ if (LookUtils.IS_OS_WINDOWS_VISTA) {
+ if (LookUtils.IS_JAVA_1_4_OR_5) {
+ Font tahoma = getDefaultGUIFontWesternModernWindowsNormal();
+ return Boolean.TRUE.equals(canDisplayLocalizedText(tahoma,
+ Locale.getDefault())) ? tahoma : defaultGUIFont;
+ }
+ }
+
+ Font iconFont = getDesktopFont(WINDOWS_ICON_FONT_KEY);
+ return Boolean.TRUE.equals(canDisplayLocalizedText(iconFont, Locale
+ .getDefault())) ? iconFont : defaultGUIFont;
+ }
+
+ /**
+ * Looks up and returns the Windows defaultGUI font. Works around a bug with
+ * Java 1.4.2_11, 1.5.0_07, and 1.6 b89 in the Vista Beta2, where the
+ * win.defaultGUI.font desktop property returns null. In this case a logical
+ * "Dialog" font is used as fallback.
+ *
+ * @return the Windows defaultGUI font, or a dialog font as fallback.
+ */
+ private static Font getDefaultGUIFont() {
+ Font font = getDesktopFont(WINDOWS_DEFAULT_GUI_FONT_KEY);
+ if (font != null)
+ return font;
+ return new Font("Dialog", Font.PLAIN, 12);
+ }
+
+ /**
+ * Checks and answers whether the given font can display text that is
+ * localized for the specified locale. Returns <code>null</code> if we can't
+ * test it.
+ * <p>
+ *
+ * First checks, if the locale's display language is available in localized
+ * form, for example "Deutsch" for the German locale. If so, we check if the
+ * given font can display the localized display language.
+ * <p>
+ *
+ * Otherwise we check some known combinations of fonts and locales and
+ * return the associated results. For all other combinations,
+ * <code>null</code> is returned to indicate that we don't know whether the
+ * font can display text in the given locale.
+ *
+ * @param font
+ * the font to be tested
+ * @param locale
+ * the locale to be used
+ * @return <code>Boolean.TRUE</code> if the font can display the locale's
+ * text, <code>Boolean.FALSE</code> if not, <code>null</code> if we
+ * don't know
+ *
+ * @since 2.0.4
+ */
+ public static Boolean canDisplayLocalizedText(Font font, Locale locale) {
+ if (localeHasLocalizedDisplayLanguage(locale)) {
+ return canDisplayLocalizedDisplayLanguage(font,
+ locale);
+ }
+ String fontName = font.getName();
+ String language = locale.getLanguage();
+ if ("Tahoma".equals(fontName)) {
+ if ("hi".equals(language))
+ return Boolean.FALSE;
+ else if ("ja".equals(language))
+ return Boolean.FALSE;
+ else if ("ko".equals(language))
+ return Boolean.FALSE;
+ else if ("zh".equals(language))
+ return Boolean.FALSE;
+ }
+ if ("Microsoft Sans Serif".equals(fontName)) {
+ if ("ja".equals(language))
+ return Boolean.FALSE;
+ else if ("ko".equals(language))
+ return Boolean.FALSE;
+ else if ("zh".equals(language))
+ return Boolean.FALSE;
+ }
+ return null;
+ }
+
+ /**
+ * Checks and answers if the given font can display the locale's localized
+ * display language, for example "English" for English, "Deutsch" for
+ * German, etc. The test invokes <code>Font#canDisplayUpTo</code> on the
+ * localized display language. In a Chinese locale this test will check if
+ * the font can display Chinese glyphs.
+ *
+ * @param font
+ * the font to be tested
+ * @param locale
+ * the locale to be used
+ * @return true if the font can display the locale's localized display
+ * language, false otherwise
+ */
+ private static boolean canDisplayLocalizedDisplayLanguage(Font font,
+ Locale locale) {
+ String testString = locale.getDisplayLanguage(locale);
+ int index = font.canDisplayUpTo(testString);
+ return index == -1;
+ }
+
+ /**
+ * Checks and answers whether the locale's display language is available in
+ * a localized form, for example "Deutsch" for the German locale.
+ *
+ * @param locale
+ * the Locale to test
+ * @return true if the display language is localized, false if not
+ */
+ private static boolean localeHasLocalizedDisplayLanguage(Locale locale) {
+ if (locale.getLanguage().equals(Locale.ENGLISH.getLanguage()))
+ return true;
+ String englishDisplayLanguage = locale
+ .getDisplayLanguage(Locale.ENGLISH);
+ String localizedDisplayLanguage = locale.getDisplayLanguage(locale);
+ return !(englishDisplayLanguage.equals(localizedDisplayLanguage));
+ }
+
+ /**
+ * Looks up and returns a font using the default toolkit's desktop
+ * properties.
+ *
+ * @param fontName
+ * the name of the font to return
+ * @return the font
+ */
+ private static Font getDesktopFont(String fontName) {
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ return (Font) toolkit.getDesktopProperty(fontName);
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/fonts/ScaledFontSet.java b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/ScaledFontSet.java
new file mode 100644
index 0000000..0d79e06
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/fonts/ScaledFontSet.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.fonts;
+
+import javax.swing.plaf.FontUIResource;
+
+import org.pushingpixels.substance.api.fonts.FontSet;
+
+/**
+ * Wrapper around the base Substance font set. Is used to create larger /
+ * smaller font sets.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ScaledFontSet implements FontSet {
+ /**
+ * Scale factor. Should be positive.
+ */
+ private float scaleFactor;
+
+ /**
+ * The base Substance font set.
+ */
+ private FontSet delegate;
+
+ /**
+ * Creates a wrapper font set.
+ *
+ * @param delegate
+ * The base Substance font set.
+ * @param scaleFactor
+ * Scale factor. Should be positive.
+ */
+ public ScaledFontSet(FontSet delegate, float scaleFactor) {
+ super();
+ this.delegate = delegate;
+ this.scaleFactor = scaleFactor;
+ }
+
+ /**
+ * Returns the wrapped font.
+ *
+ * @param systemFont
+ * Original font.
+ * @return Wrapped font.
+ */
+ private FontUIResource getWrappedFont(FontUIResource systemFont) {
+ return new FontUIResource(systemFont.getFontName(), systemFont
+ .getStyle(), (int) (systemFont.getSize() * this.scaleFactor));
+ }
+
+ @Override
+ public FontUIResource getControlFont() {
+ return this.getWrappedFont(this.delegate.getControlFont());
+ }
+
+ @Override
+ public FontUIResource getMenuFont() {
+ return this.getWrappedFont(this.delegate.getMenuFont());
+ }
+
+ @Override
+ public FontUIResource getMessageFont() {
+ return this.getWrappedFont(this.delegate.getMessageFont());
+ }
+
+ @Override
+ public FontUIResource getSmallFont() {
+ return this.getWrappedFont(this.delegate.getSmallFont());
+ }
+
+ @Override
+ public FontUIResource getTitleFont() {
+ return this.getWrappedFont(this.delegate.getTitleFont());
+ }
+
+ @Override
+ public FontUIResource getWindowTitleFont() {
+ return this.getWrappedFont(this.delegate.getWindowTitleFont());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/inputmaps/AquaInputMapSet.java b/substance/src/main/java/org/pushingpixels/substance/internal/inputmaps/AquaInputMapSet.java
new file mode 100644
index 0000000..d516385
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/inputmaps/AquaInputMapSet.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.inputmaps;
+
+import javax.swing.JTextField;
+import javax.swing.text.DefaultEditorKit;
+
+import org.pushingpixels.substance.api.inputmaps.SubstanceInputMap;
+
+public class AquaInputMapSet extends BaseInputMapSet {
+ @Override
+ public SubstanceInputMap getComboBoxAncestorInputMap() {
+ // All entries in Aqua are "aqua" except ESCAPE mapped to "hidePopup"
+ return super.getComboBoxAncestorInputMap();
+ }
+
+ protected SubstanceInputMap getSingleLineTextComponentFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("meta C", DefaultEditorKit.copyAction);
+ result.put("meta V", DefaultEditorKit.pasteAction);
+ result.put("meta X", DefaultEditorKit.cutAction);
+ result.put("COPY", DefaultEditorKit.copyAction);
+ result.put("PASTE", DefaultEditorKit.pasteAction);
+ result.put("CUT", DefaultEditorKit.cutAction);
+
+ result.put("shift LEFT", DefaultEditorKit.selectionBackwardAction);
+ result.put("shift KP_LEFT", DefaultEditorKit.selectionBackwardAction);
+ result.put("shift RIGHT", DefaultEditorKit.selectionForwardAction);
+ result.put("shift KP_RIGHT", DefaultEditorKit.selectionForwardAction);
+
+ result.put("meta LEFT", DefaultEditorKit.beginLineAction);
+ result.put("meta KP_LEFT", DefaultEditorKit.beginLineAction);
+ result.put("ctrl A", DefaultEditorKit.beginLineAction);
+ result.put("UP", DefaultEditorKit.beginLineAction);
+ result.put("KP_UP", DefaultEditorKit.beginLineAction);
+ result.put("meta RIGHT", DefaultEditorKit.endLineAction);
+ result.put("meta KP_RIGHT", DefaultEditorKit.endLineAction);
+ result.put("ctrl E", DefaultEditorKit.endLineAction);
+ result.put("DOWN", DefaultEditorKit.endLineAction);
+ result.put("KP_DOWN", DefaultEditorKit.endLineAction);
+
+ result
+ .put("meta shift LEFT",
+ DefaultEditorKit.selectionBeginLineAction);
+ result.put("meta shift KP_LEFT",
+ DefaultEditorKit.selectionBeginLineAction);
+ result.put("shift UP", DefaultEditorKit.selectionBeginLineAction);
+ result.put("shift KP_UP", DefaultEditorKit.selectionBeginLineAction);
+ result.put("meta shift RIGHT", DefaultEditorKit.selectionEndLineAction);
+ result.put("meta shift KP_RIGHT",
+ DefaultEditorKit.selectionEndLineAction);
+ result.put("shift DOWN", DefaultEditorKit.selectionEndLineAction);
+ result.put("shift KP_DOWN", DefaultEditorKit.selectionEndLineAction);
+
+ result.put("meta A", DefaultEditorKit.selectAllAction);
+
+ result.put("HOME", DefaultEditorKit.beginAction);
+ result.put("ctrl P", DefaultEditorKit.beginAction);
+ result.put("meta UP", DefaultEditorKit.beginAction);
+ result.put("meta KP_UP", DefaultEditorKit.beginAction);
+ result.put("END", DefaultEditorKit.endAction);
+ result.put("ctrl N", DefaultEditorKit.endAction);
+ result.put("ctrl V", DefaultEditorKit.endAction);
+ result.put("meta DOWN", DefaultEditorKit.endAction);
+ result.put("meta KP_DOWN", DefaultEditorKit.endAction);
+
+ result.put("shift meta KP_UP", DefaultEditorKit.selectionBeginAction);
+ result.put("shift UP", DefaultEditorKit.selectionBeginAction);
+ result.put("shift HOME", DefaultEditorKit.selectionBeginAction);
+ result.put("shift meta DOWN", DefaultEditorKit.selectionEndAction);
+ result.put("shift meta KP_DOWN", DefaultEditorKit.selectionEndAction);
+ result.put("shift END", DefaultEditorKit.selectionEndAction);
+
+ result.put("BACK_SPACE", DefaultEditorKit.deletePrevCharAction);
+ result.put("ctrl H", DefaultEditorKit.deletePrevCharAction);
+ result.put("DELETE", DefaultEditorKit.deleteNextCharAction);
+ result.put("ctrl D", DefaultEditorKit.deleteNextCharAction);
+ result.put("alt BACK_SPACE", DefaultEditorKit.deletePrevWordAction);
+ result.put("ctrl W", DefaultEditorKit.deletePrevWordAction);
+ result.put("alt DELETE", DefaultEditorKit.deleteNextWordAction);
+
+ result.put("RIGHT", DefaultEditorKit.forwardAction);
+ result.put("KP_RIGHT", DefaultEditorKit.forwardAction);
+ result.put("ctrl F", DefaultEditorKit.forwardAction);
+ result.put("LEFT", DefaultEditorKit.backwardAction);
+ result.put("KP_LEFT", DefaultEditorKit.backwardAction);
+ result.put("ctrl B", DefaultEditorKit.backwardAction);
+
+ result.put("alt RIGHT", DefaultEditorKit.nextWordAction);
+ result.put("alt KP_RIGHT", DefaultEditorKit.nextWordAction);
+ result.put("alt LEFT", DefaultEditorKit.previousWordAction);
+ result.put("alt KP_LEFT", DefaultEditorKit.previousWordAction);
+
+ result.put("shift alt RIGHT", DefaultEditorKit.selectionNextWordAction);
+ result.put("shift alt KP_RIGHT",
+ DefaultEditorKit.selectionNextWordAction);
+ result.put("shift alt LEFT",
+ DefaultEditorKit.selectionPreviousWordAction);
+ result.put("shift alt KP_LEFT",
+ DefaultEditorKit.selectionPreviousWordAction);
+
+ result.put("shift meta PAGE_UP",
+ TextComponentActions.SELECTION_PAGE_LEFT);
+ result.put("shift meta PAGE_DOWN",
+ TextComponentActions.SELECTION_PAGE_RIGHT);
+ result
+ .put("shift meta PAGE_UP",
+ TextComponentActions.SELECTION_PAGE_UP);
+ result.put("shift meta PAGE_DOWN",
+ TextComponentActions.SELECTION_PAGE_DOWN);
+
+ result.put("ENTER", JTextField.notifyAction);
+ result.put("meta BACK_SLASH", TextComponentActions.UNSELECT);
+ result.put("control shift O",
+ TextComponentActions.TOGGLE_COMPONENT_ORIENTATION);
+
+ // XXX: in Aqua these are aqua-page-down and aqua-page-up
+ result.put("PAGE_DOWN", DefaultEditorKit.pageDownAction);
+ result.put("PAGE_UP", DefaultEditorKit.pageUpAction);
+
+ return result;
+ }
+
+ @Override
+ protected SubstanceInputMap getMultilineTextComponentFocusInputMap() {
+ // This is used in EditorPane, TextArea and TextPane. Almost
+ // all key strokes can be mapped to the same basic actions used in Aqua.
+ // There are 7 key strokes that use Aqua-specific actions. These
+ // are mapped to the closest basic actions.
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("meta C", DefaultEditorKit.copyAction);
+ result.put("meta V", DefaultEditorKit.pasteAction);
+ result.put("meta X", DefaultEditorKit.cutAction);
+ result.put("COPY", DefaultEditorKit.copyAction);
+ result.put("PASTE", DefaultEditorKit.pasteAction);
+ result.put("CUT", DefaultEditorKit.cutAction);
+
+ result.put("shift LEFT", DefaultEditorKit.selectionBackwardAction);
+ result.put("shift KP_LEFT", DefaultEditorKit.selectionBackwardAction);
+ result.put("shift RIGHT", DefaultEditorKit.selectionForwardAction);
+ result.put("shift KP_RIGHT", DefaultEditorKit.selectionForwardAction);
+ result.put("alt LEFT", DefaultEditorKit.previousWordAction);
+ result.put("alt KP_LEFT", DefaultEditorKit.previousWordAction);
+ result.put("alt RIGHT", DefaultEditorKit.nextWordAction);
+ result.put("alt KP_RIGHT", DefaultEditorKit.nextWordAction);
+ result.put("alt shift LEFT",
+ DefaultEditorKit.selectionPreviousWordAction);
+ result.put("alt shift KP_LEFT",
+ DefaultEditorKit.selectionPreviousWordAction);
+ result.put("alt shift RIGHT", DefaultEditorKit.selectionNextWordAction);
+ result.put("alt shift KP_RIGHT",
+ DefaultEditorKit.selectionNextWordAction);
+
+ result.put("meta A", DefaultEditorKit.selectAllAction);
+ result.put("ctrl A", DefaultEditorKit.beginLineAction);
+ result.put("meta KP_LEFT", DefaultEditorKit.beginLineAction);
+ result.put("meta LEFT", DefaultEditorKit.beginLineAction);
+ result.put("ctrl E", DefaultEditorKit.endLineAction);
+ result.put("meta KP_RIGHT", DefaultEditorKit.endLineAction);
+ result.put("meta RIGHT", DefaultEditorKit.endLineAction);
+
+ result.put("shift meta KP_LEFT",
+ DefaultEditorKit.selectionBeginLineAction);
+ result
+ .put("shift meta LEFT",
+ DefaultEditorKit.selectionBeginLineAction);
+ result.put("shift meta KP_RIGHT",
+ DefaultEditorKit.selectionEndLineAction);
+ result.put("shift meta RIGHT", DefaultEditorKit.selectionEndLineAction);
+
+ result.put("ctrl P", DefaultEditorKit.upAction);
+ result.put("ctrl N", DefaultEditorKit.downAction);
+ // XXX: these are actually mapped to aqua-move-up and aqua-move-down
+ result.put("UP", DefaultEditorKit.upAction);
+ result.put("KP_UP", DefaultEditorKit.upAction);
+ result.put("DOWN", DefaultEditorKit.downAction);
+ result.put("KP_DOWN", DefaultEditorKit.downAction);
+ // XXX: these are actually mapped to aqua-page-up and aqua-page-down
+ result.put("PAGE_UP", DefaultEditorKit.pageUpAction);
+ result.put("PAGE_DOWN", DefaultEditorKit.pageDownAction);
+ result.put("ctrl V", DefaultEditorKit.pageDownAction);
+
+ result.put("shift PAGE_UP", TextComponentActions.SELECTION_PAGE_UP);
+ result.put("shift PAGE_DOWN", TextComponentActions.SELECTION_PAGE_DOWN);
+ result.put("meta shift PAGE_UP",
+ TextComponentActions.SELECTION_PAGE_LEFT);
+ result.put("meta shift PAGE_DOWN",
+ TextComponentActions.SELECTION_PAGE_RIGHT);
+ result.put("shift UP", DefaultEditorKit.selectionUpAction);
+ result.put("shift KP_UP", DefaultEditorKit.selectionUpAction);
+ result.put("shift DOWN", DefaultEditorKit.selectionDownAction);
+ result.put("shift KP_DOWN", DefaultEditorKit.selectionDownAction);
+
+ result.put("meta shift KP_UP", DefaultEditorKit.selectionBeginAction);
+ result.put("meta shift UP", DefaultEditorKit.selectionBeginAction);
+ result.put("shift HOME", DefaultEditorKit.selectionBeginAction);
+ result.put("meta shift KP_DOWN", DefaultEditorKit.selectionEndAction);
+ result.put("meta shift DOWN", DefaultEditorKit.selectionEndAction);
+ result.put("shift END", DefaultEditorKit.selectionEndAction);
+
+ result.put("shift alt KP_UP",
+ DefaultEditorKit.selectionBeginParagraphAction);
+ result.put("shift alt UP",
+ DefaultEditorKit.selectionBeginParagraphAction);
+ result.put("shift alt KP_DOWN",
+ DefaultEditorKit.selectionEndParagraphAction);
+ result.put("shift alt DOWN",
+ DefaultEditorKit.selectionEndParagraphAction);
+
+ result.put("ENTER", DefaultEditorKit.insertBreakAction);
+ result.put("BACK_SPACE", DefaultEditorKit.deletePrevCharAction);
+ result.put("ctrl H", DefaultEditorKit.deletePrevCharAction);
+ result.put("DELETE", DefaultEditorKit.deleteNextCharAction);
+ result.put("ctrl D", DefaultEditorKit.deleteNextCharAction);
+ result.put("alt DELETE", DefaultEditorKit.deleteNextWordAction);
+ result.put("alt BACK_SPACE", DefaultEditorKit.deletePrevWordAction);
+ result.put("ctrl W", DefaultEditorKit.deletePrevWordAction);
+
+ result.put("RIGHT", DefaultEditorKit.forwardAction);
+ result.put("KP_RIGHT", DefaultEditorKit.forwardAction);
+ result.put("ctrl F", DefaultEditorKit.forwardAction);
+ result.put("LEFT", DefaultEditorKit.backwardAction);
+ result.put("KP_LEFT", DefaultEditorKit.backwardAction);
+ result.put("ctrl B", DefaultEditorKit.backwardAction);
+ result.put("TAB", DefaultEditorKit.insertTabAction);
+ result.put("meta BACK_SLASH", TextComponentActions.UNSELECT);
+
+ result.put("meta KP_UP", DefaultEditorKit.beginAction);
+ result.put("meta UP", DefaultEditorKit.beginAction);
+ result.put("HOME", DefaultEditorKit.beginAction);
+ result.put("meta KP_DOWN", DefaultEditorKit.endAction);
+ result.put("meta DOWN", DefaultEditorKit.endAction);
+ result.put("END", DefaultEditorKit.endAction);
+
+ result.put("meta T", TextComponentActions.NEXT_LINK);
+ result.put("meta shift T", TextComponentActions.PREVIOUS_LINK);
+ result.put("meta SPACE", TextComponentActions.ACTIVATE_LINK);
+ result.put("control shift O",
+ TextComponentActions.TOGGLE_COMPONENT_ORIENTATION);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getFileChooserAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ESCAPE", FileChooserActions.CANCEL_SELECTION);
+ result.put("F5", FileChooserActions.REFRESH);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getListFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("meta C", COPY);
+ result.put("meta V", PASTE);
+ result.put("meta X", CUT);
+ result.put("COPY", COPY);
+ result.put("PASTE", PASTE);
+ result.put("CUT", CUT);
+
+ result.put("UP", ListActions.SELECT_PREVIOUS_ROW);
+ result.put("KP_UP", ListActions.SELECT_PREVIOUS_ROW);
+ result.put("shift UP", ListActions.SELECT_PREVIOUS_ROW_EXTEND);
+ result.put("shift KP_UP", ListActions.SELECT_PREVIOUS_ROW_EXTEND);
+
+ result.put("DOWN", ListActions.SELECT_NEXT_ROW);
+ result.put("KP_DOWN", ListActions.SELECT_NEXT_ROW);
+ result.put("shift DOWN", ListActions.SELECT_NEXT_ROW_EXTEND);
+ result.put("shift KP_DOWN", ListActions.SELECT_NEXT_ROW_EXTEND);
+
+ result.put("LEFT", ListActions.SELECT_PREVIOUS_COLUMN);
+ result.put("KP_LEFT", ListActions.SELECT_PREVIOUS_COLUMN);
+ result.put("shift LEFT", ListActions.SELECT_PREVIOUS_COLUMN_EXTEND);
+ result.put("shift KP_LEFT", ListActions.SELECT_PREVIOUS_COLUMN_EXTEND);
+
+ result.put("RIGHT", ListActions.SELECT_NEXT_COLUMN);
+ result.put("KP_RIGHT", ListActions.SELECT_NEXT_COLUMN);
+ result.put("shift RIGHT", ListActions.SELECT_NEXT_COLUMN_EXTEND);
+ result.put("shift KP_RIGHT", ListActions.SELECT_NEXT_COLUMN_EXTEND);
+
+ result.put("shift HOME", ListActions.SELECT_FIRST_ROW_EXTEND);
+ result.put("shift END", ListActions.SELECT_LAST_ROW_EXTEND);
+
+ result.put("shift PAGE_UP", ListActions.SCROLL_UP_EXTEND);
+ result.put("shift PAGE_DOWN", ListActions.SCROLL_DOWN_EXTEND);
+
+ result.put("meta A", ListActions.SELECT_ALL);
+
+ // XXX: missing:
+ // END -> aquaEnd
+ // HOME -> aquaHome
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getPasswordFieldFocusInputMap() {
+ return this.getSingleLineTextComponentFocusInputMap();
+ }
+
+ @Override
+ public SubstanceInputMap getScrollPaneAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("RIGHT", ScrollPaneActions.UNIT_SCROLL_RIGHT);
+ result.put("KP_RIGHT", ScrollPaneActions.UNIT_SCROLL_RIGHT);
+ result.put("DOWN", ScrollPaneActions.UNIT_SCROLL_DOWN);
+ result.put("KP_DOWN", ScrollPaneActions.UNIT_SCROLL_DOWN);
+ result.put("LEFT", ScrollPaneActions.UNIT_SCROLL_LEFT);
+ result.put("KP_LEFT", ScrollPaneActions.UNIT_SCROLL_LEFT);
+ result.put("UP", ScrollPaneActions.UNIT_SCROLL_UP);
+ result.put("KP_UP", ScrollPaneActions.UNIT_SCROLL_UP);
+
+ result.put("PAGE_UP", ScrollPaneActions.SCROLL_UP);
+ result.put("PAGE_DOWN", ScrollPaneActions.SCROLL_DOWN);
+ result.put("HOME", ScrollPaneActions.SCROLL_HOME);
+ result.put("END", ScrollPaneActions.SCROLL_END);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getSliderFocusInputMap() {
+ SubstanceInputMap result = super.getSliderFocusInputMap();
+ result.remove("ctrl PAGE_DOWN");
+ result.remove("ctrl PAGE_UP");
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getTableAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("meta C", COPY);
+ result.put("meta V", PASTE);
+ result.put("meta X", CUT);
+ result.put("COPY", COPY);
+ result.put("PASTE", PASTE);
+ result.put("CUT", CUT);
+
+ result.put("RIGHT", TableActions.NEXT_COLUMN);
+ result.put("KP_RIGHT", TableActions.NEXT_COLUMN);
+ result.put("shift RIGHT", TableActions.NEXT_COLUMN_EXTEND_SELECTION);
+ result.put("shift KP_RIGHT", TableActions.NEXT_COLUMN_EXTEND_SELECTION);
+
+ result.put("LEFT", TableActions.PREVIOUS_COLUMN);
+ result.put("KP_LEFT", TableActions.PREVIOUS_COLUMN);
+ result.put("shift LEFT", TableActions.PREVIOUS_COLUMN_EXTEND_SELECTION);
+ result.put("shift KP_LEFT",
+ TableActions.PREVIOUS_COLUMN_EXTEND_SELECTION);
+
+ result.put("DOWN", TableActions.NEXT_ROW);
+ result.put("KP_DOWN", TableActions.NEXT_ROW);
+ result.put("shift DOWN", TableActions.NEXT_ROW_EXTEND_SELECTION);
+ result.put("shift KP_DOWN", TableActions.NEXT_ROW_EXTEND_SELECTION);
+
+ result.put("UP", TableActions.PREVIOUS_ROW);
+ result.put("KP_UP", TableActions.PREVIOUS_ROW);
+ result.put("shift UP", TableActions.PREVIOUS_ROW_EXTEND_SELECTION);
+ result.put("shift KP_UP", TableActions.PREVIOUS_ROW_EXTEND_SELECTION);
+
+ result.put("HOME", TableActions.FIRST_COLUMN);
+ result.put("shift HOME", TableActions.FIRST_COLUMN_EXTEND_SELECTION);
+ result.put("END", TableActions.LAST_COLUMN);
+ result.put("shift END", TableActions.LAST_COLUMN_EXTEND_SELECTION);
+
+ result.put("PAGE_UP", TableActions.SCROLL_UP_CHANGE_SELECTION);
+ result.put("shift PAGE_UP", TableActions.SCROLL_UP_EXTEND_SELECTION);
+ result.put("PAGE_DOWN", TableActions.SCROLL_DOWN_CHANGE_SELECTION);
+ result
+ .put("shift PAGE_DOWN",
+ TableActions.SCROLL_DOWN_EXTEND_SELECTION);
+
+ result.put("TAB", TableActions.NEXT_COLUMN_CELL);
+ result.put("shift TAB", TableActions.PREVIOUS_COLUMN_CELL);
+ result.put("ENTER", TableActions.NEXT_ROW_CELL);
+ result.put("shift ENTER", TableActions.PREVIOUS_ROW_CELL);
+ result.put("meta A", TableActions.SELECT_ALL);
+
+ result.put("alt TAB", TableActions.FOCUS_HEADER);
+ result.put("shift alt TAB", TableActions.FOCUS_HEADER);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getTextFieldFocusInputMap() {
+ return this.getSingleLineTextComponentFocusInputMap();
+ }
+
+ @Override
+ public SubstanceInputMap getTreeFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("meta C", COPY);
+ result.put("meta V", PASTE);
+ result.put("meta X", CUT);
+ result.put("COPY", COPY);
+ result.put("PASTE", PASTE);
+ result.put("CUT", CUT);
+
+ result.put("UP", TreeActions.SELECT_PREVIOUS);
+ result.put("KP_UP", TreeActions.SELECT_PREVIOUS);
+ result.put("shift UP", TreeActions.SELECT_PREVIOUS_EXTEND_SELECTION);
+ result.put("shift KP_UP", TreeActions.SELECT_PREVIOUS_EXTEND_SELECTION);
+
+ result.put("DOWN", TreeActions.SELECT_NEXT);
+ result.put("KP_DOWN", TreeActions.SELECT_NEXT);
+ result.put("shift DOWN", TreeActions.SELECT_NEXT_EXTEND_SELECTION);
+ result.put("shift KP_DOWN", TreeActions.SELECT_NEXT_EXTEND_SELECTION);
+
+ result.put("ctrl A", TreeActions.SELECT_ALL);
+
+ // XXX: in Aqua this is mapped to aquaExpandNode
+ result.put("RIGHT", TreeActions.EXPAND);
+ result.put("KP_RIGHT", TreeActions.EXPAND);
+ result.put("ctrl RIGHT", TreeActions.EXPAND);
+ result.put("ctrl KP_RIGHT", TreeActions.EXPAND);
+ result.put("shift RIGHT", TreeActions.EXPAND);
+ result.put("shift KP_RIGHT", TreeActions.EXPAND);
+
+ // XXX: in Aqua this is mapped to aquaCollapseNode
+ result.put("LEFT", TreeActions.COLLAPSE);
+ result.put("KP_LEFT", TreeActions.COLLAPSE);
+ result.put("ctrl LEFT", TreeActions.COLLAPSE);
+ result.put("ctrl KP_LEFT", TreeActions.COLLAPSE);
+ result.put("shift LEFT", TreeActions.COLLAPSE);
+ result.put("shift KP_LEFT", TreeActions.COLLAPSE);
+
+ // XXX: in Aqua there are mappings to aquaFullyCollapseNode and
+ // aquaFullyExpandNode
+
+ return result;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/inputmaps/BaseInputMapSet.java b/substance/src/main/java/org/pushingpixels/substance/internal/inputmaps/BaseInputMapSet.java
new file mode 100644
index 0000000..9fb6e24
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/inputmaps/BaseInputMapSet.java
@@ -0,0 +1,1219 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.inputmaps;
+
+import javax.swing.*;
+import javax.swing.text.DefaultEditorKit;
+
+import org.pushingpixels.substance.api.inputmaps.InputMapSet;
+import org.pushingpixels.substance.api.inputmaps.SubstanceInputMap;
+
+public class BaseInputMapSet implements InputMapSet {
+ public static final String PRESSED = "pressed";
+
+ public static final String RELEASED = "released";
+
+ public static final String COPY = (String) TransferHandler.getCopyAction()
+ .getValue(Action.NAME);
+
+ public static final String PASTE = (String) TransferHandler
+ .getPasteAction().getValue(Action.NAME);
+
+ public static final String CUT = (String) TransferHandler.getCutAction()
+ .getValue(Action.NAME);
+
+ protected SubstanceInputMap getActionControlFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+ result.put("SPACE", PRESSED);
+ result.put("released SPACE", RELEASED);
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getButtonFocusInputMap() {
+ return this.getActionControlFocusInputMap();
+ }
+
+ @Override
+ public SubstanceInputMap getCheckBoxFocusInputMap() {
+ return this.getActionControlFocusInputMap();
+ }
+
+ /**
+ * Taken from BasicComboBoxUI.Actions
+ */
+ protected static class ComboActions {
+ public static final String HIDE = "hidePopup";
+ public static final String DOWN = "selectNext";
+ public static final String DOWN_2 = "selectNext2";
+ public static final String TOGGLE = "togglePopup";
+ public static final String TOGGLE_2 = "spacePopup";
+ public static final String UP = "selectPrevious";
+ public static final String UP_2 = "selectPrevious2";
+ public static final String ENTER = "enterPressed";
+ public static final String PAGE_DOWN = "pageDownPassThrough";
+ public static final String PAGE_UP = "pageUpPassThrough";
+ public static final String HOME = "homePassThrough";
+ public static final String END = "endPassThrough";
+ }
+
+ @Override
+ public SubstanceInputMap getComboBoxAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ESCAPE", ComboActions.HIDE);
+ result.put("PAGE_UP", ComboActions.PAGE_UP);
+ result.put("PAGE_DOWN", ComboActions.PAGE_DOWN);
+ result.put("HOME", ComboActions.HOME);
+ result.put("END", ComboActions.END);
+ result.put("DOWN", ComboActions.DOWN);
+ result.put("KP_DOWN", ComboActions.DOWN);
+ result.put("alt DOWN", ComboActions.TOGGLE);
+ result.put("alt KP_DOWN", ComboActions.TOGGLE);
+ result.put("alt UP", ComboActions.TOGGLE);
+ result.put("alt KP_UP", ComboActions.TOGGLE);
+ result.put("SPACE", ComboActions.TOGGLE_2);
+ result.put("ENTER", ComboActions.ENTER);
+ result.put("UP", ComboActions.UP);
+ result.put("KP_UP", ComboActions.UP);
+
+ return result;
+ };
+
+ /**
+ * Taken from BasicDesktopPaneUI.Actions
+ */
+ protected static class DesktopPaneActions {
+ public static String CLOSE = "close";
+ public static String ESCAPE = "escape";
+ public static String MAXIMIZE = "maximize";
+ public static String MINIMIZE = "minimize";
+ public static String MOVE = "move";
+ public static String RESIZE = "resize";
+ public static String RESTORE = "restore";
+ public static String LEFT = "left";
+ public static String RIGHT = "right";
+ public static String UP = "up";
+ public static String DOWN = "down";
+ public static String SHRINK_LEFT = "shrinkLeft";
+ public static String SHRINK_RIGHT = "shrinkRight";
+ public static String SHRINK_UP = "shrinkUp";
+ public static String SHRINK_DOWN = "shrinkDown";
+ public static String NEXT_FRAME = "selectNextFrame";
+ public static String PREVIOUS_FRAME = "selectPreviousFrame";
+ public static String NAVIGATE_NEXT = "navigateNext";
+ public static String NAVIGATE_PREVIOUS = "navigatePrevious";
+ }
+
+ @Override
+ public SubstanceInputMap getDesktopAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ctrl F5", DesktopPaneActions.RESTORE);
+ result.put("ctrl F4", DesktopPaneActions.CLOSE);
+ result.put("ctrl F7", DesktopPaneActions.MOVE);
+ result.put("ctrl F8", DesktopPaneActions.RESIZE);
+
+ result.put("RIGHT", DesktopPaneActions.RIGHT);
+ result.put("KP_RIGHT", DesktopPaneActions.RIGHT);
+ result.put("shift RIGHT", DesktopPaneActions.SHRINK_RIGHT);
+ result.put("shift KP_RIGHT", DesktopPaneActions.SHRINK_RIGHT);
+ result.put("LEFT", DesktopPaneActions.LEFT);
+ result.put("KP_LEFT", DesktopPaneActions.LEFT);
+ result.put("shift LEFT", DesktopPaneActions.SHRINK_LEFT);
+ result.put("shift KP_LEFT", DesktopPaneActions.SHRINK_LEFT);
+
+ result.put("UP", DesktopPaneActions.UP);
+ result.put("KP_UP", DesktopPaneActions.UP);
+ result.put("shift UP", DesktopPaneActions.SHRINK_UP);
+ result.put("shift KP_UP", DesktopPaneActions.SHRINK_UP);
+ result.put("DOWN", DesktopPaneActions.DOWN);
+ result.put("KP_DOWN", DesktopPaneActions.DOWN);
+ result.put("shift DOWN", DesktopPaneActions.SHRINK_DOWN);
+ result.put("shift KP_DOWN", DesktopPaneActions.SHRINK_DOWN);
+
+ result.put("ESCAPE", DesktopPaneActions.ESCAPE);
+ result.put("ctrl F9", DesktopPaneActions.MINIMIZE);
+ result.put("ctrl F10", DesktopPaneActions.MAXIMIZE);
+
+ result.put("ctrl F6", DesktopPaneActions.NEXT_FRAME);
+ result.put("ctrl TAB", DesktopPaneActions.NEXT_FRAME);
+ result.put("ctrl alt F6", DesktopPaneActions.NEXT_FRAME);
+ result.put("shift ctrl alt F6", DesktopPaneActions.PREVIOUS_FRAME);
+ result.put("ctrl F12", DesktopPaneActions.NAVIGATE_NEXT);
+ result.put("shift ctrl F12", DesktopPaneActions.NAVIGATE_PREVIOUS);
+
+ return result;
+ }
+
+ protected static class TextComponentActions {
+ // from DefaultEditorKit
+ public static final String SELECTION_PAGE_UP = "selection-page-up";
+ public static final String SELECTION_PAGE_DOWN = "selection-page-down";
+ public static final String SELECTION_PAGE_LEFT = "selection-page-left";
+ public static final String SELECTION_PAGE_RIGHT = "selection-page-right";
+ public static final String UNSELECT = "unselect";
+ public static final String TOGGLE_COMPONENT_ORIENTATION = "toggle-componentOrientation";
+
+ // from HTMLEditorKit
+ public static final String NEXT_LINK = "next-link-action";
+ public static final String PREVIOUS_LINK = "previous-link-action";
+ public static final String ACTIVATE_LINK = "activate-link-action";
+
+ // from JFormattedTextField.CancelAction
+ public static final String RESET_FIELD_EDIT = "reset-field-edit";
+
+ // from BasicSpinnerUI.loadActionMap
+ public static final String INCREMENT = "increment";
+ public static final String DECREMENT = "decrement";
+ }
+
+ protected SubstanceInputMap getMultilineTextComponentFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ctrl C", DefaultEditorKit.copyAction);
+ result.put("ctrl V", DefaultEditorKit.pasteAction);
+ result.put("ctrl X", DefaultEditorKit.cutAction);
+ result.put("COPY", DefaultEditorKit.copyAction);
+ result.put("PASTE", DefaultEditorKit.pasteAction);
+ result.put("CUT", DefaultEditorKit.cutAction);
+ result.put("control INSERT", DefaultEditorKit.copyAction);
+ result.put("shift INSERT", DefaultEditorKit.pasteAction);
+ result.put("shift DELETE", DefaultEditorKit.cutAction);
+
+ result.put("shift LEFT", DefaultEditorKit.selectionBackwardAction);
+ result.put("shift KP_LEFT", DefaultEditorKit.selectionBackwardAction);
+ result.put("shift RIGHT", DefaultEditorKit.selectionForwardAction);
+ result.put("shift KP_RIGHT", DefaultEditorKit.selectionForwardAction);
+ result.put("ctrl LEFT", DefaultEditorKit.previousWordAction);
+ result.put("ctrl KP_LEFT", DefaultEditorKit.previousWordAction);
+ result.put("ctrl RIGHT", DefaultEditorKit.nextWordAction);
+ result.put("ctrl KP_RIGHT", DefaultEditorKit.nextWordAction);
+ result.put("ctrl shift LEFT",
+ DefaultEditorKit.selectionPreviousWordAction);
+ result.put("ctrl shift KP_LEFT",
+ DefaultEditorKit.selectionPreviousWordAction);
+ result
+ .put("ctrl shift RIGHT",
+ DefaultEditorKit.selectionNextWordAction);
+ result.put("ctrl shift KP_RIGHT",
+ DefaultEditorKit.selectionNextWordAction);
+
+ result.put("ctrl A", DefaultEditorKit.selectAllAction);
+ result.put("HOME", DefaultEditorKit.beginLineAction);
+ result.put("END", DefaultEditorKit.endLineAction);
+ result.put("shift HOME", DefaultEditorKit.selectionBeginLineAction);
+ result.put("shift END", DefaultEditorKit.selectionEndLineAction);
+
+ result.put("UP", DefaultEditorKit.upAction);
+ result.put("KP_UP", DefaultEditorKit.upAction);
+ result.put("DOWN", DefaultEditorKit.downAction);
+ result.put("KP_DOWN", DefaultEditorKit.downAction);
+ result.put("PAGE_UP", DefaultEditorKit.pageUpAction);
+ result.put("PAGE_DOWN", DefaultEditorKit.pageDownAction);
+
+ result.put("shift PAGE_UP", TextComponentActions.SELECTION_PAGE_UP);
+ result.put("shift PAGE_DOWN", TextComponentActions.SELECTION_PAGE_DOWN);
+ result.put("ctrl shift PAGE_UP",
+ TextComponentActions.SELECTION_PAGE_LEFT);
+ result.put("ctrl shift PAGE_DOWN",
+ TextComponentActions.SELECTION_PAGE_RIGHT);
+ result.put("shift UP", DefaultEditorKit.selectionUpAction);
+ result.put("shift KP_UP", DefaultEditorKit.selectionUpAction);
+ result.put("shift DOWN", DefaultEditorKit.selectionDownAction);
+ result.put("shift KP_DOWN", DefaultEditorKit.selectionDownAction);
+
+ result.put("ctrl shift HOME", DefaultEditorKit.selectionBeginAction);
+ result.put("ctrl shift END", DefaultEditorKit.selectionEndAction);
+
+ result.put("ENTER", DefaultEditorKit.insertBreakAction);
+ result.put("BACK_SPACE", DefaultEditorKit.deletePrevCharAction);
+ result.put("shift BACK_SPACE", DefaultEditorKit.deletePrevCharAction);
+ result.put("ctrl H", DefaultEditorKit.deletePrevCharAction);
+ result.put("DELETE", DefaultEditorKit.deleteNextCharAction);
+ result.put("ctrl DELETE", DefaultEditorKit.deleteNextWordAction);
+ result.put("ctrl BACK_SPACE", DefaultEditorKit.deletePrevWordAction);
+
+ result.put("RIGHT", DefaultEditorKit.forwardAction);
+ result.put("KP_RIGHT", DefaultEditorKit.forwardAction);
+ result.put("LEFT", DefaultEditorKit.backwardAction);
+ result.put("KP_LEFT", DefaultEditorKit.backwardAction);
+ result.put("TAB", DefaultEditorKit.insertTabAction);
+ result.put("ctrl BACK_SLASH", TextComponentActions.UNSELECT);
+ result.put("ctrl HOME", DefaultEditorKit.beginAction);
+ result.put("ctrl END", DefaultEditorKit.endAction);
+
+ result.put("ctrl T", TextComponentActions.NEXT_LINK);
+ result.put("ctrl shift T", TextComponentActions.PREVIOUS_LINK);
+ result.put("ctrl SPACE", TextComponentActions.ACTIVATE_LINK);
+ result.put("control shift O",
+ TextComponentActions.TOGGLE_COMPONENT_ORIENTATION);
+
+ return result;
+ };
+
+ @Override
+ public SubstanceInputMap getEditorPaneFocusInputMap() {
+ return this.getMultilineTextComponentFocusInputMap();
+ }
+
+ /**
+ * From sun.swing.FilePane via BasicFileChooserUI
+ */
+ protected static class FileChooserActions {
+ public static final String APPROVE_SELECTION = "approveSelection";
+ public static final String CANCEL_SELECTION = "cancelSelection";
+ public static final String EDIT_FILE_NAME = "editFileName";
+ public static final String REFRESH = "refresh";
+ public static final String GO_UP = "Go Up";
+ }
+
+ @Override
+ public SubstanceInputMap getFileChooserAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ESCAPE", FileChooserActions.CANCEL_SELECTION);
+ result.put("F2", FileChooserActions.EDIT_FILE_NAME);
+ result.put("F5", FileChooserActions.REFRESH);
+ result.put("BACK_SPACE", FileChooserActions.GO_UP);
+ result.put("ENTER", FileChooserActions.APPROVE_SELECTION);
+ result.put("ctrl ENTER", FileChooserActions.APPROVE_SELECTION);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getFormattedTextFieldFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ctrl C", DefaultEditorKit.copyAction);
+ result.put("ctrl V", DefaultEditorKit.pasteAction);
+ result.put("ctrl X", DefaultEditorKit.cutAction);
+ result.put("COPY", DefaultEditorKit.copyAction);
+ result.put("PASTE", DefaultEditorKit.pasteAction);
+ result.put("CUT", DefaultEditorKit.cutAction);
+ result.put("control INSERT", DefaultEditorKit.copyAction);
+ result.put("shift INSERT", DefaultEditorKit.pasteAction);
+ result.put("shift DELETE", DefaultEditorKit.cutAction);
+
+ result.put("shift LEFT", DefaultEditorKit.selectionBackwardAction);
+ result.put("shift KP_LEFT", DefaultEditorKit.selectionBackwardAction);
+ result.put("shift RIGHT", DefaultEditorKit.selectionForwardAction);
+ result.put("shift KP_RIGHT", DefaultEditorKit.selectionForwardAction);
+
+ result.put("ctrl LEFT", DefaultEditorKit.previousWordAction);
+ result.put("ctrl KP_LEFT", DefaultEditorKit.previousWordAction);
+ result.put("ctrl RIGHT", DefaultEditorKit.nextWordAction);
+ result.put("ctrl KP_RIGHT", DefaultEditorKit.nextWordAction);
+ result.put("ctrl shift LEFT",
+ DefaultEditorKit.selectionPreviousWordAction);
+ result.put("ctrl shift KP_LEFT",
+ DefaultEditorKit.selectionPreviousWordAction);
+ result
+ .put("ctrl shift RIGHT",
+ DefaultEditorKit.selectionNextWordAction);
+ result.put("ctrl shift KP_RIGHT",
+ DefaultEditorKit.selectionNextWordAction);
+
+ result.put("ctrl A", DefaultEditorKit.selectAllAction);
+ result.put("HOME", DefaultEditorKit.beginLineAction);
+ result.put("END", DefaultEditorKit.endLineAction);
+ result.put("shift HOME", DefaultEditorKit.selectionBeginLineAction);
+ result.put("shift END", DefaultEditorKit.selectionEndLineAction);
+
+ result.put("BACK_SPACE", DefaultEditorKit.deletePrevCharAction);
+ result.put("shift BACK_SPACE", DefaultEditorKit.deletePrevCharAction);
+ result.put("ctrl H", DefaultEditorKit.deletePrevCharAction);
+ result.put("DELETE", DefaultEditorKit.deleteNextCharAction);
+ result.put("ctrl DELETE", DefaultEditorKit.deleteNextWordAction);
+ result.put("ctrl BACK_SPACE", DefaultEditorKit.deletePrevWordAction);
+ result.put("RIGHT", DefaultEditorKit.forwardAction);
+ result.put("LEFT", DefaultEditorKit.backwardAction);
+ result.put("KP_RIGHT", DefaultEditorKit.forwardAction);
+ result.put("KP_LEFT", DefaultEditorKit.backwardAction);
+
+ result.put("ENTER", JTextField.notifyAction);
+ result.put("ctrl BACK_SLASH", TextComponentActions.UNSELECT);
+ result.put("control shift O",
+ TextComponentActions.TOGGLE_COMPONENT_ORIENTATION);
+ result.put("ESCAPE", TextComponentActions.RESET_FIELD_EDIT);
+ result.put("UP", TextComponentActions.INCREMENT);
+ result.put("KP_UP", TextComponentActions.INCREMENT);
+ result.put("DOWN", TextComponentActions.DECREMENT);
+ result.put("KP_DOWN", TextComponentActions.DECREMENT);
+
+ return result;
+ }
+
+ /**
+ * From BasicListUI.Actions
+ */
+ protected static class ListActions {
+ public static final String SELECT_PREVIOUS_COLUMN = "selectPreviousColumn";
+ public static final String SELECT_PREVIOUS_COLUMN_EXTEND = "selectPreviousColumnExtendSelection";
+ public static final String SELECT_PREVIOUS_COLUMN_CHANGE_LEAD = "selectPreviousColumnChangeLead";
+ public static final String SELECT_NEXT_COLUMN = "selectNextColumn";
+ public static final String SELECT_NEXT_COLUMN_EXTEND = "selectNextColumnExtendSelection";
+ public static final String SELECT_NEXT_COLUMN_CHANGE_LEAD = "selectNextColumnChangeLead";
+ public static final String SELECT_PREVIOUS_ROW = "selectPreviousRow";
+ public static final String SELECT_PREVIOUS_ROW_EXTEND = "selectPreviousRowExtendSelection";
+ public static final String SELECT_PREVIOUS_ROW_CHANGE_LEAD = "selectPreviousRowChangeLead";
+ public static final String SELECT_NEXT_ROW = "selectNextRow";
+ public static final String SELECT_NEXT_ROW_EXTEND = "selectNextRowExtendSelection";
+ public static final String SELECT_NEXT_ROW_CHANGE_LEAD = "selectNextRowChangeLead";
+ public static final String SELECT_FIRST_ROW = "selectFirstRow";
+ public static final String SELECT_FIRST_ROW_EXTEND = "selectFirstRowExtendSelection";
+ public static final String SELECT_FIRST_ROW_CHANGE_LEAD = "selectFirstRowChangeLead";
+ public static final String SELECT_LAST_ROW = "selectLastRow";
+ public static final String SELECT_LAST_ROW_EXTEND = "selectLastRowExtendSelection";
+ public static final String SELECT_LAST_ROW_CHANGE_LEAD = "selectLastRowChangeLead";
+ public static final String SCROLL_UP = "scrollUp";
+ public static final String SCROLL_UP_EXTEND = "scrollUpExtendSelection";
+ public static final String SCROLL_UP_CHANGE_LEAD = "scrollUpChangeLead";
+ public static final String SCROLL_DOWN = "scrollDown";
+ public static final String SCROLL_DOWN_EXTEND = "scrollDownExtendSelection";
+ public static final String SCROLL_DOWN_CHANGE_LEAD = "scrollDownChangeLead";
+ public static final String SELECT_ALL = "selectAll";
+ public static final String CLEAR_SELECTION = "clearSelection";
+
+ public static final String ADD_TO_SELECTION = "addToSelection";
+ public static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
+
+ public static final String EXTEND_TO = "extendTo";
+
+ public static final String MOVE_SELECTION_TO = "moveSelectionTo";
+
+ }
+
+ @Override
+ public SubstanceInputMap getListFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ctrl C", COPY);
+ result.put("ctrl V", PASTE);
+ result.put("ctrl X", CUT);
+ result.put("COPY", COPY);
+ result.put("PASTE", PASTE);
+ result.put("CUT", CUT);
+ result.put("control INSERT", COPY);
+ result.put("shift INSERT", PASTE);
+ result.put("shift DELETE", CUT);
+
+ result.put("UP", ListActions.SELECT_PREVIOUS_ROW);
+ result.put("KP_UP", ListActions.SELECT_PREVIOUS_ROW);
+ result.put("shift UP", ListActions.SELECT_PREVIOUS_ROW_EXTEND);
+ result.put("shift KP_UP", ListActions.SELECT_PREVIOUS_ROW_EXTEND);
+ result.put("ctrl shift UP", ListActions.SELECT_PREVIOUS_ROW_EXTEND);
+ result.put("ctrl shift KP_UP", ListActions.SELECT_PREVIOUS_ROW_EXTEND);
+ result.put("ctrl UP", ListActions.SELECT_PREVIOUS_ROW_CHANGE_LEAD);
+ result.put("ctrl KP_UP", ListActions.SELECT_PREVIOUS_ROW_CHANGE_LEAD);
+
+ result.put("DOWN", ListActions.SELECT_NEXT_ROW);
+ result.put("KP_DOWN", ListActions.SELECT_NEXT_ROW);
+ result.put("shift DOWN", ListActions.SELECT_NEXT_ROW_EXTEND);
+ result.put("shift KP_DOWN", ListActions.SELECT_NEXT_ROW_EXTEND);
+ result.put("ctrl shift DOWN", ListActions.SELECT_NEXT_ROW_EXTEND);
+ result.put("ctrl shift KP_DOWN", ListActions.SELECT_NEXT_ROW_EXTEND);
+ result.put("ctrl DOWN", ListActions.SELECT_NEXT_ROW_CHANGE_LEAD);
+ result.put("ctrl KP_DOWN", ListActions.SELECT_NEXT_ROW_CHANGE_LEAD);
+
+ result.put("LEFT", ListActions.SELECT_PREVIOUS_COLUMN);
+ result.put("KP_LEFT", ListActions.SELECT_PREVIOUS_COLUMN);
+ result.put("shift LEFT", ListActions.SELECT_PREVIOUS_COLUMN_EXTEND);
+ result.put("shift KP_LEFT", ListActions.SELECT_PREVIOUS_COLUMN_EXTEND);
+ result
+ .put("ctrl shift LEFT",
+ ListActions.SELECT_PREVIOUS_COLUMN_EXTEND);
+ result.put("ctrl shift KP_LEFT",
+ ListActions.SELECT_PREVIOUS_COLUMN_EXTEND);
+ result.put("ctrl LEFT", ListActions.SELECT_PREVIOUS_COLUMN_CHANGE_LEAD);
+ result.put("ctrl KP_LEFT",
+ ListActions.SELECT_PREVIOUS_COLUMN_CHANGE_LEAD);
+
+ result.put("RIGHT", ListActions.SELECT_NEXT_COLUMN);
+ result.put("KP_RIGHT", ListActions.SELECT_NEXT_COLUMN);
+ result.put("shift RIGHT", ListActions.SELECT_NEXT_COLUMN_EXTEND);
+ result.put("shift KP_RIGHT", ListActions.SELECT_NEXT_COLUMN_EXTEND);
+ result.put("ctrl shift RIGHT", ListActions.SELECT_NEXT_COLUMN_EXTEND);
+ result
+ .put("ctrl shift KP_RIGHT",
+ ListActions.SELECT_NEXT_COLUMN_EXTEND);
+ result.put("ctrl RIGHT", ListActions.SELECT_NEXT_COLUMN_CHANGE_LEAD);
+ result.put("ctrl KP_RIGHT", ListActions.SELECT_NEXT_COLUMN_CHANGE_LEAD);
+
+ result.put("HOME", ListActions.SELECT_FIRST_ROW);
+ result.put("shift HOME", ListActions.SELECT_FIRST_ROW_EXTEND);
+ result.put("ctrl shift HOME", ListActions.SELECT_FIRST_ROW_EXTEND);
+ result.put("ctrl HOME", ListActions.SELECT_FIRST_ROW_CHANGE_LEAD);
+ result.put("END", ListActions.SELECT_LAST_ROW);
+ result.put("shift END", ListActions.SELECT_LAST_ROW_EXTEND);
+ result.put("ctrl shift END", ListActions.SELECT_LAST_ROW_EXTEND);
+ result.put("ctrl END", ListActions.SELECT_LAST_ROW_CHANGE_LEAD);
+
+ result.put("PAGE_UP", ListActions.SCROLL_UP);
+ result.put("shift PAGE_UP", ListActions.SCROLL_UP_EXTEND);
+ result.put("ctrl shift PAGE_UP", ListActions.SCROLL_UP_EXTEND);
+ result.put("ctrl PAGE_UP", ListActions.SCROLL_UP_CHANGE_LEAD);
+ result.put("PAGE_DOWN", ListActions.SCROLL_DOWN);
+ result.put("shift PAGE_DOWN", ListActions.SCROLL_DOWN_EXTEND);
+ result.put("ctrl shift PAGE_DOWN", ListActions.SCROLL_DOWN_EXTEND);
+ result.put("ctrl PAGE_DOWN", ListActions.SCROLL_DOWN_CHANGE_LEAD);
+
+ result.put("ctrl A", ListActions.SELECT_ALL);
+ result.put("ctrl SLASH", ListActions.SELECT_ALL);
+ result.put("ctrl BACK_SLASH", ListActions.CLEAR_SELECTION);
+ result.put("SPACE", ListActions.ADD_TO_SELECTION);
+ result.put("ctrl SPACE", ListActions.TOGGLE_AND_ANCHOR);
+ result.put("shift SPACE", ListActions.EXTEND_TO);
+ result.put("ctrl shift SPACE", ListActions.MOVE_SELECTION_TO);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getPasswordFieldFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ctrl C", DefaultEditorKit.copyAction);
+ result.put("ctrl V", DefaultEditorKit.pasteAction);
+ result.put("ctrl X", DefaultEditorKit.cutAction);
+ result.put("COPY", DefaultEditorKit.copyAction);
+ result.put("PASTE", DefaultEditorKit.pasteAction);
+ result.put("CUT", DefaultEditorKit.cutAction);
+ result.put("control INSERT", DefaultEditorKit.copyAction);
+ result.put("shift INSERT", DefaultEditorKit.pasteAction);
+ result.put("shift DELETE", DefaultEditorKit.cutAction);
+
+ result.put("shift LEFT", DefaultEditorKit.selectionBackwardAction);
+ result.put("shift KP_LEFT", DefaultEditorKit.selectionBackwardAction);
+ result.put("shift RIGHT", DefaultEditorKit.selectionForwardAction);
+ result.put("shift KP_RIGHT", DefaultEditorKit.selectionForwardAction);
+
+ result.put("ctrl LEFT", DefaultEditorKit.beginLineAction);
+ result.put("ctrl KP_LEFT", DefaultEditorKit.beginLineAction);
+ result.put("ctrl RIGHT", DefaultEditorKit.endLineAction);
+ result.put("ctrl KP_RIGHT", DefaultEditorKit.endLineAction);
+ result
+ .put("ctrl shift LEFT",
+ DefaultEditorKit.selectionBeginLineAction);
+ result.put("ctrl shift KP_LEFT",
+ DefaultEditorKit.selectionBeginLineAction);
+ result.put("ctrl shift RIGHT", DefaultEditorKit.selectionEndLineAction);
+ result.put("ctrl shift KP_RIGHT",
+ DefaultEditorKit.selectionEndLineAction);
+
+ result.put("ctrl A", DefaultEditorKit.selectAllAction);
+ result.put("HOME", DefaultEditorKit.beginLineAction);
+ result.put("END", DefaultEditorKit.endLineAction);
+ result.put("shift HOME", DefaultEditorKit.selectionBeginLineAction);
+ result.put("shift END", DefaultEditorKit.selectionEndLineAction);
+ result.put("BACK_SPACE", DefaultEditorKit.deletePrevCharAction);
+ result.put("shift BACK_SPACE", DefaultEditorKit.deletePrevCharAction);
+ result.put("ctrl H", DefaultEditorKit.deletePrevCharAction);
+ result.put("DELETE", DefaultEditorKit.deleteNextCharAction);
+ result.put("RIGHT", DefaultEditorKit.forwardAction);
+ result.put("LEFT", DefaultEditorKit.backwardAction);
+ result.put("KP_RIGHT", DefaultEditorKit.forwardAction);
+ result.put("KP_LEFT", DefaultEditorKit.backwardAction);
+ result.put("ENTER", JTextField.notifyAction);
+ result.put("ctrl BACK_SLASH", TextComponentActions.UNSELECT);
+ result.put("control shift O",
+ TextComponentActions.TOGGLE_COMPONENT_ORIENTATION);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getRadioButtonFocusInputMap() {
+ return this.getActionControlFocusInputMap();
+ }
+
+ /**
+ * From BasicRootPaneUI.Actions
+ */
+ protected static class RootPaneActions {
+ public static final String POST_POPUP = "postPopup";
+ }
+
+ @Override
+ public SubstanceInputMap getRootPaneAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("shift F10", RootPaneActions.POST_POPUP);
+ result.put("CONTEXT_MENU", RootPaneActions.POST_POPUP);
+
+ return result;
+ }
+
+ /**
+ * From BasicScrollBarUI.Actions
+ */
+ protected static class ScrollBarActions {
+ public static final String POSITIVE_UNIT_INCREMENT = "positiveUnitIncrement";
+ public static final String POSITIVE_BLOCK_INCREMENT = "positiveBlockIncrement";
+ public static final String NEGATIVE_UNIT_INCREMENT = "negativeUnitIncrement";
+ public static final String NEGATIVE_BLOCK_INCREMENT = "negativeBlockIncrement";
+ public static final String MIN_SCROLL = "minScroll";
+ public static final String MAX_SCROLL = "maxScroll";
+ }
+
+ @Override
+ public SubstanceInputMap getScrollBarAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("RIGHT", ScrollBarActions.POSITIVE_UNIT_INCREMENT);
+ result.put("KP_RIGHT", ScrollBarActions.POSITIVE_UNIT_INCREMENT);
+ result.put("DOWN", ScrollBarActions.POSITIVE_UNIT_INCREMENT);
+ result.put("KP_DOWN", ScrollBarActions.POSITIVE_UNIT_INCREMENT);
+ result.put("PAGE_DOWN", ScrollBarActions.POSITIVE_BLOCK_INCREMENT);
+
+ result.put("LEFT", ScrollBarActions.NEGATIVE_UNIT_INCREMENT);
+ result.put("KP_LEFT", ScrollBarActions.NEGATIVE_UNIT_INCREMENT);
+ result.put("UP", ScrollBarActions.NEGATIVE_UNIT_INCREMENT);
+ result.put("KP_UP", ScrollBarActions.NEGATIVE_UNIT_INCREMENT);
+ result.put("PAGE_UP", ScrollBarActions.NEGATIVE_BLOCK_INCREMENT);
+
+ result.put("HOME", ScrollBarActions.MIN_SCROLL);
+ result.put("END", ScrollBarActions.MAX_SCROLL);
+
+ return result;
+ }
+
+ /**
+ * From BasicScrollPaneUI.Actions
+ */
+ protected static class ScrollPaneActions {
+ public static final String SCROLL_UP = "scrollUp";
+ public static final String SCROLL_DOWN = "scrollDown";
+ public static final String SCROLL_HOME = "scrollHome";
+ public static final String SCROLL_END = "scrollEnd";
+ public static final String UNIT_SCROLL_UP = "unitScrollUp";
+ public static final String UNIT_SCROLL_DOWN = "unitScrollDown";
+ public static final String SCROLL_LEFT = "scrollLeft";
+ public static final String SCROLL_RIGHT = "scrollRight";
+ public static final String UNIT_SCROLL_LEFT = "unitScrollLeft";
+ public static final String UNIT_SCROLL_RIGHT = "unitScrollRight";
+ }
+
+ @Override
+ public SubstanceInputMap getScrollPaneAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("RIGHT", ScrollPaneActions.UNIT_SCROLL_RIGHT);
+ result.put("KP_RIGHT", ScrollPaneActions.UNIT_SCROLL_RIGHT);
+ result.put("DOWN", ScrollPaneActions.UNIT_SCROLL_DOWN);
+ result.put("KP_DOWN", ScrollPaneActions.UNIT_SCROLL_DOWN);
+ result.put("LEFT", ScrollPaneActions.UNIT_SCROLL_LEFT);
+ result.put("KP_LEFT", ScrollPaneActions.UNIT_SCROLL_LEFT);
+ result.put("UP", ScrollPaneActions.UNIT_SCROLL_UP);
+ result.put("KP_UP", ScrollPaneActions.UNIT_SCROLL_UP);
+
+ result.put("PAGE_UP", ScrollPaneActions.SCROLL_UP);
+ result.put("PAGE_DOWN", ScrollPaneActions.SCROLL_DOWN);
+ result.put("ctrl PAGE_UP", ScrollPaneActions.SCROLL_LEFT);
+ result.put("ctrl PAGE_DOWN", ScrollPaneActions.SCROLL_RIGHT);
+ result.put("ctrl HOME", ScrollPaneActions.SCROLL_HOME);
+ result.put("ctrl END", ScrollPaneActions.SCROLL_END);
+
+ return result;
+ }
+
+ /**
+ * From BasicSliderUI.Actions
+ */
+ protected static class SliderActions {
+ public static final String POSITIVE_UNIT_INCREMENT = "positiveUnitIncrement";
+ public static final String POSITIVE_BLOCK_INCREMENT = "positiveBlockIncrement";
+ public static final String NEGATIVE_UNIT_INCREMENT = "negativeUnitIncrement";
+ public static final String NEGATIVE_BLOCK_INCREMENT = "negativeBlockIncrement";
+ public static final String MIN_SCROLL_INCREMENT = "minScroll";
+ public static final String MAX_SCROLL_INCREMENT = "maxScroll";
+ }
+
+ @Override
+ public SubstanceInputMap getSliderFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("RIGHT", SliderActions.POSITIVE_UNIT_INCREMENT);
+ result.put("KP_RIGHT", SliderActions.POSITIVE_UNIT_INCREMENT);
+ result.put("DOWN", SliderActions.NEGATIVE_UNIT_INCREMENT);
+ result.put("KP_DOWN", SliderActions.NEGATIVE_UNIT_INCREMENT);
+ result.put("LEFT", SliderActions.NEGATIVE_UNIT_INCREMENT);
+ result.put("KP_LEFT", SliderActions.NEGATIVE_UNIT_INCREMENT);
+ result.put("UP", SliderActions.POSITIVE_UNIT_INCREMENT);
+ result.put("KP_UP", SliderActions.POSITIVE_UNIT_INCREMENT);
+
+ result.put("PAGE_DOWN", SliderActions.NEGATIVE_BLOCK_INCREMENT);
+ result.put("ctrl PAGE_DOWN", SliderActions.NEGATIVE_BLOCK_INCREMENT);
+ result.put("PAGE_UP", SliderActions.POSITIVE_BLOCK_INCREMENT);
+ result.put("ctrl PAGE_UP", SliderActions.POSITIVE_BLOCK_INCREMENT);
+
+ result.put("HOME", SliderActions.MIN_SCROLL_INCREMENT);
+ result.put("END", SliderActions.MAX_SCROLL_INCREMENT);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getSpinnerAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("UP", TextComponentActions.INCREMENT);
+ result.put("KP_UP", TextComponentActions.INCREMENT);
+ result.put("DOWN", TextComponentActions.DECREMENT);
+ result.put("KP_DOWN", TextComponentActions.DECREMENT);
+
+ return result;
+
+ };
+
+ protected static class SplitPaneActions {
+ public static final String NEGATIVE_INCREMENT = "negativeIncrement";
+ public static final String POSITIVE_INCREMENT = "positiveIncrement";
+ public static final String SELECT_MIN = "selectMin";
+ public static final String SELECT_MAX = "selectMax";
+ public static final String START_RESIZE = "startResize";
+ public static final String TOGGLE_FOCUS = "toggleFocus";
+ public static final String FOCUS_OUT_FORWARD = "focusOutForward";
+ public static final String FOCUS_OUT_BACKWARD = "focusOutBackward";
+ }
+
+ @Override
+ public SubstanceInputMap getSplitPaneAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("UP", SplitPaneActions.NEGATIVE_INCREMENT);
+ result.put("DOWN", SplitPaneActions.POSITIVE_INCREMENT);
+ result.put("LEFT", SplitPaneActions.NEGATIVE_INCREMENT);
+ result.put("RIGHT", SplitPaneActions.POSITIVE_INCREMENT);
+ result.put("KP_UP", SplitPaneActions.NEGATIVE_INCREMENT);
+ result.put("KP_DOWN", SplitPaneActions.POSITIVE_INCREMENT);
+ result.put("KP_LEFT", SplitPaneActions.NEGATIVE_INCREMENT);
+ result.put("KP_RIGHT", SplitPaneActions.POSITIVE_INCREMENT);
+
+ result.put("HOME", SplitPaneActions.SELECT_MIN);
+ result.put("END", SplitPaneActions.SELECT_MAX);
+ result.put("F8", SplitPaneActions.START_RESIZE);
+ result.put("F6", SplitPaneActions.TOGGLE_FOCUS);
+ result.put("ctrl TAB", SplitPaneActions.FOCUS_OUT_FORWARD);
+ result.put("ctrl shift TAB", SplitPaneActions.FOCUS_OUT_BACKWARD);
+
+ return result;
+ }
+
+ /**
+ * From BasicTabbedPaneUI.Actions
+ */
+ protected static class TabbedPaneActions {
+ public static final String NEXT = "navigateNext";
+ public static final String PREVIOUS = "navigatePrevious";
+ public static final String RIGHT = "navigateRight";
+ public static final String LEFT = "navigateLeft";
+ public static final String UP = "navigateUp";
+ public static final String DOWN = "navigateDown";
+ public static final String PAGE_UP = "navigatePageUp";
+ public static final String PAGE_DOWN = "navigatePageDown";
+ public static final String REQUEST_FOCUS = "requestFocus";
+ public static final String REQUEST_FOCUS_FOR_VISIBLE = "requestFocusForVisibleComponent";
+ public static final String SET_SELECTED = "setSelectedIndex";
+ public static final String SELECT_FOCUSED = "selectTabWithFocus";
+ public static final String SCROLL_FORWARD = "scrollTabsForwardAction";
+ public static final String SCROLL_BACKWARD = "scrollTabsBackwardAction";
+ }
+
+ @Override
+ public SubstanceInputMap getTabbedPaneAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ctrl PAGE_DOWN", TabbedPaneActions.PAGE_DOWN);
+ result.put("ctrl PAGE_UP", TabbedPaneActions.PAGE_UP);
+ result.put("ctrl UP", TabbedPaneActions.REQUEST_FOCUS);
+ result.put("ctrl KP_UP", TabbedPaneActions.REQUEST_FOCUS);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getTabbedPaneFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("RIGHT", TabbedPaneActions.RIGHT);
+ result.put("KP_RIGHT", TabbedPaneActions.RIGHT);
+ result.put("LEFT", TabbedPaneActions.LEFT);
+ result.put("KP_LEFT", TabbedPaneActions.LEFT);
+
+ result.put("UP", TabbedPaneActions.UP);
+ result.put("KP_UP", TabbedPaneActions.UP);
+ result.put("DOWN", TabbedPaneActions.DOWN);
+ result.put("KP_DOWN", TabbedPaneActions.DOWN);
+
+ result.put("ctrl DOWN", TabbedPaneActions.REQUEST_FOCUS_FOR_VISIBLE);
+ result.put("ctrl KP_DOWN", TabbedPaneActions.REQUEST_FOCUS_FOR_VISIBLE);
+
+ return result;
+ }
+
+ /**
+ * From BasicTableUI.Actions
+ */
+ protected static class TableActions {
+ public static final String CANCEL_EDITING = "cancel";
+ public static final String SELECT_ALL = "selectAll";
+ public static final String CLEAR_SELECTION = "clearSelection";
+ public static final String START_EDITING = "startEditing";
+
+ public static final String NEXT_ROW = "selectNextRow";
+ public static final String NEXT_ROW_CELL = "selectNextRowCell";
+ public static final String NEXT_ROW_EXTEND_SELECTION = "selectNextRowExtendSelection";
+ public static final String NEXT_ROW_CHANGE_LEAD = "selectNextRowChangeLead";
+ public static final String PREVIOUS_ROW = "selectPreviousRow";
+ public static final String PREVIOUS_ROW_CELL = "selectPreviousRowCell";
+ public static final String PREVIOUS_ROW_EXTEND_SELECTION = "selectPreviousRowExtendSelection";
+ public static final String PREVIOUS_ROW_CHANGE_LEAD = "selectPreviousRowChangeLead";
+
+ public static final String NEXT_COLUMN = "selectNextColumn";
+ public static final String NEXT_COLUMN_CELL = "selectNextColumnCell";
+ public static final String NEXT_COLUMN_EXTEND_SELECTION = "selectNextColumnExtendSelection";
+ public static final String NEXT_COLUMN_CHANGE_LEAD = "selectNextColumnChangeLead";
+ public static final String PREVIOUS_COLUMN = "selectPreviousColumn";
+ public static final String PREVIOUS_COLUMN_CELL = "selectPreviousColumnCell";
+ public static final String PREVIOUS_COLUMN_EXTEND_SELECTION = "selectPreviousColumnExtendSelection";
+ public static final String PREVIOUS_COLUMN_CHANGE_LEAD = "selectPreviousColumnChangeLead";
+
+ public static final String SCROLL_LEFT_CHANGE_SELECTION = "scrollLeftChangeSelection";
+ public static final String SCROLL_LEFT_EXTEND_SELECTION = "scrollLeftExtendSelection";
+ public static final String SCROLL_RIGHT_CHANGE_SELECTION = "scrollRightChangeSelection";
+ public static final String SCROLL_RIGHT_EXTEND_SELECTION = "scrollRightExtendSelection";
+
+ public static final String SCROLL_UP_CHANGE_SELECTION = "scrollUpChangeSelection";
+ public static final String SCROLL_UP_EXTEND_SELECTION = "scrollUpExtendSelection";
+ public static final String SCROLL_DOWN_CHANGE_SELECTION = "scrollDownChangeSelection";
+ public static final String SCROLL_DOWN_EXTEND_SELECTION = "scrollDownExtendSelection";
+
+ public static final String FIRST_COLUMN = "selectFirstColumn";
+ public static final String FIRST_COLUMN_EXTEND_SELECTION = "selectFirstColumnExtendSelection";
+ public static final String LAST_COLUMN = "selectLastColumn";
+ public static final String LAST_COLUMN_EXTEND_SELECTION = "selectLastColumnExtendSelection";
+
+ public static final String FIRST_ROW = "selectFirstRow";
+ public static final String FIRST_ROW_EXTEND_SELECTION = "selectFirstRowExtendSelection";
+ public static final String LAST_ROW = "selectLastRow";
+ public static final String LAST_ROW_EXTEND_SELECTION = "selectLastRowExtendSelection";
+
+ public static final String ADD_TO_SELECTION = "addToSelection";
+
+ public static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
+
+ public static final String EXTEND_TO = "extendTo";
+
+ public static final String MOVE_SELECTION_TO = "moveSelectionTo";
+
+ public static final String FOCUS_HEADER = "focusHeader";
+ }
+
+ @Override
+ public SubstanceInputMap getTableAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ctrl C", COPY);
+ result.put("ctrl V", PASTE);
+ result.put("ctrl X", CUT);
+ result.put("COPY", COPY);
+ result.put("PASTE", PASTE);
+ result.put("CUT", CUT);
+ result.put("control INSERT", COPY);
+ result.put("shift INSERT", PASTE);
+ result.put("shift DELETE", CUT);
+
+ result.put("RIGHT", TableActions.NEXT_COLUMN);
+ result.put("KP_RIGHT", TableActions.NEXT_COLUMN);
+ result.put("shift RIGHT", TableActions.NEXT_COLUMN_EXTEND_SELECTION);
+ result.put("shift KP_RIGHT", TableActions.NEXT_COLUMN_EXTEND_SELECTION);
+ result.put("ctrl shift RIGHT",
+ TableActions.NEXT_COLUMN_EXTEND_SELECTION);
+ result.put("ctrl shift KP_RIGHT",
+ TableActions.NEXT_COLUMN_EXTEND_SELECTION);
+ result.put("ctrl RIGHT", TableActions.NEXT_COLUMN_CHANGE_LEAD);
+ result.put("ctrl KP_RIGHT", TableActions.NEXT_COLUMN_CHANGE_LEAD);
+
+ result.put("LEFT", TableActions.PREVIOUS_COLUMN);
+ result.put("KP_LEFT", TableActions.PREVIOUS_COLUMN);
+ result.put("shift LEFT", TableActions.PREVIOUS_COLUMN_EXTEND_SELECTION);
+ result.put("shift KP_LEFT",
+ TableActions.PREVIOUS_COLUMN_EXTEND_SELECTION);
+ result.put("ctrl shift LEFT",
+ TableActions.PREVIOUS_COLUMN_EXTEND_SELECTION);
+ result.put("ctrl shift KP_LEFT",
+ TableActions.PREVIOUS_COLUMN_EXTEND_SELECTION);
+ result.put("ctrl LEFT", TableActions.PREVIOUS_COLUMN_CHANGE_LEAD);
+ result.put("ctrl KP_LEFT", TableActions.PREVIOUS_COLUMN_CHANGE_LEAD);
+
+ result.put("DOWN", TableActions.NEXT_ROW);
+ result.put("KP_DOWN", TableActions.NEXT_ROW);
+ result.put("shift DOWN", TableActions.NEXT_ROW_EXTEND_SELECTION);
+ result.put("shift KP_DOWN", TableActions.NEXT_ROW_EXTEND_SELECTION);
+ result.put("ctrl shift DOWN", TableActions.NEXT_ROW_EXTEND_SELECTION);
+ result
+ .put("ctrl shift KP_DOWN",
+ TableActions.NEXT_ROW_EXTEND_SELECTION);
+ result.put("ctrl DOWN", TableActions.NEXT_ROW_CHANGE_LEAD);
+ result.put("ctrl KP_DOWN", TableActions.NEXT_ROW_CHANGE_LEAD);
+
+ result.put("UP", TableActions.PREVIOUS_ROW);
+ result.put("KP_UP", TableActions.PREVIOUS_ROW);
+ result.put("shift UP", TableActions.PREVIOUS_ROW_EXTEND_SELECTION);
+ result.put("shift KP_UP", TableActions.PREVIOUS_ROW_EXTEND_SELECTION);
+ result.put("ctrl shift UP", TableActions.PREVIOUS_ROW_EXTEND_SELECTION);
+ result.put("ctrl shift KP_UP",
+ TableActions.PREVIOUS_ROW_EXTEND_SELECTION);
+ result.put("ctrl UP", TableActions.PREVIOUS_ROW_CHANGE_LEAD);
+ result.put("ctrl KP_UP", TableActions.PREVIOUS_ROW_CHANGE_LEAD);
+
+ result.put("HOME", TableActions.FIRST_COLUMN);
+ result.put("shift HOME", TableActions.FIRST_COLUMN_EXTEND_SELECTION);
+ result.put("ctrl shift HOME", TableActions.FIRST_ROW_EXTEND_SELECTION);
+ result.put("ctrl HOME", TableActions.FIRST_ROW);
+ result.put("END", TableActions.LAST_COLUMN);
+ result.put("shift END", TableActions.LAST_COLUMN_EXTEND_SELECTION);
+ result.put("ctrl shift END", TableActions.LAST_ROW_EXTEND_SELECTION);
+ result.put("ctrl END", TableActions.LAST_ROW);
+
+ result.put("PAGE_UP", TableActions.SCROLL_UP_CHANGE_SELECTION);
+ result.put("shift PAGE_UP", TableActions.SCROLL_UP_EXTEND_SELECTION);
+ result.put("ctrl shift PAGE_UP",
+ TableActions.SCROLL_LEFT_EXTEND_SELECTION);
+ result.put("ctrl PAGE_UP", TableActions.SCROLL_LEFT_CHANGE_SELECTION);
+ result.put("PAGE_DOWN", TableActions.SCROLL_DOWN_CHANGE_SELECTION);
+ result
+ .put("shift PAGE_DOWN",
+ TableActions.SCROLL_DOWN_EXTEND_SELECTION);
+ result.put("ctrl shift PAGE_DOWN",
+ TableActions.SCROLL_RIGHT_EXTEND_SELECTION);
+ result
+ .put("ctrl PAGE_DOWN",
+ TableActions.SCROLL_RIGHT_CHANGE_SELECTION);
+
+ result.put("TAB", TableActions.NEXT_COLUMN_CELL);
+ result.put("shift TAB", TableActions.PREVIOUS_COLUMN_CELL);
+ result.put("ENTER", TableActions.NEXT_ROW_CELL);
+ result.put("shift ENTER", TableActions.PREVIOUS_ROW_CELL);
+ result.put("ctrl A", TableActions.SELECT_ALL);
+ result.put("ctrl SLASH", TableActions.SELECT_ALL);
+ result.put("ctrl BACK_SLASH", TableActions.CLEAR_SELECTION);
+
+ result.put("ESCAPE", TableActions.CANCEL_EDITING);
+ result.put("F2", TableActions.START_EDITING);
+ result.put("SPACE", TableActions.ADD_TO_SELECTION);
+ result.put("ctrl SPACE", TableActions.TOGGLE_AND_ANCHOR);
+ result.put("shift SPACE", TableActions.EXTEND_TO);
+ result.put("ctrl shift SPACE", TableActions.MOVE_SELECTION_TO);
+ result.put("F8", TableActions.FOCUS_HEADER);
+
+ return result;
+ }
+
+ protected static class TableHeaderActions {
+ public static final String TOGGLE_SORT_ORDER = "toggleSortOrder";
+ public static final String SELECT_COLUMN_TO_LEFT = "selectColumnToLeft";
+ public static final String SELECT_COLUMN_TO_RIGHT = "selectColumnToRight";
+ public static final String MOVE_COLUMN_LEFT = "moveColumnLeft";
+ public static final String MOVE_COLUMN_RIGHT = "moveColumnRight";
+ public static final String RESIZE_LEFT = "resizeLeft";
+ public static final String RESIZE_RIGHT = "resizeRight";
+ public static final String FOCUS_TABLE = "focusTable";
+ }
+
+ @Override
+ public SubstanceInputMap getTableHeaderAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("SPACE", TableHeaderActions.TOGGLE_SORT_ORDER);
+
+ result.put("LEFT", TableHeaderActions.SELECT_COLUMN_TO_LEFT);
+ result.put("KP_LEFT", TableHeaderActions.SELECT_COLUMN_TO_LEFT);
+ result.put("RIGHT", TableHeaderActions.SELECT_COLUMN_TO_RIGHT);
+ result.put("KP_RIGHT", TableHeaderActions.SELECT_COLUMN_TO_RIGHT);
+
+ result.put("alt LEFT", TableHeaderActions.MOVE_COLUMN_LEFT);
+ result.put("alt KP_LEFT", TableHeaderActions.MOVE_COLUMN_LEFT);
+ result.put("alt RIGHT", TableHeaderActions.MOVE_COLUMN_RIGHT);
+ result.put("alt KP_RIGHT", TableHeaderActions.MOVE_COLUMN_RIGHT);
+
+ result.put("alt shift LEFT", TableHeaderActions.RESIZE_LEFT);
+ result.put("alt shift KP_LEFT", TableHeaderActions.RESIZE_LEFT);
+ result.put("alt shift RIGHT", TableHeaderActions.RESIZE_RIGHT);
+ result.put("alt shift KP_RIGHT", TableHeaderActions.RESIZE_RIGHT);
+
+ result.put("ESCAPE", TableHeaderActions.FOCUS_TABLE);
+
+ return result;
+
+ }
+
+ @Override
+ public SubstanceInputMap getTextAreaFocusInputMap() {
+ return this.getMultilineTextComponentFocusInputMap();
+ }
+
+ @Override
+ public SubstanceInputMap getTextFieldFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ctrl C", DefaultEditorKit.copyAction);
+ result.put("ctrl V", DefaultEditorKit.pasteAction);
+ result.put("ctrl X", DefaultEditorKit.cutAction);
+ result.put("COPY", DefaultEditorKit.copyAction);
+ result.put("PASTE", DefaultEditorKit.pasteAction);
+ result.put("CUT", DefaultEditorKit.cutAction);
+ result.put("control INSERT", DefaultEditorKit.copyAction);
+ result.put("shift INSERT", DefaultEditorKit.pasteAction);
+ result.put("shift DELETE", DefaultEditorKit.cutAction);
+
+ result.put("shift LEFT", DefaultEditorKit.selectionBackwardAction);
+ result.put("shift KP_LEFT", DefaultEditorKit.selectionBackwardAction);
+ result.put("shift RIGHT", DefaultEditorKit.selectionForwardAction);
+ result.put("shift KP_RIGHT", DefaultEditorKit.selectionForwardAction);
+ result.put("ctrl LEFT", DefaultEditorKit.previousWordAction);
+ result.put("ctrl KP_LEFT", DefaultEditorKit.previousWordAction);
+ result.put("ctrl RIGHT", DefaultEditorKit.nextWordAction);
+ result.put("ctrl KP_RIGHT", DefaultEditorKit.nextWordAction);
+ result.put("ctrl shift LEFT",
+ DefaultEditorKit.selectionPreviousWordAction);
+ result.put("ctrl shift KP_LEFT",
+ DefaultEditorKit.selectionPreviousWordAction);
+ result
+ .put("ctrl shift RIGHT",
+ DefaultEditorKit.selectionNextWordAction);
+ result.put("ctrl shift KP_RIGHT",
+ DefaultEditorKit.selectionNextWordAction);
+
+ result.put("ctrl A", DefaultEditorKit.selectAllAction);
+ result.put("HOME", DefaultEditorKit.beginLineAction);
+ result.put("END", DefaultEditorKit.endLineAction);
+ result.put("shift HOME", DefaultEditorKit.selectionBeginLineAction);
+ result.put("shift END", DefaultEditorKit.selectionEndLineAction);
+
+ result.put("BACK_SPACE", DefaultEditorKit.deletePrevCharAction);
+ result.put("shift BACK_SPACE", DefaultEditorKit.deletePrevCharAction);
+ result.put("ctrl H", DefaultEditorKit.deletePrevCharAction);
+ result.put("DELETE", DefaultEditorKit.deleteNextCharAction);
+ result.put("ctrl DELETE", DefaultEditorKit.deleteNextWordAction);
+ result.put("ctrl BACK_SPACE", DefaultEditorKit.deletePrevWordAction);
+
+ result.put("RIGHT", DefaultEditorKit.forwardAction);
+ result.put("LEFT", DefaultEditorKit.backwardAction);
+ result.put("KP_RIGHT", DefaultEditorKit.forwardAction);
+ result.put("KP_LEFT", DefaultEditorKit.backwardAction);
+ result.put("ENTER", JTextField.notifyAction);
+ result.put("ctrl BACK_SLASH", TextComponentActions.UNSELECT);
+ result.put("control shift O",
+ TextComponentActions.TOGGLE_COMPONENT_ORIENTATION);
+
+ return result;
+
+ }
+
+ @Override
+ public SubstanceInputMap getTextPaneFocusInputMap() {
+ return this.getMultilineTextComponentFocusInputMap();
+ }
+
+ @Override
+ public SubstanceInputMap getToggleButtonFocusInputMap() {
+ return this.getActionControlFocusInputMap();
+ }
+
+ /**
+ * From BasicToolBarUI.Actions
+ */
+ protected static class ToolBarActions {
+ public static final String NAVIGATE_RIGHT = "navigateRight";
+ public static final String NAVIGATE_LEFT = "navigateLeft";
+ public static final String NAVIGATE_UP = "navigateUp";
+ public static final String NAVIGATE_DOWN = "navigateDown";
+ }
+
+ @Override
+ public SubstanceInputMap getToolBarAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("UP", ToolBarActions.NAVIGATE_UP);
+ result.put("KP_UP", ToolBarActions.NAVIGATE_UP);
+ result.put("DOWN", ToolBarActions.NAVIGATE_DOWN);
+ result.put("KP_DOWN", ToolBarActions.NAVIGATE_DOWN);
+ result.put("LEFT", ToolBarActions.NAVIGATE_LEFT);
+ result.put("KP_LEFT", ToolBarActions.NAVIGATE_LEFT);
+ result.put("RIGHT", ToolBarActions.NAVIGATE_RIGHT);
+ result.put("KP_RIGHT", ToolBarActions.NAVIGATE_RIGHT);
+
+ return result;
+ }
+
+ protected static class TreeActions {
+ public static final String SELECT_PREVIOUS = "selectPrevious";
+ public static final String SELECT_PREVIOUS_CHANGE_LEAD = "selectPreviousChangeLead";
+ public static final String SELECT_PREVIOUS_EXTEND_SELECTION = "selectPreviousExtendSelection";
+ public static final String SELECT_NEXT = "selectNext";
+ public static final String SELECT_NEXT_CHANGE_LEAD = "selectNextChangeLead";
+ public static final String SELECT_NEXT_EXTEND_SELECTION = "selectNextExtendSelection";
+ public static final String SELECT_CHILD = "selectChild";
+ public static final String SELECT_CHILD_CHANGE_LEAD = "selectChildChangeLead";
+ public static final String SELECT_PARENT = "selectParent";
+ public static final String SELECT_PARENT_CHANGE_LEAD = "selectParentChangeLead";
+ public static final String SCROLL_UP_CHANGE_SELECTION = "scrollUpChangeSelection";
+ public static final String SCROLL_UP_CHANGE_LEAD = "scrollUpChangeLead";
+ public static final String SCROLL_UP_EXTEND_SELECTION = "scrollUpExtendSelection";
+ public static final String SCROLL_DOWN_CHANGE_SELECTION = "scrollDownChangeSelection";
+ public static final String SCROLL_DOWN_EXTEND_SELECTION = "scrollDownExtendSelection";
+ public static final String SCROLL_DOWN_CHANGE_LEAD = "scrollDownChangeLead";
+ public static final String SELECT_FIRST = "selectFirst";
+ public static final String SELECT_FIRST_CHANGE_LEAD = "selectFirstChangeLead";
+ public static final String SELECT_FIRST_EXTEND_SELECTION = "selectFirstExtendSelection";
+ public static final String SELECT_LAST = "selectLast";
+ public static final String SELECT_LAST_CHANGE_LEAD = "selectLastChangeLead";
+ public static final String SELECT_LAST_EXTEND_SELECTION = "selectLastExtendSelection";
+ public static final String TOGGLE = "toggle";
+ public static final String CANCEL_EDITING = "cancel";
+ public static final String START_EDITING = "startEditing";
+ public static final String SELECT_ALL = "selectAll";
+ public static final String CLEAR_SELECTION = "clearSelection";
+ public static final String SCROLL_LEFT = "scrollLeft";
+ public static final String SCROLL_RIGHT = "scrollRight";
+ public static final String SCROLL_LEFT_EXTEND_SELECTION = "scrollLeftExtendSelection";
+ public static final String SCROLL_RIGHT_EXTEND_SELECTION = "scrollRightExtendSelection";
+ public static final String SCROLL_RIGHT_CHANGE_LEAD = "scrollRightChangeLead";
+ public static final String SCROLL_LEFT_CHANGE_LEAD = "scrollLeftChangeLead";
+ public static final String EXPAND = "expand";
+ public static final String COLLAPSE = "collapse";
+ public static final String MOVE_SELECTION_TO_PARENT = "moveSelectionToParent";
+
+ public static final String ADD_TO_SELECTION = "addToSelection";
+
+ public static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
+
+ public static final String EXTEND_TO = "extendTo";
+
+ public static final String MOVE_SELECTION_TO = "moveSelectionTo";
+ }
+
+ @Override
+ public SubstanceInputMap getTreeAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ESCAPE", TreeActions.CANCEL_EDITING);
+
+ return result;
+
+ }
+
+ @Override
+ public SubstanceInputMap getTreeFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ADD", TreeActions.EXPAND);
+ result.put("SUBTRACT", TreeActions.COLLAPSE);
+
+ result.put("ctrl C", COPY);
+ result.put("ctrl V", PASTE);
+ result.put("ctrl X", CUT);
+ result.put("COPY", COPY);
+ result.put("PASTE", PASTE);
+ result.put("CUT", CUT);
+ result.put("control INSERT", COPY);
+ result.put("shift INSERT", PASTE);
+ result.put("shift DELETE", CUT);
+
+ result.put("UP", TreeActions.SELECT_PREVIOUS);
+ result.put("KP_UP", TreeActions.SELECT_PREVIOUS);
+ result.put("shift UP", TreeActions.SELECT_PREVIOUS_EXTEND_SELECTION);
+ result.put("shift KP_UP", TreeActions.SELECT_PREVIOUS_EXTEND_SELECTION);
+ result.put("ctrl shift UP",
+ TreeActions.SELECT_PREVIOUS_EXTEND_SELECTION);
+ result.put("ctrl shift KP_UP",
+ TreeActions.SELECT_PREVIOUS_EXTEND_SELECTION);
+ result.put("ctrl UP", TreeActions.SELECT_PREVIOUS_CHANGE_LEAD);
+ result.put("ctrl KP_UP", TreeActions.SELECT_PREVIOUS_CHANGE_LEAD);
+
+ result.put("DOWN", TreeActions.SELECT_NEXT);
+ result.put("KP_DOWN", TreeActions.SELECT_NEXT);
+ result.put("shift DOWN", TreeActions.SELECT_NEXT_EXTEND_SELECTION);
+ result.put("shift KP_DOWN", TreeActions.SELECT_NEXT_EXTEND_SELECTION);
+ result.put("ctrl shift DOWN", TreeActions.SELECT_NEXT_EXTEND_SELECTION);
+ result.put("ctrl shift KP_DOWN",
+ TreeActions.SELECT_NEXT_EXTEND_SELECTION);
+ result.put("ctrl DOWN", TreeActions.SELECT_NEXT_CHANGE_LEAD);
+ result.put("ctrl KP_DOWN", TreeActions.SELECT_NEXT_CHANGE_LEAD);
+
+ result.put("RIGHT", TreeActions.SELECT_CHILD);
+ result.put("KP_RIGHT", TreeActions.SELECT_CHILD);
+ result.put("LEFT", TreeActions.SELECT_PARENT);
+ result.put("KP_LEFT", TreeActions.SELECT_PARENT);
+
+ result.put("PAGE_UP", TreeActions.SCROLL_UP_CHANGE_SELECTION);
+ result.put("shift PAGE_UP", TreeActions.SCROLL_UP_EXTEND_SELECTION);
+ result
+ .put("ctrl shift PAGE_UP",
+ TreeActions.SCROLL_UP_EXTEND_SELECTION);
+ result.put("ctrl PAGE_UP", TreeActions.SCROLL_UP_CHANGE_LEAD);
+ result.put("PAGE_DOWN", TreeActions.SCROLL_DOWN_CHANGE_SELECTION);
+ result.put("shift PAGE_DOWN", TreeActions.SCROLL_DOWN_EXTEND_SELECTION);
+ result.put("ctrl shift PAGE_DOWN",
+ TreeActions.SCROLL_DOWN_EXTEND_SELECTION);
+ result.put("ctrl PAGE_DOWN", TreeActions.SCROLL_DOWN_CHANGE_LEAD);
+
+ result.put("HOME", TreeActions.SELECT_FIRST);
+ result.put("shift HOME", TreeActions.SELECT_FIRST_EXTEND_SELECTION);
+ result
+ .put("ctrl shift HOME",
+ TreeActions.SELECT_FIRST_EXTEND_SELECTION);
+ result.put("ctrl HOME", TreeActions.SELECT_FIRST_CHANGE_LEAD);
+ result.put("END", TreeActions.SELECT_LAST);
+ result.put("shift END", TreeActions.SELECT_LAST_EXTEND_SELECTION);
+ result.put("ctrl shift END", TreeActions.SELECT_LAST_EXTEND_SELECTION);
+ result.put("ctrl END", TreeActions.SELECT_LAST_CHANGE_LEAD);
+
+ result.put("F2", TreeActions.START_EDITING);
+ result.put("ctrl A", TreeActions.SELECT_ALL);
+ result.put("ctrl SLASH", TreeActions.SELECT_ALL);
+ result.put("ctrl BACK_SLASH", TreeActions.CLEAR_SELECTION);
+
+ result.put("ctrl LEFT", TreeActions.SCROLL_LEFT);
+ result.put("ctrl KP_LEFT", TreeActions.SCROLL_LEFT);
+ result.put("ctrl RIGHT", TreeActions.SCROLL_RIGHT);
+ result.put("ctrl KP_RIGHT", TreeActions.SCROLL_RIGHT);
+
+ result.put("SPACE", TreeActions.ADD_TO_SELECTION);
+ result.put("ctrl SPACE", TreeActions.TOGGLE_AND_ANCHOR);
+ result.put("shift SPACE", TreeActions.EXTEND_TO);
+ result.put("ctrl shift SPACE", TreeActions.MOVE_SELECTION_TO);
+
+ return result;
+ };
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/inputmaps/GnomeInputMapSet.java b/substance/src/main/java/org/pushingpixels/substance/internal/inputmaps/GnomeInputMapSet.java
new file mode 100644
index 0000000..86197d2
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/inputmaps/GnomeInputMapSet.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.inputmaps;
+
+import org.pushingpixels.substance.api.inputmaps.SubstanceInputMap;
+
+public class GnomeInputMapSet extends BaseInputMapSet {
+ @Override
+ public SubstanceInputMap getButtonFocusInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("SPACE", PRESSED);
+ result.put("released SPACE", RELEASED);
+ result.put("ENTER", PRESSED);
+ result.put("released ENTER", RELEASED);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getFileChooserAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ctrl ENTER", FileChooserActions.APPROVE_SELECTION);
+ result.put("ESCAPE", FileChooserActions.CANCEL_SELECTION);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getSliderFocusInputMap() {
+ SubstanceInputMap result = super.getSliderFocusInputMap();
+
+ result.remove("ctrl PAGE_DOWN");
+ result.remove("ctrl PAGE_UP");
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getTabbedPaneAncestorInputMap() {
+ SubstanceInputMap result = super.getTabbedPaneAncestorInputMap();
+
+ result.put("ctrl TAB", TabbedPaneActions.NEXT);
+ result.put("shift ctrl TAB", TabbedPaneActions.PREVIOUS);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getTreeFocusInputMap() {
+ SubstanceInputMap result = super.getTreeFocusInputMap();
+
+ result.remove("ADD");
+ result.put("BACK_SPACE", TreeActions.MOVE_SELECTION_TO_PARENT);
+ result.remove("SUBTRACT");
+
+ result.put("typed +", TreeActions.EXPAND);
+ result.put("typed -", TreeActions.COLLAPSE);
+
+ return result;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/inputmaps/WindowsInputMapSet.java b/substance/src/main/java/org/pushingpixels/substance/internal/inputmaps/WindowsInputMapSet.java
new file mode 100644
index 0000000..d41c1f7
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/inputmaps/WindowsInputMapSet.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.inputmaps;
+
+import org.pushingpixels.substance.api.inputmaps.SubstanceInputMap;
+
+public class WindowsInputMapSet extends BaseInputMapSet {
+ @Override
+ public SubstanceInputMap getComboBoxAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ESCAPE", ComboActions.HIDE);
+ result.put("PAGE_UP", ComboActions.PAGE_UP);
+ result.put("PAGE_DOWN", ComboActions.PAGE_DOWN);
+ result.put("HOME", ComboActions.HOME);
+ result.put("END", ComboActions.END);
+ result.put("DOWN", ComboActions.DOWN_2);
+ result.put("KP_DOWN", ComboActions.DOWN_2);
+ result.put("alt DOWN", ComboActions.TOGGLE);
+ result.put("alt KP_DOWN", ComboActions.TOGGLE);
+ result.put("alt UP", ComboActions.TOGGLE);
+ result.put("alt KP_UP", ComboActions.TOGGLE);
+ result.put("ENTER", ComboActions.ENTER);
+ result.put("UP", ComboActions.UP_2);
+ result.put("KP_UP", ComboActions.UP_2);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getDesktopAncestorInputMap() {
+ SubstanceInputMap result = new SubstanceInputMap();
+
+ result.put("ctrl F5", DesktopPaneActions.RESTORE);
+ result.put("ctrl F4", DesktopPaneActions.CLOSE);
+ result.put("ctrl F7", DesktopPaneActions.MOVE);
+ result.put("ctrl F8", DesktopPaneActions.RESIZE);
+
+ result.put("RIGHT", DesktopPaneActions.RIGHT);
+ result.put("KP_RIGHT", DesktopPaneActions.RIGHT);
+ result.put("LEFT", DesktopPaneActions.LEFT);
+ result.put("KP_LEFT", DesktopPaneActions.LEFT);
+
+ result.put("UP", DesktopPaneActions.UP);
+ result.put("KP_UP", DesktopPaneActions.UP);
+ result.put("DOWN", DesktopPaneActions.DOWN);
+ result.put("KP_DOWN", DesktopPaneActions.DOWN);
+
+ result.put("ESCAPE", DesktopPaneActions.ESCAPE);
+ result.put("ctrl F9", DesktopPaneActions.MINIMIZE);
+ result.put("ctrl F10", DesktopPaneActions.MAXIMIZE);
+
+ result.put("ctrl F6", DesktopPaneActions.NEXT_FRAME);
+ result.put("ctrl TAB", DesktopPaneActions.NEXT_FRAME);
+ result.put("ctrl alt F6", DesktopPaneActions.NEXT_FRAME);
+ result.put("shift ctrl alt F6", DesktopPaneActions.PREVIOUS_FRAME);
+ result.put("ctrl F12", DesktopPaneActions.NAVIGATE_NEXT);
+ result.put("shift ctrl F12", DesktopPaneActions.NAVIGATE_PREVIOUS);
+
+ return result;
+ }
+
+ @Override
+ protected SubstanceInputMap getMultilineTextComponentFocusInputMap() {
+ SubstanceInputMap result = super
+ .getMultilineTextComponentFocusInputMap();
+
+ result.remove("ctrl KP_LEFT");
+ result.remove("ctrl KP_RIGHT");
+ result.remove("KP_DOWN");
+ result.remove("KP_UP");
+ result.remove("ctrl shift KP_LEFT");
+ result.remove("ctrl shift KP_RIGHT");
+ result.remove("shift KP_LEFT");
+ result.remove("shift KP_RIGHT");
+ result.remove("shift KP_DOWN");
+ result.remove("shift KP_UP");
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getPasswordFieldFocusInputMap() {
+ SubstanceInputMap result = super.getPasswordFieldFocusInputMap();
+
+ result.remove("ctrl KP_LEFT");
+ result.remove("ctrl KP_RIGHT");
+ result.remove("ctrl shift KP_LEFT");
+ result.remove("ctrl shift KP_RIGHT");
+ result.remove("shift KP_LEFT");
+ result.remove("shift KP_RIGHT");
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getScrollBarAncestorInputMap() {
+ SubstanceInputMap result = super.getScrollBarAncestorInputMap();
+
+ result.put("ctrl PAGE_DOWN", ScrollBarActions.POSITIVE_BLOCK_INCREMENT);
+ result.put("ctrl PAGE_UP", ScrollBarActions.NEGATIVE_BLOCK_INCREMENT);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getSliderFocusInputMap() {
+ SubstanceInputMap result = super.getSliderFocusInputMap();
+
+ result.remove("ctrl PAGE_DOWN");
+ result.remove("ctrl PAGE_UP");
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getTabbedPaneAncestorInputMap() {
+ SubstanceInputMap result = super.getTabbedPaneAncestorInputMap();
+
+ result.put("ctrl TAB", TabbedPaneActions.NEXT);
+ result.put("shift ctrl TAB", TabbedPaneActions.PREVIOUS);
+
+ return result;
+ }
+
+ @Override
+ public SubstanceInputMap getTextFieldFocusInputMap() {
+ SubstanceInputMap result = super.getTextFieldFocusInputMap();
+
+ result.remove("ctrl KP_LEFT");
+ result.remove("ctrl KP_RIGHT");
+ result.remove("ctrl shift KP_LEFT");
+ result.remove("ctrl shift KP_RIGHT");
+ result.remove("shift KP_LEFT");
+ result.remove("shift KP_RIGHT");
+
+ return result;
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/painter/BackgroundPaintingUtils.java b/substance/src/main/java/org/pushingpixels/substance/internal/painter/BackgroundPaintingUtils.java
new file mode 100755
index 0000000..69347a8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/painter/BackgroundPaintingUtils.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.painter;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.text.JTextComponent;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Delegate for painting filled backgrounds.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BackgroundPaintingUtils {
+ /**
+ * Updates the background of the specified component on the specified
+ * graphic context. The background is updated only if the component is
+ * opaque.
+ *
+ * @param g
+ * Graphic context.
+ * @param c
+ * Component.
+ */
+ public static void updateIfOpaque(Graphics g, Component c) {
+ if (SubstanceCoreUtilities.isOpaque(c))
+ update(g, c, false);
+ }
+
+ /**
+ * Updates the background of the specified component on the specified
+ * graphic context. The background is not painted when the
+ * <code>force</code> parameter is <code>false</code> and at least one of
+ * the following conditions holds:
+ * <ul>
+ * <li>The component is in a cell renderer.</li>
+ * <li>The component is not showing on the screen.</li>
+ * <li>The component is in the preview mode.</li>
+ * </ul>
+ *
+ * @param g
+ * Graphic context.
+ * @param c
+ * Component.
+ * @param force
+ * If <code>true</code>, the painting of background is enforced.
+ */
+ public static void update(Graphics g, Component c, boolean force) {
+ update(g, c, force, null);
+ }
+
+ /**
+ * Updates the background of the specified component on the specified
+ * graphic context. The background is not painted when the
+ * <code>force</code> parameter is <code>false</code> and at least one of
+ * the following conditions holds:
+ * <ul>
+ * <li>The component is in a cell renderer.</li>
+ * <li>The component is not showing on the screen.</li>
+ * <li>The component is in the preview mode.</li>
+ * </ul>
+ *
+ * @param g
+ * Graphic context.
+ * @param c
+ * Component.
+ * @param force
+ * If <code>true</code>, the painting of background is enforced.
+ * @param decorationType
+ * The decoration type to use. A <code>null</code> value will result in
+ * the component being asked.
+ */
+ public static void update(Graphics g, Component c, boolean force, DecorationAreaType decorationType) {
+ // failsafe for LAF change
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ return;
+ }
+
+ boolean isInCellRenderer = (SwingUtilities.getAncestorOfClass(
+ CellRendererPane.class, c) != null);
+ boolean isPreviewMode = false;
+ if (c instanceof JComponent) {
+ isPreviewMode = (Boolean.TRUE.equals(((JComponent) c)
+ .getClientProperty(LafWidgetUtilities.PREVIEW_MODE)));
+ }
+
+ // if (!force && !isPreviewMode && !c.isShowing() && !isInCellRenderer)
+ // {
+ // return;
+ // }
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ // optimization - do not call fillRect on graphics
+ // with anti-alias turned on
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c, g));
+
+ if (decorationType == null) {
+ decorationType = SubstanceLookAndFeel
+ .getDecorationType(c);
+ }
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(c);
+ boolean isShowing = c.isShowing();
+ if (isShowing && (decorationType != DecorationAreaType.NONE)
+ && (skin.isRegisteredAsDecorationArea(decorationType))) {
+ // use the decoration painter
+ DecorationPainterUtils
+ .paintDecorationBackground(graphics, c, decorationType, force);
+ // and add overlays
+ OverlayPainterUtils
+ .paintOverlays(graphics, c, skin, decorationType);
+ } else {
+ // fill the area with solid color
+ // doing null checks on getParent() to address the case of table cell renderers (no parent)
+ Color backgr = SubstanceColorUtilities
+ .getBackgroundFillColor((((c instanceof JTextComponent) || (c instanceof JSpinner)) && c.getParent() != null )
+ ? c.getParent()
+ : c);
+ graphics.setColor(backgr);
+ graphics.fillRect(0, 0, c.getWidth(), c.getHeight());
+
+ if (isShowing) {
+ // add overlays
+ OverlayPainterUtils.paintOverlays(graphics, c, skin,
+ decorationType);
+
+ // and paint watermark
+ SubstanceWatermark watermark = SubstanceCoreUtilities
+ .getSkin(c).getWatermark();
+ if ((watermark != null) && !isPreviewMode && !isInCellRenderer
+ && SubstanceCoreUtilities.toDrawWatermark(c)) {
+ watermark.drawWatermarkImage(graphics, c, 0, 0, c
+ .getWidth(), c.getHeight());
+ }
+ }
+ }
+
+ graphics.dispose();
+ }
+
+ /**
+ * Updates the background of the specified component on the specified
+ * graphic context in the specified rectangle.
+ *
+ * @param g
+ * Graphic context.
+ * @param c
+ * Component.
+ * @param fillColor
+ * Fill color.
+ * @param rect
+ * The rectangle to fill.
+ */
+ public static void fillAndWatermark(Graphics g, JComponent c,
+ Color fillColor, Rectangle rect) {
+ // failsafe for LAF change
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ return;
+ }
+
+ boolean isInCellRenderer = (SwingUtilities.getAncestorOfClass(
+ CellRendererPane.class, c) != null);
+ if ((!c.isShowing()) && (!isInCellRenderer)) {
+ return;
+ }
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c, g));
+ graphics.setColor(fillColor);
+ graphics.fillRect(rect.x, rect.y, rect.width, rect.height);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c, 1.0f, g));
+ // stamp watermark
+ SubstanceWatermark watermark = SubstanceCoreUtilities.getSkin(c)
+ .getWatermark();
+ if ((watermark != null) && !isInCellRenderer && c.isShowing()
+ && SubstanceCoreUtilities.toDrawWatermark(c))
+ watermark.drawWatermarkImage(graphics, c, rect.x, rect.y,
+ rect.width, rect.height);
+ graphics.dispose();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/painter/DecorationPainterUtils.java b/substance/src/main/java/org/pushingpixels/substance/internal/painter/DecorationPainterUtils.java
new file mode 100755
index 0000000..7e4fbb6
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/painter/DecorationPainterUtils.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.painter;
+
+import java.awt.*;
+
+import javax.swing.*;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.decoration.SubstanceDecorationPainter;
+import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Contains utility methods related to decoration painters. This class is for
+ * internal use only.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DecorationPainterUtils {
+ /**
+ * Client property for marking a component with an instance of
+ * {@link DecorationAreaType} enum.
+ */
+ private static final String DECORATION_AREA_TYPE = "substancelaf.internal.painter.decorationAreaType";
+
+ /**
+ * Sets the decoration type of the specified component.
+ *
+ * @param comp
+ * Component.
+ * @param type
+ * Decoration type of the component and its children.
+ */
+ public static void setDecorationType(JComponent comp,
+ DecorationAreaType type) {
+ comp.putClientProperty(DECORATION_AREA_TYPE, type);
+ }
+
+ /**
+ * Clears the client properties related to the decoration area type.
+ *
+ * @param comp
+ * Component.
+ */
+ public static void clearDecorationType(JComponent comp) {
+ if (comp != null) {
+ comp.putClientProperty(DECORATION_AREA_TYPE, null);
+ }
+ }
+
+ /**
+ * Returns the decoration area type of the specified component. The
+ * component and its ancestor hierarchy are scanned for the registered
+ * decoration area type. If
+ * {@link #setDecorationType(JComponent, DecorationAreaType)} has been
+ * called on the specified component, the matching decoration type is
+ * returned. Otherwise, the component hierarchy is scanned to find the
+ * closest ancestor that was passed to
+ * {@link #setDecorationType(JComponent, DecorationAreaType)} - and its
+ * decoration type is returned. If neither the component, nor any one of its
+ * parent components has been passed to the setter method,
+ * {@link DecorationAreaType#NONE} is returned.
+ *
+ * @param comp
+ * Component.
+ * @return Decoration area type of the component.
+ */
+ public static DecorationAreaType getDecorationType(Component comp) {
+ Component c = comp;
+ while (c != null) {
+ if (c instanceof JComponent) {
+ JComponent jc = (JComponent) c;
+ Object prop = jc.getClientProperty(DECORATION_AREA_TYPE);
+ if (prop instanceof DecorationAreaType) {
+ return (DecorationAreaType) prop;
+ }
+ }
+ c = c.getParent();
+ }
+ return DecorationAreaType.NONE;
+ }
+
+ /**
+ * Returns the immediate decoration area type of the specified component.
+ * The component is checked for the registered decoration area type. If
+ * {@link #setDecorationType(javax.swing.JComponent, org.pushingpixels.substance.api.DecorationAreaType)} was
+ * not called on this component, this method returns <code>null</code>.
+ *
+ * @param comp
+ * Component.
+ * @return Immediate decoration area type of the component.
+ */
+ public static DecorationAreaType getImmediateDecorationType(Component comp) {
+ Component c = comp;
+ if (c instanceof JComponent) {
+ JComponent jc = (JComponent) c;
+ Object prop = jc.getClientProperty(DECORATION_AREA_TYPE);
+ if (prop instanceof DecorationAreaType)
+ return (DecorationAreaType) prop;
+ }
+ return null;
+ }
+
+ /**
+ * Paints the decoration background on the specified component. The
+ * decoration background is not painted when the <code>force</code>
+ * parameter is <code>false</code> and at least one of the following
+ * conditions holds:
+ * <ul>
+ * <li>The component is in a cell renderer.</li>
+ * <li>The component is not showing on the screen.</li>
+ * <li>The component is in the preview mode.</li>
+ * </ul>
+ *
+ * @param g
+ * Graphics context.
+ * @param c
+ * Component.
+ * @param force
+ * If <code>true</code>, the painting of decoration background is
+ * enforced.
+ */
+ public static void paintDecorationBackground(Graphics g, Component c,
+ boolean force) {
+ DecorationAreaType decorationType = SubstanceLookAndFeel
+ .getDecorationType(c);
+ paintDecorationBackground(g, c, decorationType, force);
+ }
+
+ /**
+ * Paints the decoration background on the specified component. See comments
+ * on {@link #paintDecorationBackground(Graphics, Component, boolean)} for
+ * the cases when the decoration background painting is skipped.
+ *
+ * @param g
+ * Graphics context.
+ * @param c
+ * Component.
+ * @param decorationType
+ * Decoration area type of the component.
+ * @param force
+ * If <code>true</code>, the painting of decoration background is
+ * enforced. #see
+ * {@link #paintDecorationBackground(Graphics, Component, boolean)}
+ */
+ public static void paintDecorationBackground(Graphics g, Component c,
+ DecorationAreaType decorationType, boolean force) {
+ // System.out.println("Painting " + c.getClass().getSimpleName());
+ boolean isInCellRenderer = (SwingUtilities.getAncestorOfClass(
+ CellRendererPane.class, c) != null);
+ boolean isPreviewMode = false;
+ if (c instanceof JComponent) {
+ isPreviewMode = (Boolean.TRUE.equals(((JComponent) c)
+ .getClientProperty(LafWidgetUtilities.PREVIEW_MODE)));
+ }
+
+ if (!force && !isPreviewMode && !c.isShowing() && !isInCellRenderer) {
+ return;
+ }
+
+ if ((c.getHeight() == 0) || (c.getWidth() == 0))
+ return;
+
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(c);
+ SubstanceDecorationPainter painter = skin.getDecorationPainter();
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ painter.paintDecorationArea(g2d, c, decorationType, c.getWidth(), c
+ .getHeight(), skin);
+
+ SubstanceWatermark watermark = SubstanceCoreUtilities.getSkin(c)
+ .getWatermark();
+ if ((watermark != null) && !isPreviewMode && !isInCellRenderer
+ && c.isShowing() && SubstanceCoreUtilities.toDrawWatermark(c)) {
+ // paint the watermark over the component
+ watermark.drawWatermarkImage(g2d, c, 0, 0, c.getWidth(), c
+ .getHeight());
+
+ // paint the background second time with 50%
+ // translucency, making the watermark' bleed' through.
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(c, 0.5f, g));
+ painter.paintDecorationArea(g2d, c, decorationType, c.getWidth(), c
+ .getHeight(), skin);
+ }
+ g2d.dispose();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/painter/HighlightPainterUtils.java b/substance/src/main/java/org/pushingpixels/substance/internal/painter/HighlightPainterUtils.java
new file mode 100644
index 0000000..966a634
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/painter/HighlightPainterUtils.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.painter;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.EnumSet;
+import java.util.Set;
+
+import javax.swing.CellRendererPane;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceConstants.Side;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.highlight.SubstanceHighlightPainter;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Contains utility methods related to highlight painters. This class is for
+ * internal use only.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class HighlightPainterUtils {
+ /**
+ * Cache for small objects.
+ */
+ protected final static LazyResettableHashMap<BufferedImage> smallCache = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceHighlightUtils");
+
+ /**
+ * Paints the highlight for the specified component.
+ *
+ * @param g
+ * Graphic context.
+ * @param rendererPane
+ * Renderer pane. Can be <code>null</code>.
+ * @param c
+ * Component.
+ * @param rect
+ * Rectangle to highlight.
+ * @param borderAlpha
+ * Border alpha.
+ * @param openSides
+ * The sides specified in this set will not be painted. Can be
+ * <code>null</code> or empty.
+ * @param fillScheme
+ * The fill color scheme.
+ * @param borderScheme
+ * The border color scheme.
+ */
+ public static void paintHighlight(Graphics g,
+ CellRendererPane rendererPane, Component c, Rectangle rect,
+ float borderAlpha, Set<Side> openSides,
+ SubstanceColorScheme fillScheme, SubstanceColorScheme borderScheme) {
+ // fix for bug 65
+ if ((rect.width <= 0) || (rect.height <= 0))
+ return;
+
+ Component compForQuerying = (rendererPane != null) ? rendererPane : c;
+ SubstanceHighlightPainter highlightPainter = SubstanceCoreUtilities
+ .getSkin(compForQuerying).getHighlightPainter();
+ SubstanceBorderPainter highlightBorderPainter = SubstanceCoreUtilities
+ .getHighlightBorderPainter(compForQuerying);
+ Graphics2D g2d = (Graphics2D) g.create(rect.x, rect.y, rect.width,
+ rect.height);
+
+ if (openSides == null) {
+ openSides = EnumSet.noneOf(Side.class);
+ }
+ if (rect.width * rect.height < 100000) {
+ String openKey = "";
+ for (Side oSide : openSides) {
+ openKey += oSide.name() + "-";
+ }
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(highlightPainter
+ .getDisplayName(), highlightBorderPainter.getDisplayName(),
+ rect.width, rect.height, fillScheme.getDisplayName(),
+ borderScheme.getDisplayName(), borderAlpha, openKey);
+ BufferedImage result = smallCache.get(key);
+ if (result == null) {
+ result = createHighlighterImage(c, rect, borderAlpha,
+ openSides, fillScheme, borderScheme, highlightPainter,
+ highlightBorderPainter);
+ smallCache.put(key, result);
+ }
+ g2d.drawImage(result, 0, 0, null);
+ }
+ }
+
+ private static BufferedImage createHighlighterImage(Component c,
+ Rectangle rect, float borderAlpha, Set<Side> openSides,
+ SubstanceColorScheme currScheme,
+ SubstanceColorScheme currBorderScheme,
+ SubstanceHighlightPainter highlightPainter,
+ SubstanceBorderPainter highlightBorderPainter) {
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(rect.width,
+ rect.height);
+ Graphics2D resGraphics = result.createGraphics();
+ highlightPainter.paintHighlight(resGraphics, c, rect.width,
+ rect.height, currScheme);
+ paintHighlightBorder(resGraphics, c, rect.width, rect.height,
+ borderAlpha, openSides, highlightBorderPainter,
+ currBorderScheme);
+ resGraphics.dispose();
+ return result;
+ }
+
+ /**
+ * Paints the highlight border for the specified component.
+ *
+ * @param graphics
+ * Graphic context.
+ * @param comp
+ * Component.
+ * @param width
+ * Border width.
+ * @param height
+ * Border width.
+ * @param borderAlpha
+ * Border alpha.
+ * @param openSides
+ * The sides specified in this set will not be painted. Can be
+ * <code>null</code> or empty.
+ * @param highlightBorderPainter
+ * Border painter for the highlights.
+ * @param borderColorScheme
+ * The border color scheme.
+ */
+ private static void paintHighlightBorder(Graphics2D graphics,
+ Component comp, int width, int height, float borderAlpha,
+ Set<Side> openSides, SubstanceBorderPainter highlightBorderPainter,
+ SubstanceColorScheme borderColorScheme) {
+ if (borderAlpha <= 0.0f)
+ return;
+
+ int openDelta = 3 + (int) (Math.ceil(3.0 * SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(comp))));
+ int deltaLeft = openSides.contains(Side.LEFT) ? openDelta : 0;
+ int deltaRight = openSides.contains(Side.RIGHT) ? openDelta : 0;
+ int deltaTop = openSides.contains(Side.TOP) ? openDelta : 0;
+ int deltaBottom = openSides.contains(Side.BOTTOM) ? openDelta : 0;
+
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(comp)) / 2.0);
+ Shape contour = new Rectangle(borderDelta, borderDelta, width
+ + deltaLeft + deltaRight - 2 * borderDelta - 1, height
+ + deltaTop + deltaBottom - 2 * borderDelta - 1);
+
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.translate(-deltaLeft, -deltaTop);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(null,
+ borderAlpha, graphics));
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(comp));
+ Shape contourInner = new Rectangle(borderDelta + borderThickness,
+ borderDelta + borderThickness, width + deltaLeft + deltaRight
+ - 2 * borderDelta - 2 * borderThickness - 1, height
+ + deltaTop + deltaBottom - 2 * borderDelta - 2
+ * borderThickness - 1);
+
+ highlightBorderPainter.paintBorder(g2d, comp, width + deltaLeft
+ + deltaRight, height + deltaTop + deltaBottom, contour,
+ contourInner, borderColorScheme);
+ g2d.dispose();
+ }
+
+ /**
+ * Returns the memory usage string.
+ *
+ * @return Memory usage string.
+ */
+ public static String getMemoryUsage() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("SubstanceHighlightUtils: \n");
+ sb.append("\t" + smallCache.size() + " smalls");
+ return sb.toString();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/painter/OverlayPainterUtils.java b/substance/src/main/java/org/pushingpixels/substance/internal/painter/OverlayPainterUtils.java
new file mode 100644
index 0000000..e22631c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/painter/OverlayPainterUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.painter;
+
+import java.awt.*;
+import java.util.List;
+
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.painter.overlay.SubstanceOverlayPainter;
+
+/**
+ * Contains utility methods related to overlay painters. This class is for
+ * internal use only.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class OverlayPainterUtils {
+ /**
+ * Paints all registered overlays on the specified component. Overlay
+ * painters are registered with
+ * {@link SubstanceSkin#addOverlayPainter(SubstanceOverlayPainter, DecorationAreaType...)}
+ * API.
+ *
+ * @param g
+ * Graphics context.
+ * @param c
+ * Component.
+ * @param skin
+ * Component skin.
+ * @param decorationAreaType
+ * Component decoration area type.
+ */
+ public static void paintOverlays(Graphics g, Component c,
+ SubstanceSkin skin, DecorationAreaType decorationAreaType) {
+ List<SubstanceOverlayPainter> overlayPainters = skin
+ .getOverlayPainters(decorationAreaType);
+ if (overlayPainters.size() == 0)
+ return;
+
+ for (SubstanceOverlayPainter overlayPainter : overlayPainters) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ overlayPainter.paintOverlay(g2d, c, decorationAreaType, c
+ .getWidth(), c.getHeight(), skin);
+ g2d.dispose();
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/painter/SeparatorPainterUtils.java b/substance/src/main/java/org/pushingpixels/substance/internal/painter/SeparatorPainterUtils.java
new file mode 100644
index 0000000..0ce3744
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/painter/SeparatorPainterUtils.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.painter;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.Collection;
+
+import javax.swing.JSeparator;
+import javax.swing.SwingConstants;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Contains utility methods related to painting separators. This class is for
+ * internal use only.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SeparatorPainterUtils {
+ /**
+ * Cached images of separators.
+ */
+ private static LazyResettableHashMap<BufferedImage> cached = new LazyResettableHashMap<BufferedImage>(
+ "SeparatorPainterUtils");
+
+ /**
+ * Paints a separator.
+ *
+ * @param c
+ * Component.
+ * @param graphics
+ * Graphics context.
+ * @param width
+ * Separator width.
+ * @param height
+ * Separator height.
+ * @param orientation
+ * Separator orientation.
+ */
+ public static void paintSeparator(Component c, Graphics graphics,
+ int width, int height, int orientation) {
+ paintSeparator(c, graphics, width, height, orientation, true, 10);
+ }
+
+ /**
+ * Paints a separator.
+ *
+ * @param c
+ * Component.
+ * @param graphics
+ * Graphics context.
+ * @param width
+ * Separator width.
+ * @param height
+ * Separator height.
+ * @param orientation
+ * Separator orientation.
+ * @param hasShadow
+ * If <code>true</code>, the separator painting will have shadow.
+ * @param maxGradLength
+ * Specifies the maximum pixel length of "ramp" portions of the
+ * separator. The ramp portions are located on separator ends and
+ * allow providing a faded appearance on those ends.
+ */
+ public static void paintSeparator(Component c, Graphics graphics,
+ int width, int height, int orientation, boolean hasShadow,
+ int maxGradLength) {
+ paintSeparator(c, graphics, width, height, orientation, hasShadow,
+ maxGradLength, maxGradLength, false);
+ }
+
+ /**
+ * Paints a separator.
+ *
+ * @param c
+ * Component.
+ * @param g
+ * Graphics context.
+ * @param width
+ * Separator width.
+ * @param height
+ * Separator height.
+ * @param orientation
+ * Separator orientation.
+ * @param hasShadow
+ * If <code>true</code>, the separator painting will have shadow.
+ * @param maxGradLengthStart
+ * Specifies the maximum pixel length of the starting "ramp"
+ * portion of the separator. The starting ramp portion is located
+ * on top / left separator end and allows providing a faded
+ * appearance on that end.
+ * @param maxGradLengthEnd
+ * Specifies the maximum pixel length of the ending "ramp"
+ * portion of the separator. The ending ramp portion is located
+ * on bottom / right separator end and allows providing a faded
+ * appearance on that end.
+ * @param toEnforceAlphaColors
+ * If <code>true</code>, the fade sequences will always use alpha
+ * colors. This may affect the performance.
+ */
+ public static void paintSeparator(Component c, Graphics g, int width,
+ int height, int orientation, boolean hasShadow,
+ int maxGradLengthStart, int maxGradLengthEnd,
+ boolean toEnforceAlphaColors) {
+ SubstanceColorScheme compScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c, ColorSchemeAssociationKind.SEPARATOR,
+ ComponentState.ENABLED);
+ paintSeparator(c, g, compScheme, width, height, orientation, hasShadow,
+ maxGradLengthStart, maxGradLengthEnd, toEnforceAlphaColors);
+ }
+
+ /**
+ * Paints a separator.
+ *
+ * @param c
+ * Component.
+ * @param g
+ * Graphics context.
+ * @param scheme
+ * Color scheme.
+ * @param width
+ * Separator width.
+ * @param height
+ * Separator height.
+ * @param orientation
+ * Separator orientation.
+ * @param hasShadow
+ * If <code>true</code>, the separator painting will have shadow.
+ * @param maxGradLengthStart
+ * Specifies the maximum pixel length of the starting "ramp"
+ * portion of the separator. The starting ramp portion is located
+ * on top / left separator end and allows providing a faded
+ * appearance on that end.
+ * @param maxGradLengthEnd
+ * Specifies the maximum pixel length of the ending "ramp"
+ * portion of the separator. The ending ramp portion is located
+ * on bottom / right separator end and allows providing a faded
+ * appearance on that end.
+ * @param toEnforceAlphaColors
+ * If <code>true</code>, the fade sequences will always use alpha
+ * colors. This may affect the performance.
+ */
+ public static void paintSeparator(Component c, Graphics g,
+ SubstanceColorScheme scheme, int width, int height,
+ int orientation, boolean hasShadow, int maxGradLengthStart,
+ int maxGradLengthEnd, boolean toEnforceAlphaColors) {
+
+ DecorationAreaType decorationAreaType = SubstanceLookAndFeel
+ .getDecorationType(c);
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(c);
+ // use alpha colors when the control is in a painted decoration area
+ // (where skin can use different background colors) or in a decoration
+ // area that has overlays.
+ boolean toUseAlphaColors = ((decorationAreaType == null) || (decorationAreaType == DecorationAreaType.NONE)) ? false
+ : skin.isRegisteredAsDecorationArea(decorationAreaType)
+ || (skin.getOverlayPainters(decorationAreaType).size() > 0);
+ toUseAlphaColors = toUseAlphaColors || toEnforceAlphaColors;
+
+ Color backgrFill = SubstanceColorUtilities.getBackgroundFillColor(c);
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(c);
+ float borderStrokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(fontSize);
+ if ((orientation == JSeparator.HORIZONTAL) && (height == 0)) {
+ height = (int) Math.ceil(2.0 * borderStrokeWidth);
+ }
+ if ((orientation == JSeparator.VERTICAL) && (width == 0)) {
+ width = (int) Math.ceil(2.0 * borderStrokeWidth);
+ }
+
+ if ((width == 0) || (height == 0))
+ return;
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(fontSize, scheme
+ .getDisplayName(), width, height, orientation, hasShadow,
+ maxGradLengthStart, maxGradLengthEnd, toUseAlphaColors,
+ backgrFill.getRGB());
+
+ BufferedImage singleLine = cached.get(key);
+ if (singleLine == null) {
+ singleLine = SubstanceCoreUtilities.getBlankImage(width, height);
+ Graphics2D graphics = singleLine.createGraphics();
+
+ Color foreLight = getSeparatorLightColor(scheme);
+ Color foreDark = getSeparatorDarkColor(scheme);
+ Color back = getSeparatorShadowColor(scheme);
+
+ Color foreLight12 = toUseAlphaColors ? SubstanceColorUtilities
+ .getAlphaColor(foreLight, 32) : SubstanceColorUtilities
+ .getInterpolatedColor(foreLight, backgrFill, 0.12);
+ Color foreDark95 = toUseAlphaColors ? SubstanceColorUtilities
+ .getAlphaColor(foreDark, 240) : SubstanceColorUtilities
+ .getInterpolatedColor(foreDark, backgrFill, 0.95);
+ Color back12 = toUseAlphaColors ? SubstanceColorUtilities
+ .getAlphaColor(back, 32) : SubstanceColorUtilities
+ .getInterpolatedColor(back, backgrFill, 0.12);
+ Color back95 = toUseAlphaColors ? SubstanceColorUtilities
+ .getAlphaColor(back, 240) : SubstanceColorUtilities
+ .getInterpolatedColor(back, backgrFill, 0.95);
+ graphics.setStroke(new BasicStroke(borderStrokeWidth,
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
+ if (orientation == JSeparator.VERTICAL) {
+ int gradStart = Math.min(maxGradLengthStart, height / 2);
+ int gradEnd = Math.min(maxGradLengthEnd, height / 2);
+ graphics.translate(Math.max(0, width / 2 - 1), 0);
+ graphics.setPaint(new GradientPaint(0, 0, foreLight12, 0,
+ gradStart, foreDark95));
+ graphics.drawLine(0, 0, 0, gradStart);
+ graphics.setColor(foreDark95);
+ graphics.drawLine(0, gradStart, 0, height - gradEnd);
+ graphics.setPaint(new GradientPaint(0, height - gradEnd,
+ foreDark95, 0, height, foreLight12));
+ graphics.drawLine(0, height - gradEnd, 0, height);
+
+ if (hasShadow) {
+ int offset = (int) borderStrokeWidth;
+ graphics.setPaint(new GradientPaint(offset, 0, back12,
+ offset, gradStart, back95));
+ graphics.drawLine(offset, 0, offset, gradStart);
+ graphics.setColor(back95);
+ graphics.drawLine(offset, gradStart, offset, height
+ - gradEnd);
+ graphics.setPaint(new GradientPaint(offset, height
+ - gradEnd, back95, offset, height, back12));
+ graphics.drawLine(offset, height - gradEnd, offset, height);
+ }
+ } else {
+ // HORIZONTAL
+ int gradStart = Math.min(maxGradLengthStart, width / 2);
+ int gradEnd = Math.min(maxGradLengthEnd, width / 2);
+ graphics.translate(0, Math.max(0, height / 2 - 1));
+ graphics.setPaint(new GradientPaint(0, 0, foreLight12,
+ gradStart, 0, foreDark95));
+ graphics.drawLine(0, 0, gradStart, 0);
+ graphics.setColor(foreDark95);
+ graphics.drawLine(gradStart, 0, width - gradEnd, 0);
+ graphics.setPaint(new GradientPaint(width - gradEnd, 0,
+ foreDark95, width, 0, foreLight12));
+ graphics.drawLine(width - gradEnd, 0, width, 0);
+
+ if (hasShadow) {
+ int offset = (int) borderStrokeWidth;
+ graphics.setPaint(new GradientPaint(0, offset, back12,
+ gradStart, offset, back95));
+ graphics.drawLine(0, offset, gradStart, offset);
+ graphics.setColor(back95);
+ graphics.drawLine(gradStart, offset, width - gradEnd,
+ offset);
+ graphics.setPaint(new GradientPaint(width - gradEnd,
+ offset, back95, width, offset, back12));
+ graphics.drawLine(width - gradEnd, offset, width, offset);
+ }
+ }
+ graphics.dispose();
+ cached.put(key, singleLine);
+ }
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.drawImage(singleLine, 0, 0, null);
+ g2d.dispose();
+ }
+
+ public static Color getSeparatorShadowColor(SubstanceColorScheme scheme) {
+ return scheme.isDark() ? scheme.getDarkColor() : scheme
+ .getUltraLightColor();
+ }
+
+ public static Color getSeparatorDarkColor(SubstanceColorScheme scheme) {
+ return scheme.isDark() ? scheme.getExtraLightColor()
+ : SubstanceColorUtilities.getInterpolatedColor(scheme
+ .getMidColor(), scheme.getDarkColor(), 0.4);
+ }
+
+ public static Color getSeparatorLightColor(SubstanceColorScheme scheme) {
+ return scheme.isDark() ? scheme.getLightColor()
+ : SubstanceColorUtilities.getInterpolatedColor(scheme
+ .getLightColor(), scheme.getDarkColor(), 0.8);
+ }
+
+ /**
+ * Paints vertical separator lines.
+ *
+ * @param g
+ * Graphics context.
+ * @param c
+ * Component.
+ * @param scheme
+ * Color scheme for painting the vertical separator lines.
+ * @param y
+ * The top Y coordinate of the lines.
+ * @param x
+ * The X coordinates of the lines.
+ * @param height
+ * The height of the lines.
+ * @param fadeStartFraction
+ * The start fraction of the fade out sequence.
+ */
+ public static void paintVerticalLines(Graphics g, Component c,
+ SubstanceColorScheme scheme, int y, Collection<Integer> x,
+ int height, float fadeStartFraction) {
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(c);
+ Color backgrFill = SubstanceColorUtilities.getBackgroundFillColor(c);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(componentFontSize,
+ scheme.getDisplayName(), 0, height, SwingConstants.VERTICAL,
+ true, 0.0, fadeStartFraction, backgrFill.getRGB());
+
+ float borderStrokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize);
+ int offset = (int) borderStrokeWidth;
+ BufferedImage singleLine = cached.get(key);
+ if (singleLine == null) {
+ singleLine = SubstanceCoreUtilities.getBlankImage(Math.max(2,
+ (int) Math.ceil(2.0 * borderStrokeWidth)), height);
+ Graphics2D graphics = singleLine.createGraphics();
+
+ Color foreLight = getSeparatorLightColor(scheme);
+ Color foreDark = getSeparatorDarkColor(scheme);
+ Color back = getSeparatorShadowColor(scheme);
+
+ graphics.setStroke(new BasicStroke(borderStrokeWidth,
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
+ Color foreLight12 = SubstanceColorUtilities.getInterpolatedColor(
+ foreLight, backgrFill, 0.12);
+ Color foreDark95 = SubstanceColorUtilities.getInterpolatedColor(
+ foreDark, backgrFill, 0.95);
+ Color back12 = SubstanceColorUtilities.getInterpolatedColor(back,
+ backgrFill, 0.12);
+ Color back95 = SubstanceColorUtilities.getInterpolatedColor(back,
+ backgrFill, 0.95);
+
+ LinearGradientPaint forePaint = new LinearGradientPaint(0, 0, 0,
+ height, new float[] { 0.0f, fadeStartFraction, 1.0f },
+ new Color[] { foreDark95, foreDark95, foreLight12 });
+ graphics.setPaint(forePaint);
+ graphics.translate(borderStrokeWidth / 2, 0);
+ graphics.drawLine(0, 0, 0, height);
+
+ LinearGradientPaint backPaint = new LinearGradientPaint(0, 0, 0,
+ height, new float[] { 0.0f, fadeStartFraction, 1.0f },
+ new Color[] { back95, back95, back12 });
+ graphics.setPaint(backPaint);
+ graphics.drawLine(offset, 0, offset, height);
+
+ graphics.dispose();
+ cached.put(key, singleLine);
+ }
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ for (int lineX : x) {
+ g2d.drawImage(singleLine, lineX - offset / 2, y, null);
+ }
+ g2d.dispose();
+ }
+
+ /**
+ * Paints horizontal separator lines.
+ *
+ * @param g
+ * Graphics context.
+ * @param c
+ * Component.
+ * @param scheme
+ * Color scheme for painting the horizontal separator lines.
+ * @param x
+ * The left X coordinate of the lines.
+ * @param y
+ * The Y coordinates of the lines.
+ * @param width
+ * The width of the lines.
+ * @param fadeStartFraction
+ * The start fraction of the fade out sequence.
+ * @param isLtr
+ * If <code>true</code>, the lines are left-to-right and the fade
+ * out is on the right side. Otherwise, the fade out is on the
+ * left side.
+ */
+ public static void paintHorizontalLines(Graphics g, Component c,
+ SubstanceColorScheme scheme, int x, Collection<Integer> y,
+ int width, float fadeStartFraction, boolean isLtr) {
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(c);
+ Color backgrFill = SubstanceColorUtilities.getBackgroundFillColor(c);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(componentFontSize,
+ scheme.getDisplayName(), width, 0, SwingConstants.VERTICAL,
+ true, 0.0, fadeStartFraction, isLtr, backgrFill.getRGB());
+
+ float borderStrokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize);
+ int offset = (int) borderStrokeWidth;
+ BufferedImage singleLine = cached.get(key);
+ if (singleLine == null) {
+ singleLine = SubstanceCoreUtilities.getBlankImage(width, Math.max(
+ 2, (int) Math.ceil(2.0 * borderStrokeWidth)));
+ Graphics2D graphics = singleLine.createGraphics();
+
+ Color foreLight = getSeparatorLightColor(scheme);
+ Color foreDark = getSeparatorDarkColor(scheme);
+ Color back = getSeparatorShadowColor(scheme);
+
+ graphics.setStroke(new BasicStroke(borderStrokeWidth,
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
+ Color foreLight12 = SubstanceColorUtilities.getInterpolatedColor(
+ foreLight, backgrFill, 0.12);
+ Color foreDark95 = SubstanceColorUtilities.getInterpolatedColor(
+ foreDark, backgrFill, 0.95);
+ Color back12 = SubstanceColorUtilities.getInterpolatedColor(back,
+ backgrFill, 0.12);
+ Color back95 = SubstanceColorUtilities.getInterpolatedColor(back,
+ backgrFill, 0.95);
+
+ LinearGradientPaint forePaint = new LinearGradientPaint(0, 0,
+ width, 0, new float[] { 0.0f, fadeStartFraction, 1.0f },
+ new Color[] { isLtr ? foreDark95 : foreLight12, foreDark95,
+ isLtr ? foreLight12 : foreDark95 });
+ graphics.setPaint(forePaint);
+ graphics.drawLine(0, 0, width, 0);
+
+ LinearGradientPaint backPaint = new LinearGradientPaint(0, 9,
+ width, 0, new float[] { 0.0f, fadeStartFraction, 1.0f },
+ new Color[] { isLtr ? back95 : back12, back95,
+ isLtr ? back12 : back95 });
+ graphics.setPaint(backPaint);
+ graphics.drawLine(0, offset, width, offset);
+
+ graphics.dispose();
+ cached.put(key, singleLine);
+ }
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ for (int lineY : y) {
+ g2d.drawImage(singleLine, x, lineY - offset / 2, null);
+ }
+ g2d.dispose();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/painter/SimplisticFillPainter.java b/substance/src/main/java/org/pushingpixels/substance/internal/painter/SimplisticFillPainter.java
new file mode 100644
index 0000000..af1abe7
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/painter/SimplisticFillPainter.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.painter;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.painter.fill.StandardFillPainter;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * Fill painter that returns images with simplistic appearance. This class is
+ * <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SimplisticFillPainter extends StandardFillPainter {
+ /**
+ * Reusable instance of this painter.
+ */
+ public static final SimplisticFillPainter INSTANCE = new SimplisticFillPainter();
+
+ @Override
+ public String getDisplayName() {
+ return "Simplistic";
+ }
+
+ @Override
+ public Color getTopFillColor(SubstanceColorScheme fillScheme) {
+ return super.getMidFillColorTop(fillScheme);
+ }
+
+ @Override
+ public Color getMidFillColorTop(SubstanceColorScheme fillScheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(super
+ .getMidFillColorTop(fillScheme), super
+ .getBottomFillColor(fillScheme), 0.5);
+ }
+
+ @Override
+ public Color getTopShineColor(SubstanceColorScheme fillScheme) {
+ return null;
+ }
+
+ @Override
+ public Color getBottomShineColor(SubstanceColorScheme fillScheme) {
+ return null;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/painter/SimplisticSoftBorderPainter.java b/substance/src/main/java/org/pushingpixels/substance/internal/painter/SimplisticSoftBorderPainter.java
new file mode 100644
index 0000000..1087f63
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/painter/SimplisticSoftBorderPainter.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.painter;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.painter.border.StandardBorderPainter;
+
+/**
+ * Border painter that returns images with classic appearance. This class is
+ * <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SimplisticSoftBorderPainter extends StandardBorderPainter {
+ @Override
+ public String getDisplayName() {
+ return "Simplistic Soft Border";
+ }
+
+ @Override
+ public Color getTopBorderColor(SubstanceColorScheme borderScheme) {
+ return super.getMidBorderColor(borderScheme);
+ }
+
+ @Override
+ public Color getMidBorderColor(SubstanceColorScheme borderScheme) {
+ return super.getBottomBorderColor(borderScheme);
+ }
+
+ @Override
+ public Color getBottomBorderColor(SubstanceColorScheme borderScheme) {
+ return super.getBottomBorderColor(borderScheme);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/plugin/BasePlugin.java b/substance/src/main/java/org/pushingpixels/substance/internal/plugin/BasePlugin.java
new file mode 100755
index 0000000..61345a4
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/plugin/BasePlugin.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.plugin;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.geom.GeneralPath;
+import java.awt.image.BufferedImage;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ResourceBundle;
+
+import javax.swing.UIDefaults;
+import javax.swing.plaf.ColorUIResource;
+import javax.swing.plaf.DimensionUIResource;
+import javax.swing.plaf.FontUIResource;
+import javax.swing.plaf.IconUIResource;
+import javax.swing.plaf.InsetsUIResource;
+
+import org.pushingpixels.lafplugin.LafComponentPlugin;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.fonts.FontSet;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.icon.SubstanceIconFactory;
+
+/**
+ * Core plugin for additional UI delegates. Contains information on Quaqua and
+ * Xoetrope color chooser panels. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BasePlugin implements LafComponentPlugin {
+
+ /**
+ * Common directory for Quaqua images.
+ */
+ protected final static String commonDir = "/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/";
+
+ /**
+ * Color chooser class name from Quaqua.
+ */
+ protected final static String quaquaColorChooserClassName = "org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.Quaqua14ColorChooserUI";
+
+ /**
+ * Indication whether the Quaqua color chooser is available. The lite
+ * version strips away the Quaqua color chooser.
+ */
+ protected boolean hasQuaquaColorChooser;
+
+ /**
+ * Creates the base plugin.
+ */
+ public BasePlugin() {
+ try {
+ Class.forName(quaquaColorChooserClassName);
+ this.hasQuaquaColorChooser = true;
+ } catch (ClassNotFoundException cnfe) {
+ this.hasQuaquaColorChooser = false;
+ }
+ }
+
+ /**
+ * From Quaqua
+ */
+ protected Object makeImage(String location) {
+ return new UIDefaults.ProxyLazyValue(
+ "org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.QuaquaIconFactory",
+ "createImage", new Object[] { location });
+ }
+
+ protected static Object makeButtonStateIcon(String location, int states) {
+ return new UIDefaults.ProxyLazyValue(
+ "org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.QuaquaIconFactory",
+ "createButtonStateIcon", new Object[] { location,
+ states});
+ }
+
+ protected Object makeBufferedImage(String location) {
+ return new UIDefaults.ProxyLazyValue(
+ "org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.QuaquaIconFactory",
+ "createBufferedImage", new Object[] { location });
+ }
+
+ public static Object makeIcon(Class baseClass, String location) {
+ return new UIDefaults.ProxyLazyValue(
+ "org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.QuaquaIconFactory",
+ "createIcon", new Object[] { baseClass, location });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafplugin.LafComponentPlugin#getDefaults(java.lang.
+ * Object)
+ */
+ @Override
+ public Object[] getDefaults(Object mSkin) {
+ if (this.hasQuaquaColorChooser) {
+ ResourceBundle bundle = ResourceBundle
+ .getBundle("org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.Labels");
+ List labelsList = new LinkedList();
+ for (Enumeration i = bundle.getKeys(); i.hasMoreElements();) {
+ String key = (String) i.nextElement();
+ labelsList.add(key);
+ labelsList.add(bundle.getObject(key));
+ }
+ SubstanceSkin skin = (SubstanceSkin) mSkin;
+ final SubstanceColorScheme colorScheme = skin
+ .getActiveColorScheme(DecorationAreaType.NONE);
+ InsetsUIResource visualMargin = new InsetsUIResource(0, 0, 0, 0);
+ Color foregroundColor = new ColorUIResource(colorScheme
+ .getForegroundColor());
+ Object[] mainDefaults = new Object[] {
+ // quaqua
+ "Slider.upThumbSmall",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceIconFactory
+ .getSliderHorizontalIcon(
+ SubstanceSizeUtils
+ .getSliderIconSize(SubstanceSizeUtils
+ .getControlFontSize()) - 2,
+ true);
+ }
+ },
+
+ // quaqua
+ "Slider.leftThumbSmall",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceIconFactory
+ .getSliderVerticalIcon(
+ SubstanceSizeUtils
+ .getSliderIconSize(SubstanceSizeUtils
+ .getControlFontSize()) - 2,
+ true);
+ }
+ },
+
+ // quaqua
+ "Component.visualMargin",
+ visualMargin,
+
+ // quaqua
+ "ColorChooser.foreground",
+ foregroundColor,
+
+ // class names of default choosers
+ "ColorChooser.defaultChoosers",
+ new String[] {
+ "org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser.ColorWheelChooser",
+ "org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser.ColorSlidersChooser",
+ "org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser.ColorPalettesChooser",
+ "org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser.SwatchesChooser",
+ "org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser.CrayonsChooser",
+ "org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser.Quaqua15ColorPicker",
+ "org.pushingpixels.substance.internal.contrib.xoetrope.editor.color.ColorWheelPanel" },
+ // "ColorChooser.swatchesDefaultRecentColor", ...,
+ // "ColorChooser.swatchesRecentSwatchSize", ...,
+ "ColorChooser.swatchesSwatchSize",
+ new DimensionUIResource(5, 5),
+ "ColorChooser.resetMnemonic",
+ -1,
+ "ColorChooser.crayonsImage",
+ makeImage(commonDir + "big_crayons.png"),
+ "ColorChooser.textSliderGap",
+ 0,
+ "ColorChooser.colorPalettesIcon",
+ makeButtonStateIcon(commonDir + "palette.png", 1),
+ "ColorChooser.colorSlidersIcon",
+ makeButtonStateIcon(commonDir + "chart_bar.png", 1),
+ "ColorChooser.colorSwatchesIcon",
+ makeButtonStateIcon(commonDir + "color_swatch.png", 1),
+ "ColorChooser.colorWheelIcon",
+ makeButtonStateIcon(commonDir + "color_wheel.png", 1),
+ "ColorChooser.crayonsIcon",
+ makeButtonStateIcon(commonDir + "pencil.png", 1),
+ "ColorChooser.imagePalettesIcon",
+ makeButtonStateIcon(commonDir + "image.png", 1),
+
+ // Icon of the color picker tool
+ "ColorChooser.colorPickerIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new IconUIResource(SubstanceImageCreator
+ .getSearchIcon(15, colorScheme, true));
+ }
+ },
+
+ // Magnifying glass used as the cursor image
+ "ColorChooser.colorPickerMagnifier",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ BufferedImage result = SubstanceCoreUtilities
+ .getBlankImage(48, 48);
+ Graphics2D g = result.createGraphics();
+
+ g.setColor(Color.black);
+ g.translate(-4, -6);
+ int xc = 20;
+ int yc = 22;
+ int r = 15;
+
+ g.setStroke(new BasicStroke(2.5f));
+ g.drawOval(xc - r, yc - r, 2 * r, 2 * r);
+ g.setStroke(new BasicStroke(4.0f));
+ GeneralPath handle = new GeneralPath();
+ handle.moveTo((float) (xc + r / Math.sqrt(2.0)),
+ (float) (yc + r / Math.sqrt(2.0)));
+ handle.lineTo(45, 47);
+ g.draw(handle);
+ g.translate(4, 6);
+
+ g.setStroke(new BasicStroke(1.0f));
+ g.drawLine(16, 4, 16, 13);
+ g.drawLine(4, 16, 13, 16);
+ g.drawLine(16, 19, 16, 28);
+ g.drawLine(19, 16, 28, 16);
+
+ return result;
+ }
+ },
+ // makeBufferedImage(commonDir + "zoomer.png"),
+ // Hot spot of the magnifier cursor
+ "ColorChooser.colorPickerHotSpot",
+ new UIDefaults.ProxyLazyValue("java.awt.Point",
+ new Object[] {29, 29}),
+ // Pick point relative to hot spot
+ "ColorChooser.colorPickerPickOffset",
+ new UIDefaults.ProxyLazyValue("java.awt.Point",
+ new Object[] {-13, -13}),
+ // Rectangle used for drawing the mask of the magnifying
+ // glass
+ "ColorChooser.colorPickerGlassRect",
+ new UIDefaults.ProxyLazyValue("java.awt.Rectangle",
+ new Object[] {3, 3,
+ 26, 26}),
+ // Capture rectangle. Width and height must be equal sized
+ // and must be odd.
+ // The position of the capture rectangle is relative to the
+ // hot spot.
+ "ColorChooser.colorPickerCaptureRect",
+ new UIDefaults.ProxyLazyValue("java.awt.Rectangle",
+ new Object[] {-15, -15,
+ 5, 5}),
+ // Zoomed (magnified) capture image. Width and height must
+ // be a multiple of the capture rectangles size.
+ "ColorChooser.colorPickerZoomRect",
+ new UIDefaults.ProxyLazyValue("java.awt.Rectangle",
+ new Object[] {4, 4,
+ 25, 25}),
+
+ // // Localization support
+ // "Labels",
+ // ResourceBundleUtil
+ // .getBundle("org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.Labels"),
+
+ };
+
+ Object[] colorDefaults;
+ FontSet substanceFontSet = SubstanceLookAndFeel.getFontPolicy()
+ .getFontSet("Substance", null);
+ Font controlFont = substanceFontSet.getControlFont();
+
+ Font fontBoldBaseP1 = new FontUIResource(controlFont.deriveFont(
+ Font.BOLD, controlFont.getSize() + 1));
+
+ Font fontPlainBaseM2 = new FontUIResource(controlFont
+ .deriveFont((float) (controlFont.getSize() - 2)));
+
+ colorDefaults = new Object[] {
+ "ColorChooserUI",
+ "org.pushingpixels.substance.internal.ui.SubstanceColorChooserUI",
+
+ "ColorChooser.font", controlFont,
+
+ "ColorChooser.smallFont", fontPlainBaseM2,
+
+ "ColorChooser.crayonsFont", fontBoldBaseP1 };
+
+ Object[] labelDefaults = new Object[mainDefaults.length
+ + labelsList.size()];
+ for (int i = 0; i < mainDefaults.length; i++)
+ labelDefaults[i] = mainDefaults[i];
+ int start = mainDefaults.length;
+ for (int i = 0; i < labelsList.size(); i++)
+ labelDefaults[start + i] = labelsList.get(i);
+ mainDefaults = labelDefaults;
+
+ if (colorDefaults != null) {
+ Object[] defaults = new Object[mainDefaults.length
+ + colorDefaults.length];
+ for (int i = 0; i < mainDefaults.length; i++)
+ defaults[i] = mainDefaults[i];
+ start = mainDefaults.length;
+ for (int i = 0; i < colorDefaults.length; i++)
+ defaults[start + i] = colorDefaults[i];
+ return defaults;
+ }
+ return mainDefaults;
+ } else {
+ // Object[] defaults = new Object[labelsList.size()];
+ // for (int i = 0; i < labelsList.size(); i++)
+ // defaults[i] = labelsList.get(i);
+ return new Object[0];
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafplugin.LafPlugin#uninitialize()
+ */
+ @Override
+ public void uninitialize() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafplugin.LafPlugin#initialize()
+ */
+ @Override
+ public void initialize() {
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/plugin/BaseSkinPlugin.java b/substance/src/main/java/org/pushingpixels/substance/internal/plugin/BaseSkinPlugin.java
new file mode 100644
index 0000000..1eebd09
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/plugin/BaseSkinPlugin.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.plugin;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.pushingpixels.substance.api.skin.*;
+
+/**
+ * Core plugin for skins. See
+ * {@link org.pushingpixels.substance.internal.plugin.SubstanceSkinPlugin}
+ * interface. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov.
+ */
+public class BaseSkinPlugin implements SubstanceSkinPlugin {
+ /**
+ * Creates info object on a single skin.
+ *
+ * @param displayName
+ * Skin display name.
+ * @param skinClass
+ * Skin class.
+ * @param isDefault
+ * Indication whether the specified skin is default.
+ * @return Info object on the specified skin.
+ */
+ private static SkinInfo create(String displayName, Class<?> skinClass,
+ boolean isDefault) {
+ SkinInfo result = new SkinInfo(displayName, skinClass.getName());
+ result.setDefault(isDefault);
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.plugin.SubstanceSkinPlugin#getSkins()
+ */
+ @Override
+ public Set<SkinInfo> getSkins() {
+ Set<SkinInfo> result = new HashSet<SkinInfo>();
+ // result.add(create(AutumnSkin.NAME, AutumnSkin.class, false));
+ result.add(create(BusinessSkin.NAME, BusinessSkin.class, false));
+ result.add(create(BusinessBlackSteelSkin.NAME,
+ BusinessBlackSteelSkin.class, false));
+ result.add(create(BusinessBlueSteelSkin.NAME,
+ BusinessBlueSteelSkin.class, false));
+ result.add(create(CremeSkin.NAME, CremeSkin.class, false));
+ result.add(create(ModerateSkin.NAME, ModerateSkin.class, false));
+ result.add(create(SaharaSkin.NAME, SaharaSkin.class, false));
+ result.add(create(OfficeBlack2007Skin.NAME, OfficeBlack2007Skin.class,
+ false));
+ result.add(create(OfficeBlue2007Skin.NAME, OfficeBlue2007Skin.class,
+ false));
+ result.add(create(OfficeSilver2007Skin.NAME,
+ OfficeSilver2007Skin.class, false));
+ result.add(create(RavenSkin.NAME, RavenSkin.class, false));
+ result.add(create(GraphiteSkin.NAME, GraphiteSkin.class, false));
+ result.add(create(GraphiteGlassSkin.NAME, GraphiteGlassSkin.class,
+ false));
+ result
+ .add(create(GraphiteAquaSkin.NAME, GraphiteAquaSkin.class,
+ false));
+ result.add(create(ChallengerDeepSkin.NAME, ChallengerDeepSkin.class,
+ false));
+ result.add(create(EmeraldDuskSkin.NAME, EmeraldDuskSkin.class, false));
+ result.add(create(NebulaSkin.NAME, NebulaSkin.class, false));
+ result.add(create(NebulaBrickWallSkin.NAME, NebulaBrickWallSkin.class,
+ false));
+ result.add(create(MistSilverSkin.NAME, MistSilverSkin.class, false));
+ result.add(create(MistAquaSkin.NAME, MistAquaSkin.class, false));
+ result.add(create(AutumnSkin.NAME, AutumnSkin.class, false));
+ result.add(create(CeruleanSkin.NAME, CeruleanSkin.class, false));
+ result.add(create(CremeCoffeeSkin.NAME, CremeCoffeeSkin.class, false));
+ result.add(create(DustSkin.NAME, DustSkin.class, false));
+ result.add(create(DustCoffeeSkin.NAME, DustCoffeeSkin.class, false));
+ result.add(create(TwilightSkin.NAME, TwilightSkin.class, false));
+ result.add(create(MagellanSkin.NAME, MagellanSkin.class, false));
+ result.add(create(GeminiSkin.NAME, GeminiSkin.class, false));
+ result.add(create(MarinerSkin.NAME, MarinerSkin.class, false));
+
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.substance.plugin.SubstanceSkinPlugin#
+ * getDefaultSkinClassName()
+ */
+ @Override
+ public String getDefaultSkinClassName() {
+ return null;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/plugin/SubstanceSkinPlugin.java b/substance/src/main/java/org/pushingpixels/substance/internal/plugin/SubstanceSkinPlugin.java
new file mode 100644
index 0000000..4184fbc
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/plugin/SubstanceSkinPlugin.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.plugin;
+
+import java.util.Set;
+
+import org.pushingpixels.lafplugin.LafPlugin;
+import org.pushingpixels.substance.api.skin.SkinInfo;
+
+/**
+ * Plugin for skin extension. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SubstanceSkinPlugin extends LafPlugin {
+ /**
+ * XML tag that contains fully-qualified class name of skin plugin class.
+ */
+ public static final String TAG_SKIN_PLUGIN_CLASS = "skin-plugin-class";
+
+ /**
+ * Returns information on all available skins in <code>this</code> plugin.
+ *
+ * @return Information on all available skins in <code>this</code> plugin.
+ */
+ public Set<SkinInfo> getSkins();
+
+ /**
+ * Returns the class name of the default skin.
+ *
+ * @return The class name of the default skin.
+ */
+ public String getDefaultSkinClassName();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceButtonUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceButtonUI.java
new file mode 100644
index 0000000..f6b4755
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceButtonUI.java
@@ -0,0 +1,516 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.*;
+import javax.swing.text.View;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.internal.animation.*;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.border.SubstanceButtonBorder;
+import org.pushingpixels.substance.internal.utils.icon.GlowingIcon;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.RepeatBehavior;
+import org.pushingpixels.trident.swing.SwingRepaintCallback;
+
+/**
+ * UI for buttons in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceButtonUI extends BasicButtonUI implements
+ TransitionAwareUI, ModificationAwareUI {
+ /**
+ * Property used during the button shaper switch.
+ */
+ public static final String BORDER_COMPUTED = "substancelaf.buttonbordercomputed";
+
+ /**
+ * Property used during the button shaper switch.
+ */
+ public static final String BORDER_COMPUTING = "substancelaf.buttonbordercomputing";
+
+ /**
+ * Property used to store the original (pre-<b>Substance</b>) button border.
+ */
+ public static final String BORDER_ORIGINAL = "substancelaf.buttonborderoriginal";
+
+ /**
+ * Property used to store the original button icon.
+ */
+ public static final String ICON_ORIGINAL = "substancelaf.buttoniconoriginal";
+
+ /**
+ * Property used to store the original (pre-<b>Substance</b>) button
+ * opacity.
+ */
+ public static final String OPACITY_ORIGINAL = "substancelaf.buttonopacityoriginal";
+
+ /**
+ * Property used to lock the original (pre-<b>Substance</b>) button opacity.
+ */
+ public static final String LOCK_OPACITY = "substancelaf.lockopacity";
+
+ /**
+ * Internal property used to mark close buttons on title panes.
+ */
+ public static final String IS_TITLE_CLOSE_BUTTON = "substancelaf.internal.isTitleCloseButton";
+
+ /**
+ * Painting delegate.
+ */
+ private ButtonBackgroundDelegate delegate;
+
+ /**
+ * The matching glowing icon. Is used only when
+ * {@link AnimationConfigurationManager#isAnimationAllowed(AnimationFacet, Component)}
+ * returns true on {@link AnimationFacet#ICON_GLOW}.
+ */
+ protected GlowingIcon glowingIcon;
+
+ /**
+ * Property change listener. Listens on changes to the
+ * {@link SubstanceLookAndFeel#BUTTON_SHAPER_PROPERTY} property and
+ * {@link AbstractButton#MODEL_CHANGED_PROPERTY} property.
+ */
+ protected PropertyChangeListener substancePropertyListener;
+
+ /**
+ * Tracker for visual state transitions.
+ */
+ protected ButtonVisualStateTracker substanceVisualStateTracker;
+
+ protected AbstractButton button;
+
+ private Timeline modifiedTimeline;
+
+ private Rectangle viewRect = new Rectangle();
+
+ private Rectangle iconRect = new Rectangle();
+
+ private Rectangle textRect = new Rectangle();
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceButtonUI((AbstractButton) comp);
+ }
+
+ /**
+ * Simple constructor.
+ */
+ public SubstanceButtonUI(AbstractButton button) {
+ this.button = button;
+ this.delegate = new ButtonBackgroundDelegate();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicButtonUI#installDefaults(javax.swing.
+ * AbstractButton)
+ */
+ @Override
+ protected void installDefaults(AbstractButton b) {
+ super.installDefaults(b);
+
+ if (b.getClientProperty(SubstanceButtonUI.BORDER_ORIGINAL) == null)
+ b.putClientProperty(SubstanceButtonUI.BORDER_ORIGINAL, b
+ .getBorder());
+
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(b);
+
+ if (b.getClientProperty(SubstanceButtonUI.BORDER_COMPUTED) == null) {
+ b.setBorder(shaper.getButtonBorder(b));
+ } else {
+ Border currBorder = b.getBorder();
+ if (!(currBorder instanceof SubstanceButtonBorder)) {
+ b.setBorder(shaper.getButtonBorder(b));
+ } else {
+ SubstanceButtonBorder sbCurrBorder = (SubstanceButtonBorder) currBorder;
+ if (shaper.getClass() != sbCurrBorder.getButtonShaperClass())
+ b.setBorder(shaper.getButtonBorder(b));
+ }
+ }
+ b.putClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL, b.isOpaque());
+ b.setOpaque(false);
+
+ b.setRolloverEnabled(true);
+
+ LookAndFeel.installProperty(b, "iconTextGap", SubstanceSizeUtils
+ .getTextIconGap(SubstanceSizeUtils.getComponentFontSize(b)));
+
+ if (Boolean.TRUE.equals(b
+ .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED))) {
+ trackModificationFlag();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicButtonUI#uninstallDefaults(javax.swing.
+ * AbstractButton)
+ */
+ @Override
+ protected void uninstallDefaults(AbstractButton b) {
+ super.uninstallDefaults(b);
+
+ b.setBorder((Border) b
+ .getClientProperty(SubstanceButtonUI.BORDER_ORIGINAL));
+ b.setOpaque((Boolean) b
+ .getClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL));
+ Icon origIcon = (Icon) b
+ .getClientProperty(SubstanceButtonUI.ICON_ORIGINAL);
+ if (origIcon != null)
+ b.setIcon(origIcon);
+ b.putClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicButtonUI#createButtonListener(javax.swing
+ * .AbstractButton)
+ */
+ @Override
+ protected BasicButtonListener createButtonListener(AbstractButton b) {
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicButtonUI#installListeners(javax.swing.
+ * AbstractButton)
+ */
+ @Override
+ protected void installListeners(final AbstractButton b) {
+ super.installListeners(b);
+
+ this.substanceVisualStateTracker = new ButtonVisualStateTracker();
+ this.substanceVisualStateTracker.installListeners(b, true);
+
+ this.trackGlowingIcon();
+
+ // this.substanceButtonListener = new RolloverButtonListener(b);
+ // b.addMouseListener(this.substanceButtonListener);
+ // b.addMouseMotionListener(this.substanceButtonListener);
+ // b.addFocusListener(this.substanceButtonListener);
+ // b.addPropertyChangeListener(this.substanceButtonListener);
+ // b.addChangeListener(this.substanceButtonListener);
+
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (AbstractButton.ICON_CHANGED_PROPERTY.equals(evt
+ .getPropertyName())) {
+ trackGlowingIcon();
+ }
+
+ if (SubstanceLookAndFeel.WINDOW_MODIFIED.equals(evt
+ .getPropertyName())) {
+ boolean newValue = (Boolean) evt.getNewValue();
+ if (newValue) {
+ trackModificationFlag();
+ } else {
+ if (modifiedTimeline != null) {
+ modifiedTimeline.cancel();
+ }
+ }
+ }
+ }
+ };
+ b.addPropertyChangeListener(this.substancePropertyListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicButtonUI#uninstallListeners(javax.swing.
+ * AbstractButton)
+ */
+ @Override
+ protected void uninstallListeners(AbstractButton b) {
+ this.substanceVisualStateTracker.uninstallListeners(b);
+ this.substanceVisualStateTracker = null;
+
+ b.removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+
+ super.uninstallListeners(b);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicButtonUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return;
+
+ final AbstractButton b = (AbstractButton) c;
+
+ if (b instanceof JButton) {
+ JButton jb = (JButton) b;
+ if (RootPaneDefaultButtonTracker.isPulsating(jb)) {
+ RootPaneDefaultButtonTracker.update(jb);
+ }
+ }
+
+ FontMetrics fm = g.getFontMetrics();
+
+ Insets i = c.getInsets();
+
+ viewRect.x = i.left;
+ viewRect.y = i.top;
+ viewRect.width = b.getWidth() - (i.right + viewRect.x);
+ viewRect.height = b.getHeight() - (i.bottom + viewRect.y);
+
+ textRect.x = textRect.y = textRect.width = textRect.height = 0;
+ iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
+
+ Font f = c.getFont();
+
+ // layout the text and icon
+ String text = SwingUtilities.layoutCompoundLabel(c, fm, b.getText(), b
+ .getIcon(), b.getVerticalAlignment(), b
+ .getHorizontalAlignment(), b.getVerticalTextPosition(), b
+ .getHorizontalTextPosition(), viewRect, iconRect, textRect, b
+ .getText() == null ? 0 : b.getIconTextGap());
+
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ View v = (View) c.getClientProperty(BasicHTML.propertyKey);
+ g2d.setFont(f);
+
+ this.delegate.updateBackground(g2d, b);
+
+ if (v != null) {
+ v.paint(g2d, textRect);
+ } else {
+ this.paintButtonText(g2d, b, textRect, text);
+ }
+
+ // Paint the Icon
+ if (b.getIcon() != null) {
+ paintIcon(g2d, c, iconRect);
+ }
+
+ if (b.isFocusPainted()) {
+ SubstanceCoreUtilities.paintFocus(g, b, b, this, null, textRect,
+ 1.0f, SubstanceSizeUtils
+ .getFocusRingPadding(SubstanceSizeUtils
+ .getComponentFontSize(b)));
+ }
+
+ // g2d.setColor(Color.red);
+ // g2d.draw(iconRect);
+ // g2d.draw(viewRect);
+ // g2d.draw(textRect);
+
+ // if (isPartOfCompositeControl) {
+ // g.drawImage(offscreen, 0, 0, null);
+ // }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.ComponentUI#getPreferredSize(javax.swing.JComponent)
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ AbstractButton button = (AbstractButton) c;
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(button);
+
+ // fix for defect 263
+ Dimension superPref = super.getPreferredSize(button);
+ if (superPref == null)
+ return null;
+
+ if (shaper == null)
+ return superPref;
+
+ Dimension result = shaper.getPreferredSize(button, superPref);
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#contains(javax.swing.JComponent, int,
+ * int)
+ */
+ @Override
+ public boolean contains(JComponent c, int x, int y) {
+ return ButtonBackgroundDelegate.contains((JButton) c, x, y);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicButtonUI#paintIcon(java.awt.Graphics,
+ * javax.swing.JComponent, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ // graphics.setColor(Color.red);
+ // graphics.fill(iconRect);
+
+ // We have three types of icons:
+ // 1. The original button icon
+ // 2. The themed version of 1.
+ // 3. The glowing version of 1.
+ AbstractButton b = (AbstractButton) c;
+ Icon originalIcon = SubstanceCoreUtilities.getOriginalIcon(b, b
+ .getIcon());
+ Icon themedIcon = (!(b instanceof JRadioButton)
+ && !(b instanceof JCheckBox) && SubstanceCoreUtilities
+ .useThemedDefaultIcon(b)) ? SubstanceCoreUtilities
+ .getThemedIcon(b, originalIcon) : originalIcon;
+
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(b, g));
+ float activeAmount = this.substanceVisualStateTracker
+ .getStateTransitionTracker().getActiveStrength();
+ if (activeAmount >= 0.0f) {
+ if (AnimationConfigurationManager.getInstance().isAnimationAllowed(
+ AnimationFacet.ICON_GLOW, b)
+ && this.substanceVisualStateTracker
+ .getStateTransitionTracker().getIconGlowTracker()
+ .isPlaying()) {
+ this.glowingIcon.paintIcon(b, graphics, iconRect.x, iconRect.y);
+ } else {
+ themedIcon.paintIcon(b, graphics, iconRect.x, iconRect.y);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(b,
+ activeAmount, g));
+ originalIcon.paintIcon(b, graphics, iconRect.x, iconRect.y);
+ }
+ } else {
+ originalIcon.paintIcon(b, graphics, iconRect.x, iconRect.y);
+ }
+ graphics.dispose();
+ }
+
+ /**
+ * Paints the text.
+ *
+ * @param g
+ * Graphic context
+ * @param button
+ * Button
+ * @param textRect
+ * Text rectangle
+ * @param text
+ * Text to paint
+ */
+ protected void paintButtonText(Graphics g, AbstractButton button,
+ Rectangle textRect, String text) {
+ SubstanceTextUtilities.paintText(g, button, textRect, text, button
+ .getDisplayedMnemonicIndex());
+ }
+
+ /**
+ * Tracks possible usage of glowing icon.
+ *
+ */
+ protected void trackGlowingIcon() {
+ Icon currIcon = this.button.getIcon();
+ if (currIcon instanceof GlowingIcon)
+ return;
+ if (currIcon == null)
+ return;
+ this.glowingIcon = new GlowingIcon(currIcon,
+ this.substanceVisualStateTracker.getStateTransitionTracker()
+ .getIconGlowTracker());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ this.paint(g, c);
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return this.contains(this.button, me.getX(), me.getY());
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.substanceVisualStateTracker.getStateTransitionTracker();
+ }
+
+ private void trackModificationFlag() {
+ this.modifiedTimeline = new Timeline(this.button);
+ AnimationConfigurationManager.getInstance().configureModifiedTimeline(
+ modifiedTimeline);
+ this.modifiedTimeline
+ .addCallback(new SwingRepaintCallback(this.button));
+ this.modifiedTimeline.playLoop(RepeatBehavior.REVERSE);
+ }
+
+ @Override
+ public Timeline getModificationTimeline() {
+ return this.modifiedTimeline;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceCheckBoxMenuItemUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceCheckBoxMenuItemUI.java
new file mode 100644
index 0000000..84da9aa
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceCheckBoxMenuItemUI.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicCheckBoxMenuItemUI;
+
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.icon.CheckBoxMenuItemIcon;
+import org.pushingpixels.substance.internal.utils.menu.MenuUtilities;
+import org.pushingpixels.substance.internal.utils.menu.SubstanceMenu;
+import org.pushingpixels.substance.internal.utils.menu.MenuUtilities.MenuPropertyListener;
+
+/**
+ * UI for check box menu items in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceCheckBoxMenuItemUI extends BasicCheckBoxMenuItemUI
+ implements SubstanceMenu, TransitionAwareUI {
+ /**
+ * Rollover listener.
+ */
+ protected RolloverMenuItemListener substanceRolloverListener;
+
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Listens on all changes to the underlying menu item.
+ */
+ protected MenuPropertyListener substanceMenuPropertyListener;
+
+ protected PropertyChangeListener substancePropertyListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ JCheckBoxMenuItem item = (JCheckBoxMenuItem) comp;
+ // add rollover listener
+ item.setRolloverEnabled(true);
+ return new SubstanceCheckBoxMenuItemUI((JCheckBoxMenuItem) comp);
+ }
+
+ public SubstanceCheckBoxMenuItemUI(JCheckBoxMenuItem menuItem) {
+ this.stateTransitionTracker = new StateTransitionTracker(menuItem,
+ menuItem.getModel());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuItemUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ // Improving performance on big menus.
+ this.substanceMenuPropertyListener = new MenuPropertyListener(
+ this.menuItem);
+ this.substanceMenuPropertyListener.install();
+ // fix for defect 109 - storing reference to rollover listener
+ this.substanceRolloverListener = new RolloverMenuItemListener(
+ this.menuItem, this.stateTransitionTracker);
+ this.menuItem.addMouseListener(this.substanceRolloverListener);
+
+ this.stateTransitionTracker.registerModelListeners();
+
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (AbstractButton.MODEL_CHANGED_PROPERTY.equals(evt
+ .getPropertyName())) {
+ stateTransitionTracker.setModel((ButtonModel) evt
+ .getNewValue());
+ }
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (menuItem != null) {
+ menuItem.updateUI();
+ }
+ }
+ });
+ }
+ }
+ };
+ this.menuItem.addPropertyChangeListener(this.substancePropertyListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuItemUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ super.uninstallListeners();
+
+ // Improving performance on big menus.
+ this.substanceMenuPropertyListener.uninstall();
+ this.substanceMenuPropertyListener = null;
+
+ // fix for defect 109 - unregistering rollover listener
+ this.menuItem.removeMouseListener(this.substanceRolloverListener);
+ this.substanceRolloverListener = null;
+
+ this.menuItem
+ .removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+
+ this.stateTransitionTracker.unregisterModelListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuItemUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ if (this.checkIcon == null || this.checkIcon instanceof UIResource) {
+ this.checkIcon = new CheckBoxMenuItemIcon(this.menuItem,
+ 1 + SubstanceSizeUtils
+ .getMenuCheckMarkSize(SubstanceSizeUtils
+ .getComponentFontSize(this.menuItem)));
+ }
+ this.defaultTextIconGap = SubstanceSizeUtils
+ .getTextIconGap(SubstanceSizeUtils
+ .getComponentFontSize(this.menuItem));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getAssociatedMenuItem()
+ */
+ @Override
+ public JMenuItem getAssociatedMenuItem() {
+ return this.menuItem;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getAcceleratorFont()
+ */
+ @Override
+ public Font getAcceleratorFont() {
+ return this.acceleratorFont;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getArrowIcon()
+ */
+ @Override
+ public Icon getArrowIcon() {
+ return this.arrowIcon;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getCheckIcon()
+ */
+ @Override
+ public Icon getCheckIcon() {
+ return this.checkIcon;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getDefaultTextIconGap()
+ */
+ @Override
+ public int getDefaultTextIconGap() {
+ return this.defaultTextIconGap;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicMenuItemUI#getPreferredMenuItemSize(javax
+ * .swing.JComponent, javax.swing.Icon, javax.swing.Icon, int)
+ */
+ @Override
+ protected Dimension getPreferredMenuItemSize(JComponent c, Icon checkIcon,
+ Icon arrowIcon, int defaultTextIconGap) {
+ Dimension superDim = super.getPreferredMenuItemSize(c, checkIcon,
+ arrowIcon, defaultTextIconGap);
+ return new Dimension(MenuUtilities.getPreferredWidth(menuItem),
+ superDim.height);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicMenuItemUI#paintMenuItem(java.awt.Graphics,
+ * javax.swing.JComponent, javax.swing.Icon, javax.swing.Icon,
+ * java.awt.Color, java.awt.Color, int)
+ */
+ @Override
+ protected void paintMenuItem(Graphics g, JComponent c, Icon checkIcon,
+ Icon arrowIcon, Color background, Color foreground,
+ int defaultTextIconGap) {
+ MenuUtilities.paintMenuItem(g, menuItem, checkIcon, arrowIcon,
+ defaultTextIconGap);
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return true;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceCheckBoxUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceCheckBoxUI.java
new file mode 100644
index 0000000..1a6f882
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceCheckBoxUI.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.AlphaComposite;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicButtonListener;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * UI for check boxes in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceCheckBoxUI extends SubstanceRadioButtonUI {
+ /**
+ * Prefix for the checkbox-related properties in the {@link UIManager}.
+ */
+ private final static String propertyPrefix = "CheckBox" + ".";
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceCheckBoxUI((JToggleButton) comp);
+ }
+
+ /**
+ * Hash map for storing icons.
+ */
+ private static LazyResettableHashMap<Icon> icons = new LazyResettableHashMap<Icon>(
+ "SubstanceCheckBoxUI");
+
+ /**
+ * Simple constructor.
+ *
+ * @param button
+ * The associated button.
+ */
+ public SubstanceCheckBoxUI(JToggleButton button) {
+ super(button);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicRadioButtonUI#getPropertyPrefix()
+ */
+ @Override
+ protected String getPropertyPrefix() {
+ return propertyPrefix;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.SubstanceRadioButtonUI#installDefaults(javax
+ * .swing .AbstractButton)
+ */
+ @Override
+ protected void installDefaults(AbstractButton b) {
+ super.installDefaults(b);
+
+ button.setRolloverEnabled(true);
+
+ Border border = b.getBorder();
+ if (border == null || border instanceof UIResource) {
+ b.setBorder(SubstanceSizeUtils.getCheckBoxBorder(SubstanceSizeUtils
+ .getComponentFontSize(b), b.getComponentOrientation()
+ .isLeftToRight()));
+ }
+ }
+
+ /**
+ * Returns the icon that matches the current and previous states of the
+ * checkbox.
+ *
+ * @param button
+ * Button (should be {@link JCheckBox}).
+ * @param stateTransitionTracker
+ * state of the checkbox.
+ * @return Matching icon.
+ */
+ private static Icon getIcon(JToggleButton button,
+ StateTransitionTracker stateTransitionTracker) {
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(button);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(button);
+ ComponentState currState = modelStateInfo.getCurrModelState();
+
+ SubstanceColorScheme baseFillColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, ColorSchemeAssociationKind.FILL,
+ currState);
+ SubstanceColorScheme baseMarkColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, ColorSchemeAssociationKind.MARK,
+ currState);
+ SubstanceColorScheme baseBorderColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, ColorSchemeAssociationKind.BORDER,
+ currState);
+ float visibility = stateTransitionTracker
+ .getFacetStrength(ComponentStateFacet.SELECTION);
+ boolean isCheckMarkFadingOut = !currState
+ .isFacetActive(ComponentStateFacet.SELECTION);
+
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(button);
+ int checkMarkSize = SubstanceSizeUtils.getCheckBoxMarkSize(fontSize);
+
+ HashMapKey keyBase = SubstanceCoreUtilities.getHashKey(fontSize,
+ checkMarkSize, fillPainter.getDisplayName(), borderPainter
+ .getDisplayName(),
+ baseFillColorScheme.getDisplayName(), baseMarkColorScheme
+ .getDisplayName(), baseBorderColorScheme
+ .getDisplayName(), visibility, isCheckMarkFadingOut);
+ Icon iconBase = icons.get(keyBase);
+ if (iconBase == null) {
+ iconBase = new ImageIcon(SubstanceImageCreator.getCheckBox(button,
+ fillPainter, borderPainter, checkMarkSize, currState,
+ baseFillColorScheme, baseMarkColorScheme,
+ baseBorderColorScheme, visibility, isCheckMarkFadingOut));
+ icons.put(keyBase, iconBase);
+ }
+ if (currState.isDisabled() || (activeStates.size() == 1)) {
+ return iconBase;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(iconBase
+ .getIconWidth(), iconBase.getIconHeight());
+ Graphics2D g2d = result.createGraphics();
+ // draw the base layer
+ iconBase.paintIcon(button, g2d, 0, 0);
+
+ // draw other active layers
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ // System.out.println("Painting state " + activeState + "[curr is "
+ // + currState + "] with " + activeEntry.getValue());
+ if (activeState == currState)
+ continue;
+
+ float stateContribution = activeEntry.getValue().getContribution();
+ if (stateContribution > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(stateContribution));
+ SubstanceColorScheme fillColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button,
+ ColorSchemeAssociationKind.FILL, activeState);
+ SubstanceColorScheme markColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button,
+ ColorSchemeAssociationKind.MARK, activeState);
+ SubstanceColorScheme borderColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button,
+ ColorSchemeAssociationKind.BORDER, activeState);
+
+ HashMapKey keyLayer = SubstanceCoreUtilities.getHashKey(
+ fontSize, checkMarkSize, fillPainter.getDisplayName(),
+ borderPainter.getDisplayName(), fillColorScheme
+ .getDisplayName(), markColorScheme
+ .getDisplayName(), borderColorScheme
+ .getDisplayName(), visibility);
+ Icon iconLayer = icons.get(keyLayer);
+ if (iconLayer == null) {
+ iconLayer = new ImageIcon(SubstanceImageCreator
+ .getCheckBox(button, fillPainter, borderPainter,
+ checkMarkSize, currState, fillColorScheme,
+ markColorScheme, borderColorScheme,
+ visibility, isCheckMarkFadingOut));
+ icons.put(keyLayer, iconLayer);
+ }
+
+ iconLayer.paintIcon(button, g2d, 0, 0);
+ }
+ }
+
+ g2d.dispose();
+ return new ImageIcon(result);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicButtonUI#createButtonListener(javax.swing
+ * .AbstractButton)
+ */
+ @Override
+ protected BasicButtonListener createButtonListener(AbstractButton b) {
+ return new RolloverButtonListener(b, this.stateTransitionTracker);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicRadioButtonUI#getDefaultIcon()
+ */
+ @Override
+ public Icon getDefaultIcon() {
+ return SubstanceCheckBoxUI.getIcon(this.button,
+ this.stateTransitionTracker);
+ }
+
+ /**
+ * Returns memory usage string.
+ *
+ * @return Memory usage string.
+ */
+ public static String getMemoryUsage() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("SubstanceCheckBox: \n");
+ sb.append("\t" + SubstanceCheckBoxUI.icons.size() + " icons");
+ return sb.toString();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceColorChooserUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceColorChooserUI.java
new file mode 100644
index 0000000..12f7bb8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceColorChooserUI.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.lang.reflect.Method;
+import java.security.AccessControlException;
+import java.util.*;
+
+import javax.swing.JComponent;
+import javax.swing.UIManager;
+import javax.swing.colorchooser.AbstractColorChooserPanel;
+import javax.swing.plaf.ComponentUI;
+
+import org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.Quaqua14ColorChooserUI;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+
+/**
+ * UI for color chooser in <b>Substance</b> look and feel. This UI delegate is
+ * removed in Lite versions of <b>Substance</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceColorChooserUI extends Quaqua14ColorChooserUI {
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceColorChooserUI();
+ }
+
+ @Override
+ protected AbstractColorChooserPanel[] createDefaultChoosers() {
+ String[] defaultChoosers = (String[]) UIManager
+ .get("ColorChooser.defaultChoosers");
+
+ List<AbstractColorChooserPanel> panelList = new LinkedList<AbstractColorChooserPanel>();
+ // AbstractColorChooserPanel[] panels = new
+ // AbstractColorChooserPanel[defaultChoosers.length];
+ for (int i = 0; i < defaultChoosers.length; i++) {
+ try {
+ Method setBundleMethod = Class.forName(defaultChoosers[i])
+ .getMethod("setLabelBundle",
+ new Class[] { ResourceBundle.class });
+ setBundleMethod.invoke(null,
+ new Object[] { SubstanceCoreUtilities
+ .getResourceBundle(null) });
+ } catch (Throwable t) {
+ // ignore - either the method doesn't exist or the invocation
+ // failed. Nothing to do in both cases.
+ }
+ try {
+ AbstractColorChooserPanel panel = (AbstractColorChooserPanel) Class
+ .forName(defaultChoosers[i]).newInstance();
+ panelList.add(panel);
+ } catch (AccessControlException e) {
+ // ignore - happens on unsigned apps in WebStart environment
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new InternalError("Unable to instantiate "
+ + defaultChoosers[i]);
+ }
+ }
+ return panelList.toArray(new AbstractColorChooserPanel[0]);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceComboBoxUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceComboBoxUI.java
new file mode 100755
index 0000000..0bc19ec
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceComboBoxUI.java
@@ -0,0 +1,781 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicComboBoxUI;
+import javax.swing.plaf.basic.ComboPopup;
+import javax.swing.text.JTextComponent;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceConstants.Side;
+import org.pushingpixels.substance.api.renderers.SubstanceDefaultComboBoxRenderer;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities.TextComponentAware;
+import org.pushingpixels.substance.internal.utils.border.SubstanceTextComponentBorder;
+import org.pushingpixels.substance.internal.utils.combo.*;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
+
+/**
+ * UI for combo boxes in <b>Substance </b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ * @author Thomas Bierhance http://www.orbital-computer.de/JComboBox/
+ * @author inostock
+ */
+public class SubstanceComboBoxUI extends BasicComboBoxUI implements
+ TransitionAwareUI {
+ /**
+ * Property change handler on <code>enabled</code> property,
+ * <code>componentOrientation</code> property and on
+ * {@link SubstanceLookAndFeel#COMBO_BOX_POPUP_FLYOUT_ORIENTATION} property.
+ */
+ protected ComboBoxPropertyChangeHandler substanceChangeHandler;
+
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Surrogate button model for tracking the state transitions.
+ */
+ protected ButtonModel transitionModel;
+
+ /**
+ * Listener for transition animations.
+ */
+ protected RolloverTextControlListener substanceRolloverListener;
+
+ /**
+ * Painting delegate.
+ */
+ protected ComboBoxBackgroundDelegate delegate;
+
+ protected Icon uneditableArrowIcon;
+
+ protected Insets layoutInsets;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ SubstanceComboBoxUI ui = new SubstanceComboBoxUI((JComboBox) comp);
+ ui.comboBox = (JComboBox) comp;
+ ui.comboBox.setOpaque(false);
+ return ui;
+ }
+
+ @Override
+ public void installUI(JComponent c) {
+ super.installUI(c);
+
+ c.putClientProperty(SubstanceCoreUtilities.TEXT_COMPONENT_AWARE,
+ new TextComponentAware<JComboBox>() {
+ @Override
+ public JTextComponent getTextComponent(JComboBox t) {
+ if (t.isEditable()) {
+ Component editorComp = t.getEditor()
+ .getEditorComponent();
+ if (editorComp instanceof JTextComponent) {
+ return (JTextComponent) editorComp;
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public void uninstallUI(JComponent c) {
+ c.putClientProperty(SubstanceCoreUtilities.TEXT_COMPONENT_AWARE, null);
+
+ super.uninstallUI(c);
+ }
+
+ public SubstanceComboBoxUI(JComboBox combo) {
+ this.comboBox = combo;
+
+ this.transitionModel = new DefaultButtonModel();
+ this.transitionModel.setArmed(false);
+ this.transitionModel.setSelected(false);
+ this.transitionModel.setPressed(false);
+ this.transitionModel.setRollover(false);
+ this.transitionModel.setEnabled(combo.isEnabled());
+
+ this.stateTransitionTracker = new StateTransitionTracker(this.comboBox,
+ this.transitionModel);
+
+ this.delegate = new ComboBoxBackgroundDelegate();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#createArrowButton()
+ */
+ @Override
+ protected JButton createArrowButton() {
+ SubstanceDropDownButton result = new SubstanceDropDownButton(
+ this.comboBox);
+ result.setFont(this.comboBox.getFont());
+ result.setIcon(getCurrentIcon(result));
+ return result;
+ }
+
+ /**
+ * Returns the icon for the specified arrow button.
+ *
+ * @param button
+ * Arrow button.
+ * @return Icon for the specified button.
+ */
+ private Icon getCurrentIcon(JButton button) {
+ Icon icon = SubstanceCoreUtilities
+ .getArrowIcon(button, SubstanceCoreUtilities
+ .getPopupFlyoutOrientation(this.comboBox));
+ return icon;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#createRenderer()
+ */
+ @Override
+ protected ListCellRenderer createRenderer() {
+ return new SubstanceDefaultComboBoxRenderer.SubstanceUIResource(
+ this.comboBox);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ this.substanceChangeHandler = new ComboBoxPropertyChangeHandler();
+ this.comboBox.addPropertyChangeListener(this.substanceChangeHandler);
+
+ this.substanceRolloverListener = new RolloverTextControlListener(
+ this.comboBox, this, this.transitionModel);
+ this.substanceRolloverListener.registerListeners();
+
+ this.stateTransitionTracker.registerModelListeners();
+ this.stateTransitionTracker.registerFocusListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.stateTransitionTracker.unregisterModelListeners();
+ this.stateTransitionTracker.unregisterFocusListeners();
+
+ this.comboBox.removePropertyChangeListener(this.substanceChangeHandler);
+ this.substanceChangeHandler = null;
+
+ this.substanceRolloverListener.unregisterListeners();
+ this.substanceRolloverListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+
+ // this icon must be created after the font has been installed
+ // on the combobox
+ this.uneditableArrowIcon = SubstanceCoreUtilities.getArrowIcon(
+ this.comboBox,
+ new TransitionAwareIcon.TransitionAwareUIDelegate() {
+ @Override
+ public TransitionAwareUI getTransitionAwareUI() {
+ return (TransitionAwareUI) comboBox.getUI();
+ }
+ }, SubstanceCoreUtilities
+ .getPopupFlyoutOrientation(this.comboBox));
+ this.updateComboBoxBorder();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#createLayoutManager()
+ */
+ @Override
+ protected LayoutManager createLayoutManager() {
+ return new SubstanceComboBoxLayoutManager();
+ }
+
+ /**
+ * Layout manager for combo box.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class SubstanceComboBoxLayoutManager extends
+ BasicComboBoxUI.ComboBoxLayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container parent) {
+ JComboBox cb = (JComboBox) parent;
+
+ int width = cb.getWidth();
+ int height = cb.getHeight();
+
+ Insets insets = layoutInsets;
+ int buttonWidth = SubstanceSizeUtils
+ .getScrollBarWidth(SubstanceSizeUtils
+ .getComponentFontSize(comboBox));
+ // buttonWidth = Math.max(buttonWidth,
+ // arrowButton.getPreferredSize().width);
+
+ if (arrowButton != null) {
+ if (!comboBox.isEditable()) {
+ arrowButton.setBounds(0, 0, 0, 0);
+ } else {
+ if (cb.getComponentOrientation().isLeftToRight()) {
+ arrowButton.setBounds(width - buttonWidth
+ - insets.right, 0, buttonWidth + insets.right,
+ height);
+ } else {
+ arrowButton.setBounds(0, 0, buttonWidth + insets.left,
+ height);
+ }
+ }
+ }
+ if (editor != null) {
+ Rectangle r = rectangleForCurrentValue();
+ editor.setBounds(r);
+ }
+ }
+ }
+
+ @Override
+ protected Rectangle rectangleForCurrentValue() {
+ int width = this.comboBox.getWidth();
+ int height = this.comboBox.getHeight();
+ Insets insets = this.layoutInsets;
+ int buttonWidth = SubstanceSizeUtils
+ .getScrollBarWidth(SubstanceSizeUtils
+ .getComponentFontSize(comboBox));
+ if (this.comboBox.getComponentOrientation().isLeftToRight()) {
+ return new Rectangle(insets.left, insets.top, width - insets.left
+ - insets.right - buttonWidth, height - insets.top
+ - insets.bottom);
+ } else {
+ int startX = insets.left + buttonWidth;
+ return new Rectangle(startX, insets.top, width - startX
+ - insets.right, height - insets.top - insets.bottom);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#getDefaultSize()
+ */
+ @Override
+ protected Dimension getDefaultSize() {
+ Component rend = new SubstanceDefaultComboBoxRenderer(this.comboBox)
+ .getListCellRendererComponent(listBox, " ", -1, false, false);
+ rend.setFont(this.comboBox.getFont());
+
+ return rend.getPreferredSize();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicComboBoxUI#getMinimumSize(javax.swing.JComponent
+ * )
+ */
+ @Override
+ public Dimension getMinimumSize(JComponent c) {
+ if (!this.isMinimumSizeDirty) {
+ return new Dimension(this.cachedMinimumSize);
+ }
+
+ // Dimension size = null;
+ //
+ // if (!this.comboBox.isEditable() && this.arrowButton != null
+ // && this.arrowButton instanceof SubstanceComboBoxButton) {
+ //
+ SubstanceDropDownButton button = (SubstanceDropDownButton) this.arrowButton;
+ Insets buttonInsets = button.getInsets();
+ Insets insets = this.comboBox.getInsets();
+
+ Dimension size = this.getDisplaySize();
+
+ size.width += insets.left + insets.right;
+ size.width += buttonInsets.left + buttonInsets.right;
+ size.width += button.getMinimumSize().getWidth();
+ size.height += insets.top + insets.bottom;
+ // } else if (this.comboBox.isEditable() && this.arrowButton != null
+ // && this.editor != null) {
+ // size = super.getMinimumSize(c);
+ // } else {
+ // size = super.getMinimumSize(c);
+ // }
+
+ this.cachedMinimumSize.setSize(size.width, size.height);
+ this.isMinimumSizeDirty = false;
+
+ return new Dimension(this.cachedMinimumSize);
+ }
+
+ /**
+ * This property change handler changes combo box arrow icon based on the
+ * enabled status of the combo box.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public class ComboBoxPropertyChangeHandler extends PropertyChangeHandler {
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicComboBoxUI$PropertyChangeHandler#
+ * propertyChange(java.beans.PropertyChangeEvent)
+ */
+ @Override
+ public void propertyChange(final PropertyChangeEvent e) {
+ String propertyName = e.getPropertyName();
+
+ if (propertyName.equals("componentOrientation")) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (SubstanceComboBoxUI.this.comboBox == null)
+ return;
+ final ComponentOrientation newOrientation = (ComponentOrientation) e
+ .getNewValue();
+ final ListCellRenderer cellRenderer = SubstanceComboBoxUI.this.comboBox
+ .getRenderer();
+ final ComboBoxEditor editor = SubstanceComboBoxUI.this.comboBox
+ .getEditor();
+ if (SubstanceComboBoxUI.this.popup instanceof Component) {
+ final Component cPopup = (Component) SubstanceComboBoxUI.this.popup;
+ cPopup.applyComponentOrientation(newOrientation);
+ cPopup.doLayout();
+ }
+ if (cellRenderer instanceof Component) {
+ ((Component) cellRenderer)
+ .applyComponentOrientation(newOrientation);
+ }
+ if ((editor != null)
+ && (editor.getEditorComponent() != null)) {
+ (editor.getEditorComponent())
+ .applyComponentOrientation(newOrientation);
+ }
+ if (SubstanceComboBoxUI.this.comboBox != null)
+ SubstanceComboBoxUI.this.comboBox.repaint();
+
+ configureArrowButtonStraightSide();
+ }
+ });
+ }
+
+ if (SubstanceLookAndFeel.COMBO_BOX_POPUP_FLYOUT_ORIENTATION
+ .equals(propertyName)) {
+ ((SubstanceDropDownButton) arrowButton)
+ .setIcon(SubstanceCoreUtilities
+ .getArrowIcon(
+ arrowButton,
+ SubstanceCoreUtilities
+ .getPopupFlyoutOrientation(SubstanceComboBoxUI.this.comboBox)));
+
+ }
+
+ if ("font".equals(propertyName)) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (comboBox != null)
+ comboBox.updateUI();
+ }
+ });
+ }
+
+ if ("background".equals(propertyName)) {
+ if (comboBox.isEditable()) {
+ comboBox.getEditor().getEditorComponent().setBackground(
+ comboBox.getBackground());
+ popup.getList().setBackground(comboBox.getBackground());
+ }
+ }
+
+ if ("editable".equals(propertyName)) {
+ updateComboBoxBorder();
+ isMinimumSizeDirty = true;
+ }
+
+ if ("enabled".equals(propertyName)) {
+ SubstanceComboBoxUI.this.transitionModel.setEnabled(comboBox
+ .isEnabled());
+ }
+ // Do not call super - fix for bug 63
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#createPopup()
+ */
+ @Override
+ protected ComboPopup createPopup() {
+ final ComboPopup sPopup = new SubstanceComboPopup(this.comboBox);
+
+ final ComponentOrientation currOrientation = this.comboBox
+ .getComponentOrientation();
+
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (SubstanceComboBoxUI.this.comboBox == null)
+ return;
+
+ if (sPopup instanceof Component) {
+ final Component cPopup = (Component) sPopup;
+ cPopup.applyComponentOrientation(currOrientation);
+ cPopup.doLayout();
+ }
+ ListCellRenderer cellRenderer = SubstanceComboBoxUI.this.comboBox
+ .getRenderer();
+ if (cellRenderer instanceof Component) {
+ ((Component) cellRenderer)
+ .applyComponentOrientation(currOrientation);
+ }
+ ComboBoxEditor editor = SubstanceComboBoxUI.this.comboBox
+ .getEditor();
+ if ((editor != null) && (editor.getEditorComponent() != null)) {
+ (editor.getEditorComponent())
+ .applyComponentOrientation(currOrientation);
+ }
+ SubstanceComboBoxUI.this.comboBox.repaint();
+ }
+ });
+ return sPopup;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ int width = this.comboBox.getWidth();
+ int height = this.comboBox.getHeight();
+ Insets insets = this.comboBox.getInsets();
+
+ int componentFontSize = SubstanceSizeUtils
+ .getComponentFontSize(this.comboBox);
+ if (this.comboBox.isEditable()) {
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize));
+ Shape contour = SubstanceOutlineUtilities
+ .getBaseOutline(
+ width,
+ height,
+ Math
+ .max(
+ 0,
+ 2.0f
+ * SubstanceSizeUtils
+ .getClassicButtonCornerRadius(componentFontSize)
+ - borderDelta), null,
+ borderDelta);
+
+ graphics.setColor(SubstanceTextUtilities
+ .getTextBackgroundFillColor(this.comboBox));
+ graphics.fill(contour);
+ } else {
+ this.delegate.updateBackground(graphics, this.comboBox,
+ this.transitionModel);
+
+ Icon icon = this.uneditableArrowIcon;
+ int iw = icon.getIconWidth();
+ int ih = icon.getIconHeight();
+ int origButtonWidth = SubstanceSizeUtils
+ .getScrollBarWidth(componentFontSize);
+ if (this.comboBox.getComponentOrientation().isLeftToRight()) {
+ int iconX = width - origButtonWidth - insets.right / 2
+ + (origButtonWidth - iw) / 2;
+ int iconY = insets.top
+ + (height - insets.top - insets.bottom - ih) / 2;
+ icon.paintIcon(this.comboBox, graphics, iconX, iconY);
+ } else {
+ int iconX = insets.left / 2 + (origButtonWidth - iw) / 2;
+ int iconY = insets.top
+ + (height - insets.top - insets.bottom - ih) / 2;
+ icon.paintIcon(this.comboBox, graphics, iconX, iconY);
+ }
+ }
+
+ hasFocus = comboBox.hasFocus();
+ if (!comboBox.isEditable()) {
+ Rectangle r = rectangleForCurrentValue();
+
+ ListCellRenderer renderer = this.comboBox.getRenderer();
+ Component rendererComponent;
+ if (hasFocus) {
+ rendererComponent = renderer.getListCellRendererComponent(
+ this.listBox, this.comboBox.getSelectedItem(), -1,
+ true, hasFocus);
+ } else {
+ rendererComponent = renderer.getListCellRendererComponent(
+ this.listBox, this.comboBox.getSelectedItem(), -1,
+ false, hasFocus);
+ }
+ rendererComponent.setFont(this.comboBox.getFont());
+
+ // Fix for 4238829: should lay out the JPanel.
+ boolean shouldValidate = false;
+ if (rendererComponent instanceof JPanel) {
+ shouldValidate = true;
+ }
+
+ // SubstanceCoreUtilities.workaroundBug6576507(graphics);
+
+ if (this.comboBox.getComponentOrientation().isLeftToRight()) {
+ this.currentValuePane.paintComponent(graphics,
+ rendererComponent, this.comboBox, r.x, r.y, r.width,
+ r.height, shouldValidate);
+ } else {
+ this.currentValuePane.paintComponent(graphics,
+ rendererComponent, this.comboBox, r.x, r.y, r.width,
+ r.height, shouldValidate);
+ }
+ }
+
+ if (!this.comboBox.isEditable()) {
+ Rectangle r = new Rectangle(insets.left, layoutInsets.top, width
+ - insets.left - insets.right, height - layoutInsets.top
+ - layoutInsets.bottom);
+ this.paintFocus(graphics, r);
+ }
+
+ graphics.dispose();
+ }
+
+ /**
+ * Paints the focus indication.
+ *
+ * @param g
+ * Graphics.
+ * @param bounds
+ * Bounds for text.
+ */
+ protected void paintFocus(Graphics g, Rectangle bounds) {
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(this.comboBox);
+ int focusRingPadding = SubstanceSizeUtils.getFocusRingPadding(fontSize) / 2;
+ int x = bounds.x;
+ int y = bounds.y;
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.translate(x, y);
+
+ SubstanceCoreUtilities.paintFocus(g2d, this.comboBox, this.comboBox,
+ this, SubstanceOutlineUtilities.getBaseOutline(bounds.width,
+ bounds.height, SubstanceSizeUtils
+ .getClassicButtonCornerRadius(fontSize), null,
+ 0), bounds, 1.0f, focusRingPadding);
+
+ g2d.dispose();
+ }
+
+ /**
+ * Returns the popup of the associated combobox.
+ *
+ * @return The popup of the associated combobox.
+ */
+ public ComboPopup getPopup() {
+ return this.popup;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#configureArrowButton()
+ */
+ @Override
+ public void configureArrowButton() {
+ super.configureArrowButton();
+ // Mustang decided to make the arrow button focusable on
+ // focusable comboboxes
+ this.arrowButton.setFocusable(false);
+
+ // this.substanceFocusListener = new FocusListener() {
+ // public void focusGained(FocusEvent e) {
+ // arrowButton.setSelected(true);
+ // }
+ //
+ // public void focusLost(FocusEvent e) {
+ // arrowButton.setSelected(false);
+ // }
+ // };
+ // this.arrowButton.setSelected(this.comboBox.hasFocus());
+ // this.comboBox.addFocusListener(this.substanceFocusListener);
+
+ this.configureArrowButtonStraightSide();
+ }
+
+ /**
+ * Configures the straight side of the arrow button.
+ */
+ protected void configureArrowButtonStraightSide() {
+ this.arrowButton.putClientProperty(
+ SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY, this.comboBox
+ .getComponentOrientation().isLeftToRight() ? Side.LEFT
+ : Side.RIGHT);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#unconfigureArrowButton()
+ */
+ @Override
+ public void unconfigureArrowButton() {
+ // this.comboBox.removeFocusListener(this.substanceFocusListener);
+ // this.substanceFocusListener = null;
+ super.unconfigureArrowButton();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#configureEditor()
+ */
+ @Override
+ protected void configureEditor() {
+ super.configureEditor();
+ // This for Mustang - setting Substance once again adds a border on
+ // the text field in the combo editor.
+ if (this.editor instanceof JComponent) {
+ Insets ins = SubstanceSizeUtils
+ .getComboTextBorderInsets(SubstanceSizeUtils
+ .getComponentFontSize(this.editor));
+ ((JComponent) this.editor).setBorder(new EmptyBorder(ins.top,
+ ins.left, ins.bottom, ins.right));
+ this.editor.setBackground(this.comboBox.getBackground());
+ // ((JComponent) this.editor).setBorder(new LineBorder(Color.red));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboBoxUI#createEditor()
+ */
+ @Override
+ protected ComboBoxEditor createEditor() {
+ return new SubstanceComboBoxEditor.UIResource();
+ }
+
+ private void updateComboBoxBorder() {
+ Border b = this.comboBox.getBorder();
+ if (b == null || b instanceof UIResource) {
+ int comboFontSize = SubstanceSizeUtils
+ .getComponentFontSize(this.comboBox);
+ Insets comboBorderInsets = SubstanceSizeUtils
+ .getComboBorderInsets(comboFontSize);
+ if (this.comboBox.isEditable()) {
+ this.comboBox.setBorder(new SubstanceTextComponentBorder(
+ comboBorderInsets));
+ } else {
+ this.comboBox
+ .setBorder(new BorderUIResource.EmptyBorderUIResource(
+ comboBorderInsets));
+ // BasicComboBoxUI does not invalidate display size when
+ // combo becomes uneditable. However, this is not good
+ // in Substance which has different preferred size for
+ // editable and uneditable combos. Calling the method below
+ // will trigger the path in BasicComboBoxUI.Handler that
+ // will invalidate the cached sizes.
+ this.comboBox.setPrototypeDisplayValue(this.comboBox
+ .getPrototypeDisplayValue());
+ }
+ this.layoutInsets = SubstanceSizeUtils
+ .getComboLayoutInsets(comboFontSize);
+ } else {
+ this.layoutInsets = new Insets(0, 0, 0, 0);
+ }
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ return false;
+ }
+ SubstanceButtonShaper shaper = ClassicButtonShaper.INSTANCE;
+ if (shaper == null)
+ return false;
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(this.comboBox,
+ SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(this.comboBox)), null);
+ return contour.contains(me.getPoint());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceDesktopIconUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceDesktopIconUI.java
new file mode 100644
index 0000000..e3de572
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceDesktopIconUI.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Font;
+
+import javax.swing.JComponent;
+import javax.swing.UIManager;
+import javax.swing.JInternalFrame.JDesktopIcon;
+import javax.swing.event.MouseInputListener;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicDesktopIconUI;
+
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceInternalFrameTitlePane;
+
+/**
+ * UI for desktop icons in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceDesktopIconUI extends BasicDesktopIconUI {
+ /**
+ * Listener on the title label (for the dragging purposes).
+ */
+ private MouseInputListener substanceLabelMouseInputListener;
+
+ /**
+ * Width of minimized component (desktop icon).
+ */
+ private int width;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceDesktopIconUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicDesktopIconUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ Font f = this.desktopIcon.getFont();
+ if ((f == null) || (f instanceof UIResource)) {
+ this.desktopIcon.setFont(UIManager.getFont("DesktopIcon.font"));
+ }
+ this.width = UIManager.getInt("DesktopIcon.width");
+ this.desktopIcon.setBackground(SubstanceCoreUtilities.getSkin(
+ this.desktopIcon.getInternalFrame()).getBackgroundColorScheme(
+ DecorationAreaType.SECONDARY_TITLE_PANE)
+ // SubstanceColorSchemeUtilities
+ // //.getColorScheme(this.desktopIcon.getInternalFrame(),
+ // ComponentState.ACTIVE).
+ .getBackgroundFillColor());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicDesktopIconUI#installComponents()
+ */
+ @Override
+ protected void installComponents() {
+ this.frame = this.desktopIcon.getInternalFrame();
+ // this.frame.setOpaque(false);
+ // Icon icon = this.frame.getFrameIcon();
+
+ this.iconPane = new SubstanceInternalFrameTitlePane(this.frame);
+ this.iconPane.setOpaque(false);
+ this.desktopIcon.setLayout(new BorderLayout());
+ this.desktopIcon.add(this.iconPane, BorderLayout.CENTER);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicDesktopIconUI#uninstallComponents()
+ */
+ @Override
+ protected void uninstallComponents() {
+ ((SubstanceInternalFrameTitlePane) this.iconPane).uninstall();
+
+ this.desktopIcon.setLayout(null);
+ this.desktopIcon.remove(this.iconPane);
+
+ this.frame = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicDesktopIconUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ this.substanceLabelMouseInputListener = this.createMouseInputListener();
+ this.iconPane
+ .addMouseMotionListener(this.substanceLabelMouseInputListener);
+ this.iconPane.addMouseListener(this.substanceLabelMouseInputListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicDesktopIconUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ ((SubstanceInternalFrameTitlePane) this.iconPane).uninstallListeners();
+
+ this.iconPane
+ .removeMouseMotionListener(this.substanceLabelMouseInputListener);
+ this.iconPane
+ .removeMouseListener(this.substanceLabelMouseInputListener);
+ this.substanceLabelMouseInputListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.ComponentUI#getPreferredSize(javax.swing.JComponent)
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ // Desktop icons can not be resized. Their dimensions should
+ // always be the minimum size. See getMinimumSize(JComponent c).
+ return this.getMinimumSize(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#getMinimumSize(javax.swing.JComponent)
+ */
+ @Override
+ public Dimension getMinimumSize(JComponent c) {
+ // For the desktop icon we will use the layout maanger to
+ // determine the correct height of the component, but we want to keep
+ // the width consistent according to the jlf spec.
+ return new Dimension(this.width, this.desktopIcon.getLayout()
+ .minimumLayoutSize(this.desktopIcon).height);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#getMaximumSize(javax.swing.JComponent)
+ */
+ @Override
+ public Dimension getMaximumSize(JComponent c) {
+ // Desktop icons can not be resized. Their dimensions should
+ // always be the minimum size. See getMinimumSize(JComponent c).
+ return this.getMinimumSize(c);
+ }
+
+ // /*
+ // * (non-Javadoc)
+ // *
+ // * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
+ // * javax.swing.JComponent)
+ // */
+ // @Override
+ // public void paint(Graphics g, JComponent c) {
+ // JInternalFrame.JDesktopIcon di = (JInternalFrame.JDesktopIcon) c;
+ // di.setOpaque(false);
+ //
+ // int width = di.getWidth();
+ // int height = di.getHeight();
+ //
+ // Graphics2D graphics = (Graphics2D) g.create();
+ // // the background is translucent
+ // // graphics.setComposite(AlphaComposite.getInstance(
+ // // AlphaComposite.SRC_ATOP, 0.6f));
+ // //
+ // // SubstanceImageCreator.paintRectangularBackground(graphics, 0, 0,
+ // // width,
+ // // height, SubstanceCoreUtilities.getActiveScheme(this.desktopIcon
+ // // .getInternalFrame()), false, false);
+ //
+ // di.paintComponents(graphics);
+ //
+ // graphics.dispose();
+ // }
+ //
+ // /*
+ // * (non-Javadoc)
+ // *
+ // * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ // * javax.swing.JComponent)
+ // */
+ // @Override
+ // public void update(Graphics g, JComponent c) {
+ // this.paint(g, c);
+ // }
+ //
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicDesktopIconUI#installUI(javax.swing.JComponent
+ * )
+ */
+ @Override
+ public void installUI(JComponent c) {
+ super.installUI(c);
+ c.setOpaque(false);
+ }
+
+ @Override
+ public void uninstallUI(JComponent c) {
+ // desktopIcon.remove(this.titleLabel);
+ // super.uninstallUI(c);
+
+ SubstanceInternalFrameTitlePane thePane = (SubstanceInternalFrameTitlePane) this.iconPane;
+ super.uninstallUI(c);
+ thePane.uninstallListeners();
+ }
+
+ /**
+ * Returns the component for desktop icon hover (internal frame preview)
+ * functionality.
+ *
+ * @return The component for desktop icon hover (internal frame preview)
+ * functionality.
+ */
+ public JComponent getComponentForHover() {
+ return this.iconPane;
+ }
+
+ /**
+ * Installs the UI delegate on the desktop icon if necessary.
+ *
+ * @param jdi
+ * Desktop icon.
+ */
+ public void installIfNecessary(JDesktopIcon jdi) {
+ // fix for issue 344 - reopening an internal frame
+ // that has been closed.
+ if (this.desktopIcon == null) {
+ this.installUI(jdi);
+ }
+ }
+
+ /**
+ * Uninstalls the UI delegate from the desktop icon if necessary.
+ *
+ * @param jdi
+ * Desktop icon.
+ */
+ public void uninstallIfNecessary(JDesktopIcon jdi) {
+ // fix for issue 345 - an internal frame used in inner option pane
+ // gets closed twice
+ if (this.desktopIcon == jdi) {
+ this.uninstallUI(jdi);
+ }
+ }
+
+ void setWindowModified(boolean isWindowModified) {
+ ((SubstanceInternalFrameTitlePane) this.iconPane).getCloseButton()
+ .putClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED,
+ isWindowModified);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceDesktopPaneUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceDesktopPaneUI.java
new file mode 100644
index 0000000..77863d7
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceDesktopPaneUI.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+
+import javax.swing.JComponent;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicDesktopPaneUI;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.border.SubstanceBorder;
+
+/**
+ * UI for desktop panes in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceDesktopPaneUI extends BasicDesktopPaneUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceDesktopPaneUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicDesktopPaneUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ Border curr = this.desktop.getBorder();
+ if ((curr == null) || (curr instanceof UIResource)) {
+ this.desktop.setBorder(new SubstanceBorder());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ if (!c.isShowing()) {
+ return;
+ }
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c, g));
+ if (SubstanceCoreUtilities.isOpaque(c)) {
+ // hack for JLayeredPane.paint() and JDesktopPane.isOpaque()
+ Color back = c.getBackground();
+ if (back instanceof UIResource) {
+ graphics.setColor(UIManager.getColor("Panel.background"));
+ graphics.fillRect(0, 0, c.getWidth(), c.getHeight());
+ }
+ BackgroundPaintingUtils.updateIfOpaque(graphics, c);
+ super.paint(graphics, c);
+ } else {
+ super.paint(graphics, c);
+ }
+ // graphics.setColor(UIManager.getColor("Desktop.foreground"));
+ // graphics.drawRect(0, 0, c.getWidth() - 1, c.getHeight() - 1);
+ graphics.dispose();
+
+ // GhostPaintingUtils.paintGhostImages(c, g);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceEditorPaneUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceEditorPaneUI.java
new file mode 100644
index 0000000..728a63c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceEditorPaneUI.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicEditorPaneUI;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * UI for editor panes in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceEditorPaneUI extends BasicEditorPaneUI implements
+ TransitionAwareUI {
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * The associated editor pane.
+ */
+ protected JEditorPane editorPane;
+
+ /**
+ * Property change listener.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Listener for transition animations.
+ */
+ private RolloverTextControlListener substanceRolloverListener;
+
+ /**
+ * Surrogate button model for tracking the state transitions.
+ */
+ private ButtonModel transitionModel;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceEditorPaneUI(comp);
+ }
+
+ /**
+ * Simple constructor.
+ *
+ * @param c
+ * Component (editor pane).
+ */
+ public SubstanceEditorPaneUI(JComponent c) {
+ super();
+ this.editorPane = (JEditorPane) c;
+
+ this.transitionModel = new DefaultButtonModel();
+ this.transitionModel.setArmed(false);
+ this.transitionModel.setSelected(false);
+ this.transitionModel.setPressed(false);
+ this.transitionModel.setRollover(false);
+ this.transitionModel.setEnabled(this.editorPane.isEnabled());
+
+ this.stateTransitionTracker = new StateTransitionTracker(
+ this.editorPane, this.transitionModel);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ super.installListeners();
+
+ this.substanceRolloverListener = new RolloverTextControlListener(
+ this.editorPane, this, this.transitionModel);
+ this.substanceRolloverListener.registerListeners();
+
+ this.stateTransitionTracker.registerModelListeners();
+ this.stateTransitionTracker.registerFocusListeners();
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // remember the caret location - issue 404
+ int caretPos = editorPane.getCaretPosition();
+ editorPane.updateUI();
+ editorPane.setCaretPosition(caretPos);
+ Container parent = editorPane.getParent();
+ if (parent != null) {
+ parent.invalidate();
+ parent.validate();
+ }
+ }
+ });
+ }
+
+ if ("enabled".equals(evt.getPropertyName())) {
+ transitionModel.setEnabled(editorPane.isEnabled());
+ }
+ }
+ };
+ this.editorPane
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.stateTransitionTracker.unregisterModelListeners();
+ this.stateTransitionTracker.unregisterFocusListeners();
+
+ this.editorPane
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+
+ // support for per-window skins
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (editorPane == null)
+ return;
+ Color foregr = editorPane.getForeground();
+ if ((foregr == null) || (foregr instanceof UIResource)) {
+ editorPane
+ .setForeground(SubstanceColorUtilities
+ .getForegroundColor(SubstanceLookAndFeel
+ .getCurrentSkin(editorPane)
+ .getEnabledColorScheme(
+ SubstanceLookAndFeel
+ .getDecorationType(editorPane))));
+ }
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTextUI#paintBackground(java.awt.Graphics)
+ */
+ @Override
+ protected void paintBackground(Graphics g) {
+ SubstanceTextUtilities.paintTextCompBackground(g, this.editorPane);
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return true;
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceFileChooserUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceFileChooserUI.java
new file mode 100644
index 0000000..a874d21
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceFileChooserUI.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileView;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicFileChooserUI;
+import javax.swing.plaf.metal.MetalFileChooserUI;
+
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI for file chooser in <b>Substance</b> look and feel. The
+ * {@link BasicFileChooserUI} can't be used on its own (creates an empty
+ * dialog).
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceFileChooserUI extends MetalFileChooserUI {
+ /**
+ * Custom file view - for system icons on the files.
+ */
+ private final SubstanceFileView fileView = new SubstanceFileView();
+
+ /**
+ * Custom file view implementation that returns system-specific file icons.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class SubstanceFileView extends BasicFileView {
+ /**
+ * Cache for the file icons.
+ */
+ private final Map<String, Icon> pathIconCache = new HashMap<String, Icon>();
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicFileChooserUI$BasicFileView#getCachedIcon
+ * (java.io.File)
+ */
+ @Override
+ public Icon getCachedIcon(File f) {
+ return pathIconCache.get(f.getPath());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicFileChooserUI$BasicFileView#getIcon(java
+ * .io.File)
+ */
+ @Override
+ public Icon getIcon(File f) {
+ Icon icon = getCachedIcon(f);
+ if (icon != null)
+ return icon;
+
+ icon = getDefaultIcon(f);
+ // System.out.println("System : " + f.getAbsolutePath() + " --> "
+ // + icon);
+ if (icon == null) {
+ icon = super.getIcon(f);
+ if (icon == null) {
+ icon = new ImageIcon(SubstanceCoreUtilities.getBlankImage(
+ 8, 8));
+ }
+ // System.out.println("Super : " + f.getAbsolutePath() + " --> "
+ // + icon);
+ }
+ cacheIcon(f, icon);
+ return icon;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicFileChooserUI$BasicFileView#cacheIcon
+ * (java.io.File, javax.swing.Icon)
+ */
+ @Override
+ public void cacheIcon(File f, Icon icon) {
+ pathIconCache.put(f.getPath(), icon);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicFileChooserUI$BasicFileView#clearIconCache
+ * ()
+ */
+ @Override
+ public void clearIconCache() {
+ pathIconCache.clear();
+ }
+
+ /**
+ * Returns the default file icon.
+ *
+ * @param f
+ * File.
+ * @return File icon.
+ */
+ public Icon getDefaultIcon(File f) {
+ JFileChooser fileChooser = getFileChooser();
+ Icon icon = fileChooser.getFileSystemView().getSystemIcon(f);
+
+ if (SubstanceCoreUtilities.useThemedDefaultIcon(null)) {
+ icon = SubstanceCoreUtilities.getThemedIcon(fileChooser, icon);
+ }
+ return icon;
+ }
+ }
+
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceFileChooserUI((JFileChooser) comp);
+ }
+
+ /**
+ * Creates the UI delegate for the specified file chooser.
+ *
+ * @param filechooser
+ * File chooser.
+ */
+ public SubstanceFileChooserUI(JFileChooser filechooser) {
+ super(filechooser);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicFileChooserUI#getFileView(javax.swing.
+ * JFileChooser)
+ */
+ @Override
+ public FileView getFileView(JFileChooser fc) {
+ return fileView;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceFormattedTextFieldUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceFormattedTextFieldUI.java
new file mode 100644
index 0000000..31b1ff8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceFormattedTextFieldUI.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicBorders;
+import javax.swing.plaf.basic.BasicFormattedTextFieldUI;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.border.SubstanceTextComponentBorder;
+import org.pushingpixels.trident.swing.SwingRepaintCallback;
+
+/**
+ * UI for formatted text fields in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceFormattedTextFieldUI extends BasicFormattedTextFieldUI
+ implements TransitionAwareUI {
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * The associated formatted text field.
+ */
+ protected JFormattedTextField textField;
+
+ /**
+ * Property change listener.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Listener for transition animations.
+ */
+ private RolloverTextControlListener substanceRolloverListener;
+
+ /**
+ * Surrogate button model for tracking the state transitions.
+ */
+ private ButtonModel transitionModel;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceFormattedTextFieldUI(comp);
+ }
+
+ /**
+ * Simple constructor.
+ *
+ * @param c
+ * Component (formatted text field).
+ */
+ public SubstanceFormattedTextFieldUI(JComponent c) {
+ super();
+ this.textField = (JFormattedTextField) c;
+
+ this.transitionModel = new DefaultButtonModel();
+ this.transitionModel.setArmed(false);
+ this.transitionModel.setSelected(false);
+ this.transitionModel.setPressed(false);
+ this.transitionModel.setRollover(false);
+ this.transitionModel.setEnabled(this.textField.isEnabled());
+
+ this.stateTransitionTracker = new StateTransitionTracker(
+ this.textField, this.transitionModel);
+ this.stateTransitionTracker
+ .setRepaintCallback(new StateTransitionTracker.RepaintCallback() {
+ @Override
+ public SwingRepaintCallback getRepaintCallback() {
+ return SubstanceCoreUtilities
+ .getTextComponentRepaintCallback(textField);
+ }
+ });
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substanceRolloverListener = new RolloverTextControlListener(
+ this.textField, this, this.transitionModel);
+ this.substanceRolloverListener.registerListeners();
+
+ this.stateTransitionTracker.registerModelListeners();
+ this.stateTransitionTracker.registerFocusListeners();
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // remember the caret location - issue 404
+ int caretPos = textField.getCaretPosition();
+ textField.updateUI();
+ textField.setCaretPosition(caretPos);
+ Container parent = textField.getParent();
+ if (parent != null) {
+ parent.invalidate();
+ parent.validate();
+ }
+ }
+ });
+ }
+
+ if ("enabled".equals(evt.getPropertyName())) {
+ transitionModel.setEnabled(textField.isEnabled());
+ }
+ }
+ };
+ this.textField
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.stateTransitionTracker.unregisterModelListeners();
+ this.stateTransitionTracker.unregisterFocusListeners();
+
+ this.textField
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ this.substanceRolloverListener.unregisterListeners();
+ this.substanceRolloverListener = null;
+
+ // this.textField.removeFocusListener(this.substanceFocusListener);
+ // this.substanceFocusListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ Border b = this.textField.getBorder();
+ if (b == null || b instanceof UIResource) {
+ Border newB = new BorderUIResource.CompoundBorderUIResource(
+ new SubstanceTextComponentBorder(SubstanceSizeUtils
+ .getTextBorderInsets(SubstanceSizeUtils
+ .getComponentFontSize(this.textField))),
+ new BasicBorders.MarginBorder());
+ this.textField.setBorder(newB);
+ }
+
+ // support for per-window skins
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (textField == null)
+ return;
+ Color foregr = textField.getForeground();
+ if ((foregr == null) || (foregr instanceof UIResource)) {
+ textField
+ .setForeground(SubstanceColorUtilities
+ .getForegroundColor(SubstanceLookAndFeel
+ .getCurrentSkin(textField)
+ .getEnabledColorScheme(
+ SubstanceLookAndFeel
+ .getDecorationType(textField))));
+ }
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTextUI#paintBackground(java.awt.Graphics)
+ */
+ @Override
+ protected void paintBackground(Graphics g) {
+ SubstanceTextUtilities.paintTextCompBackground(g, this.textField);
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ return false;
+ }
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(
+ this.textField, 2.0f * SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(this.textField)), null);
+ return contour.contains(me.getPoint());
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceInternalFrameUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceInternalFrameUI.java
new file mode 100755
index 0000000..ebd3330
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceInternalFrameUI.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.Color;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JComponent;
+import javax.swing.JInternalFrame;
+import javax.swing.JInternalFrame.JDesktopIcon;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicInternalFrameUI;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceInternalFrameTitlePane;
+
+/**
+ * UI for internal frames in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceInternalFrameUI extends BasicInternalFrameUI {
+ /**
+ * Title pane
+ */
+ private SubstanceInternalFrameTitlePane titlePane;
+
+ /**
+ * Property listener on the associated internal frame.
+ */
+ protected PropertyChangeListener substancePropertyListener;
+
+ /**
+ * Simple constructor.
+ *
+ * @param b
+ * Associated internal frame.
+ */
+ public SubstanceInternalFrameUI(JInternalFrame b) {
+ super(b);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceInternalFrameUI((JInternalFrame) comp);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicInternalFrameUI#createNorthPane(javax.swing
+ * .JInternalFrame)
+ */
+ @Override
+ protected JComponent createNorthPane(JInternalFrame w) {
+ this.titlePane = new SubstanceInternalFrameTitlePane(w);
+
+ // f.putClientProperty(INTERNAL_FRAME_PINNED, Boolean.TRUE);
+
+ return this.titlePane;
+ }
+
+ @Override
+ protected void installComponents() {
+ if (SubstanceCoreUtilities.isRoundedCorners(frame)) {
+ frame.setOpaque(false);
+ }
+ super.installComponents();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicInternalFrameUI#uninstallComponents()
+ */
+ @Override
+ protected void uninstallComponents() {
+ this.titlePane.uninstall();
+ super.uninstallComponents();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicInternalFrameUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ String propertyName = evt.getPropertyName();
+ if (JInternalFrame.IS_CLOSED_PROPERTY.equals(propertyName)) {
+ titlePane.uninstall();
+ JDesktopIcon jdi = frame.getDesktopIcon();
+ SubstanceDesktopIconUI ui = (SubstanceDesktopIconUI) jdi
+ .getUI();
+ ui.uninstallIfNecessary(jdi);
+ } else if ("background".equals(propertyName)) {
+ Color newBackgr = (Color) evt.getNewValue();
+ if (!(newBackgr instanceof UIResource)) {
+ getTitlePane().setBackground(newBackgr);
+ frame.getDesktopIcon().setBackground(newBackgr);
+ }
+ } else if ("ancestor".equals(propertyName)) {
+ // fix for issue 344 - reopening an internal frame
+ // that has been closed.
+ JDesktopIcon jdi = frame.getDesktopIcon();
+ SubstanceDesktopIconUI ui = (SubstanceDesktopIconUI) jdi
+ .getUI();
+ ui.installIfNecessary(jdi);
+ } else if (JInternalFrame.IS_SELECTED_PROPERTY.equals(propertyName)) {
+ titlePane.setActive((Boolean)evt.getNewValue());
+ } else if (SubstanceLookAndFeel.WINDOW_ROUNDED_CORNERS.equals(propertyName)
+ || JInternalFrame.IS_MAXIMUM_PROPERTY.equals(propertyName))
+ {
+ if (SubstanceCoreUtilities.isRoundedCorners(frame)) {
+ frame.setOpaque(false);
+ }
+ }
+ }
+ };
+ this.frame.addPropertyChangeListener(this.substancePropertyListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicInternalFrameUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.frame.removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+ super.uninstallListeners();
+ }
+
+ // private class BorderListener1 extends BorderListener implements
+ // SwingConstants {
+ //
+ // Rectangle getIconBounds() {
+ // int xOffset = 5;
+ // Rectangle rect = null;
+ //
+ // Icon icon = SubstanceInternalFrameUI.this.frame.getFrameIcon();
+ // if (icon != null) {
+ // int iconY = ((SubstanceInternalFrameUI.this.titlePane.getHeight() / 2) -
+ // (icon
+ // .getIconHeight() / 2));
+ // rect = new Rectangle(xOffset, iconY, icon.getIconWidth(), icon
+ // .getIconHeight());
+ // }
+ // return rect;
+ // }
+ //
+ // @Override
+ // public void mouseClicked(MouseEvent e) {
+ // if ((e.getClickCount() == 2) && (e.getSource() ==
+ // SubstanceInternalFrameUI.this.getNorthPane())
+ // && SubstanceInternalFrameUI.this.frame.isClosable() &&
+ // !SubstanceInternalFrameUI.this.frame.isIcon()) {
+ // Rectangle rect = this.getIconBounds();
+ // if ((rect != null) && rect.contains(e.getX(), e.getY())) {
+ // SubstanceInternalFrameUI.this.frame.doDefaultCloseAction();
+ // } else {
+ // super.mouseClicked(e);
+ // }
+ // } else {
+ // super.mouseClicked(e);
+ // }
+ // }
+ // } // / End BorderListener Class
+ //
+ // /**
+ // * Returns the <code>MouseInputAdapter<code> that will be installed
+ // * on the TitlePane.
+ // *
+ // * @param w the <code>JInternalFrame</code>
+ // * @return the <code>MouseInputAdapter</code> that will be installed
+ // * on the TitlePane.
+ // * @since 1.6
+ // */
+ // @Override
+ // protected MouseInputAdapter createBorderListener(JInternalFrame w) {
+ // return new BorderListener1();
+ // }
+ //
+ /**
+ * Returns the title pane of the associated internal frame. This method is
+ * <b>for internal use only</b>.
+ *
+ * @return Title pane of the associated internal frame.
+ */
+ public SubstanceInternalFrameTitlePane getTitlePane() {
+ return titlePane;
+ }
+
+ void setWindowModified(boolean isWindowModified) {
+ titlePane.getCloseButton().putClientProperty(
+ SubstanceLookAndFeel.WINDOW_MODIFIED,
+ isWindowModified);
+
+ SubstanceDesktopIconUI desktopIconUi = (SubstanceDesktopIconUI) this.frame
+ .getDesktopIcon().getUI();
+ desktopIconUi.setWindowModified(isWindowModified);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceLabelUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceLabelUI.java
new file mode 100644
index 0000000..84db9c4
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceLabelUI.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicHTML;
+import javax.swing.plaf.basic.BasicLabelUI;
+import javax.swing.text.View;
+
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceTextUtilities;
+
+/**
+ * UI for labels in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceLabelUI extends BasicLabelUI {
+ /**
+ * Property change listener.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+ private Rectangle paintIconR = new Rectangle();
+ private Rectangle paintTextR = new Rectangle();
+ private Rectangle paintViewR = new Rectangle();
+ private Insets paintViewInsets = new Insets(0, 0, 0, 0);
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceLabelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicLabelUI#installListeners(javax.swing.JLabel)
+ */
+ @Override
+ protected void installListeners(final JLabel c) {
+ super.installListeners(c);
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("opaque".equals(evt.getPropertyName())) {
+ if (!Boolean.TRUE.equals(c
+ .getClientProperty(SubstanceButtonUI.LOCK_OPACITY))) {
+ c.putClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL,
+ evt.getNewValue());
+ // System.out
+ // .println("PCL: "
+ // + b.getText()
+ // + "->"
+ // + b
+ // .getClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL)
+ // );
+ }
+ }
+
+ }
+ };
+ c.addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicLabelUI#uninstallListeners(javax.swing.JLabel
+ * )
+ */
+ @Override
+ protected void uninstallListeners(JLabel c) {
+ c.removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ super.uninstallListeners(c);
+ }
+
+ @Override
+ public void paint(Graphics g, JComponent c) {
+
+ JLabel label = (JLabel) c;
+ String text = label.getText();
+
+ Icon icon = null;
+ if (label.isEnabled()) {
+ icon = label.getIcon();
+ if ((icon != null)
+ && SubstanceCoreUtilities.useThemedDefaultIcon(label))
+ icon = SubstanceCoreUtilities.getThemedIcon(label, icon);
+ } else {
+ icon = label.getDisabledIcon();
+ }
+
+ if ((icon == null) && (text == null)) {
+ return;
+ }
+
+ Insets insets = label.getInsets(paintViewInsets);
+ paintViewR.x = insets.left;
+ paintViewR.y = insets.top;
+ paintViewR.width = c.getWidth() - (insets.left + insets.right);
+ paintViewR.height = c.getHeight() - (insets.top + insets.bottom);
+ paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
+ paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
+
+ String clippedText = SwingUtilities.layoutCompoundLabel(label, g
+ .getFontMetrics(), text, icon, label.getVerticalAlignment(),
+ label.getHorizontalAlignment(),
+ label.getVerticalTextPosition(), label
+ .getHorizontalTextPosition(), paintViewR, paintIconR,
+ paintTextR, label.getIconTextGap());
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ BackgroundPaintingUtils.updateIfOpaque(g2d, c);
+ if (icon != null) {
+ icon.paintIcon(c, g2d, paintIconR.x, paintIconR.y);
+ }
+ ComponentState labelState = label.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED;
+ float labelAlpha = SubstanceColorSchemeUtilities.getAlpha(label,
+ labelState);
+ if (text != null) {
+ final View v = (View) c.getClientProperty(BasicHTML.propertyKey);
+ if (v != null) {
+ v.paint(g2d, paintTextR);
+ } else {
+ // fix for issue 406 - use the same FG computation
+ // color as for other controls
+ SubstanceTextUtilities.paintText(g, label, paintTextR,
+ clippedText, label.getDisplayedMnemonicIndex(),
+ labelState, labelAlpha);
+ }
+ }
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ // failsafe for LAF change
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return;
+ this.paint(g, c);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceListUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceListUI.java
new file mode 100644
index 0000000..be5dd45
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceListUI.java
@@ -0,0 +1,921 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.ButtonModel;
+import javax.swing.DefaultButtonModel;
+import javax.swing.JComponent;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListModel;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListDataEvent;
+import javax.swing.event.ListDataListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicListUI;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.ComponentStateFacet;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.renderers.SubstanceDefaultListCellRenderer;
+import org.pushingpixels.substance.internal.animation.StateTransitionMultiTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.RepaintCallback;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceStripingUtils;
+import org.pushingpixels.substance.internal.utils.UpdateOptimizationAware;
+import org.pushingpixels.substance.internal.utils.UpdateOptimizationInfo;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.TimelineCallback;
+import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
+
+/**
+ * UI for lists in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceListUI extends BasicListUI implements
+ UpdateOptimizationAware {
+ /**
+ * Holds the list of currently selected indices.
+ */
+ protected Map<Integer, Object> selectedIndices;
+
+ /**
+ * Holds the currently rolled-over index, or -1 is there is none such.
+ */
+ protected int rolledOverIndex;
+
+ /**
+ * Property listener that listens to the
+ * {@link SubstanceLookAndFeel#WATERMARK_VISIBLE} property.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Listener for transition animations on list selections.
+ */
+ protected ListSelectionListener substanceListSelectionListener;
+
+ /**
+ * Listener for transition animations on list rollovers.
+ */
+ protected RolloverFadeListener substanceFadeRolloverListener;
+
+ private ComponentListener substanceComponentListener;
+
+ private StateTransitionMultiTracker<Integer> stateTransitionMultiTracker;
+
+ private ListDataListener substanceListDataListener;
+
+ private class SubstanceListSelectionListener implements
+ ListSelectionListener {
+ @Override
+ public void valueChanged(final ListSelectionEvent e) {
+ // fix for issue 469/474 - update the inner structures
+ // in a separate event
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ handleListSelectionChange(e);
+ if (list != null) {
+ list.repaint();
+ }
+ }
+ });
+ }
+
+ private void handleListSelectionChange(ListSelectionEvent e) {
+ if (list == null) {
+ // fix for issue 464 - misbehaving app listener can change
+ // look-and-feel without giving this listener a chance to
+ // react
+ return;
+ }
+ // optimization on large lists and large selections
+ if (LafWidgetUtilities.hasNoAnimations(list,
+ AnimationFacet.SELECTION))
+ return;
+
+ // no selection animations on non-Substance renderers
+ if (!(list.getCellRenderer() instanceof SubstanceDefaultListCellRenderer)) {
+ syncModelContents();
+ return;
+ }
+
+ Set<StateTransitionTracker> initiatedTrackers = new HashSet<StateTransitionTracker>();
+ boolean fadeCanceled = false;
+
+ for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
+ if (i >= list.getModel().getSize())
+ continue;
+ if (list.isSelectedIndex(i)) {
+ // check if was selected before
+ if (!selectedIndices.containsKey(i)) {
+ // start fading in
+ // System.out.println("Fade in on index " + i);
+
+ selectedIndices.put(i, list.getModel().getElementAt(i));
+
+ if (!fadeCanceled) {
+ StateTransitionTracker tracker = getTracker(i,
+ (i == rolledOverIndex), false);
+ tracker.getModel().setSelected(true);
+
+ initiatedTrackers.add(tracker);
+ if (initiatedTrackers.size() > 25) {
+ stateTransitionMultiTracker.clear();
+ initiatedTrackers.clear();
+ fadeCanceled = true;
+ }
+ }
+ }
+ } else {
+ // check if was selected before and still points to the
+ // same element
+ if (selectedIndices.containsKey(i)) {
+ if (selectedIndices.get(i) == list.getModel()
+ .getElementAt(i)) {
+ // start fading out
+ // System.out
+ // .println("Fade out on index " + i);
+
+ if (!fadeCanceled) {
+ StateTransitionTracker tracker = getTracker(i,
+ (i == rolledOverIndex), true);
+ tracker.getModel().setSelected(false);
+
+ initiatedTrackers.add(tracker);
+ if (initiatedTrackers.size() > 25) {
+ stateTransitionMultiTracker.clear();
+ initiatedTrackers.clear();
+ fadeCanceled = true;
+ }
+ }
+ }
+ selectedIndices.remove(i);
+ }
+ }
+ }
+ }
+ }
+
+ private final class SubstanceListDataListener implements ListDataListener {
+ private void _syncModelContents() {
+ // fix for issue 469/474 - update the inner structures
+ // in a separate event
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ syncModelContents();
+ }
+ });
+ }
+
+ @Override
+ public void intervalRemoved(ListDataEvent e) {
+ _syncModelContents();
+ }
+
+ @Override
+ public void intervalAdded(ListDataEvent e) {
+ _syncModelContents();
+ }
+
+ @Override
+ public void contentsChanged(ListDataEvent e) {
+ _syncModelContents();
+ }
+ }
+
+ /**
+ * Listener for fade animations on list rollovers.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class RolloverFadeListener implements MouseListener,
+ MouseMotionListener {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ // if (SubstanceCoreUtilities.toBleedWatermark(list))
+ // return;
+
+ fadeOutRolloverIndication();
+ // System.out.println("Nulling RO index");
+ resetRolloverIndex();
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ if (!list.isEnabled())
+ return;
+ handleMove(e);
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ if (!list.isEnabled())
+ return;
+ handleMove(e);
+ }
+
+ /**
+ * Handles various mouse move events and initiates the fade animation if
+ * necessary.
+ *
+ * @param e
+ * Mouse event.
+ */
+ private void handleMove(MouseEvent e) {
+ // no rollover effects on non-Substance renderers
+ if (!(list.getCellRenderer() instanceof SubstanceDefaultListCellRenderer)) {
+ fadeOutRolloverIndication();
+ resetRolloverIndex();
+ return;
+ }
+
+ int roIndex = list.locationToIndex(e.getPoint());
+ if ((roIndex >= 0) && (roIndex < list.getModel().getSize())) {
+ // test actual hit
+ if (!list.getCellBounds(roIndex, roIndex)
+ .contains(e.getPoint())) {
+ roIndex = -1;
+ }
+ }
+ if ((roIndex < 0) || (roIndex >= list.getModel().getSize())) {
+ fadeOutRolloverIndication();
+ // System.out.println("Nulling RO index");
+ resetRolloverIndex();
+ } else {
+ // check if this is the same index
+ if ((rolledOverIndex >= 0) && (rolledOverIndex == roIndex))
+ return;
+
+ fadeOutRolloverIndication();
+
+ // rollover on a new row
+ StateTransitionTracker tracker = getTracker(roIndex, false,
+ list.isSelectedIndex(roIndex));
+ tracker.getModel().setRollover(true);
+ rolledOverIndex = roIndex;
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceListUI();
+ }
+
+ /**
+ * Creates a UI delegate for list.
+ */
+ public SubstanceListUI() {
+ super();
+ rolledOverIndex = -1;
+ selectedIndices = new HashMap<Integer, Object>();
+
+ this.stateTransitionMultiTracker = new StateTransitionMultiTracker<Integer>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicListUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ if (SubstanceCoreUtilities.toDrawWatermark(list)) {
+ list.setOpaque(false);
+ }
+
+ syncModelContents();
+ }
+
+ @Override
+ protected void uninstallDefaults() {
+ selectedIndices.clear();
+
+ super.uninstallDefaults();
+ }
+
+ @Override
+ public void uninstallUI(JComponent c) {
+ this.stateTransitionMultiTracker.clear();
+
+ super.uninstallUI(c);
+ }
+
+ /**
+ * Repaints a single cell during the fade animation cycle.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class CellRepaintCallback extends UIThreadTimelineCallbackAdapter {
+ /**
+ * Associated list.
+ */
+ protected JList list;
+
+ /**
+ * Associated (animated) cell index.
+ */
+ protected int cellIndex;
+
+ /**
+ * Creates a new animation repaint callback.
+ *
+ * @param list
+ * Associated list.
+ * @param cellIndex
+ * Associated (animated) cell index.
+ */
+ public CellRepaintCallback(JList list, int cellIndex) {
+ this.list = list;
+ this.cellIndex = cellIndex;
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ repaintCell();
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ // System.out.println(cellIndex + " at " + timelinePosition);
+ repaintCell();
+ }
+
+ /**
+ * Repaints the associated cell.
+ */
+ private void repaintCell() {
+ // SwingUtilities.invokeLater(new Runnable() {
+ // public void run() {
+ if (SubstanceListUI.this.list == null) {
+ // may happen if the LAF was switched in the meantime
+ return;
+ }
+ try {
+ maybeUpdateLayoutState();
+ int cellCount = list.getModel().getSize();
+ if ((cellCount > 0) && (cellIndex < cellCount)) {
+ // need to retrieve the cell rectangle since the
+ // cells can be moved while animating
+ Rectangle rect = SubstanceListUI.this.getCellBounds(list,
+ cellIndex, cellIndex);
+ // System.out.println("Repainting " + cellIndex
+ // + " at " + rect);
+ list.repaint(rect);
+ }
+ } catch (RuntimeException re) {
+ return;
+ }
+ }
+ // });
+ // }
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ // Add listener for the selection animation
+ substanceListSelectionListener = new SubstanceListSelectionListener();
+ list.getSelectionModel().addListSelectionListener(
+ substanceListSelectionListener);
+
+ substanceFadeRolloverListener = new RolloverFadeListener();
+ list.addMouseMotionListener(substanceFadeRolloverListener);
+ list.addMouseListener(substanceFadeRolloverListener);
+
+ substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(final PropertyChangeEvent evt) {
+ if (SubstanceLookAndFeel.WATERMARK_VISIBLE.equals(evt
+ .getPropertyName())) {
+ list.setOpaque(!SubstanceCoreUtilities
+ .toDrawWatermark(list));
+ }
+ if ("model".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ ListModel oldModel = (ListModel) evt.getOldValue();
+ if (oldModel != null) {
+ oldModel.removeListDataListener(substanceListDataListener);
+ }
+ ListModel newModel = (ListModel) evt.getNewValue();
+ substanceListDataListener = new SubstanceListDataListener();
+ newModel.addListDataListener(substanceListDataListener);
+ syncModelContents();
+ }
+ });
+ }
+ if ("selectionModel".equals(evt.getPropertyName())) {
+ // fix for issue 475 - wire the listener on the new
+ // selection model
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ ListSelectionModel oldModel = (ListSelectionModel) evt
+ .getOldValue();
+ if (oldModel != null) {
+ oldModel.removeListSelectionListener(substanceListSelectionListener);
+ }
+ ListSelectionModel newModel = (ListSelectionModel) evt
+ .getNewValue();
+ substanceListSelectionListener = new SubstanceListSelectionListener();
+ newModel.addListSelectionListener(substanceListSelectionListener);
+ syncModelContents();
+ }
+ });
+ }
+ }
+ };
+ list.addPropertyChangeListener(substancePropertyChangeListener);
+
+ this.substanceComponentListener = new ComponentAdapter() {
+ @Override
+ public void componentMoved(ComponentEvent e) {
+ // clear the rollover indexes since these are no longer
+ // in sync with the mouse location
+ fadeOutRolloverIndication();
+ resetRolloverIndex();
+ }
+ };
+ this.list.addComponentListener(this.substanceComponentListener);
+
+ this.substanceListDataListener = new SubstanceListDataListener();
+ this.list.getModel()
+ .addListDataListener(this.substanceListDataListener);
+ }
+
+ @Override
+ protected void uninstallListeners() {
+ this.list.getModel().removeListDataListener(
+ this.substanceListDataListener);
+ this.substanceListDataListener = null;
+
+ list.getSelectionModel().removeListSelectionListener(
+ substanceListSelectionListener);
+ substanceListSelectionListener = null;
+
+ list.removeMouseMotionListener(substanceFadeRolloverListener);
+ list.removeMouseListener(substanceFadeRolloverListener);
+ substanceFadeRolloverListener = null;
+
+ list.removePropertyChangeListener(substancePropertyChangeListener);
+ substancePropertyChangeListener = null;
+
+ this.list.removeComponentListener(this.substanceComponentListener);
+ this.substanceComponentListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicListUI#paintCell(java.awt.Graphics, int,
+ * java.awt.Rectangle, javax.swing.ListCellRenderer, javax.swing.ListModel,
+ * javax.swing.ListSelectionModel, int)
+ */
+ @Override
+ protected void paintCell(Graphics g, int row, Rectangle rowBounds,
+ ListCellRenderer cellRenderer, ListModel dataModel,
+ ListSelectionModel selModel, int leadIndex) {
+ Object value = dataModel.getElementAt(row);
+ boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
+ boolean isSelected = selModel.isSelectedIndex(row);
+
+ Component rendererComponent = cellRenderer
+ .getListCellRendererComponent(list, value, row, isSelected,
+ cellHasFocus);
+
+ if (!(rendererComponent instanceof SubstanceDefaultListCellRenderer)) {
+ // if it's not Substance renderer - ask the Basic delegate to paint
+ // it.
+ super.paintCell(g, row, rowBounds, cellRenderer, dataModel,
+ selModel, leadIndex);
+ return;
+ }
+
+ boolean isWatermarkBleed = updateInfo.toDrawWatermark;
+
+ int cx = rowBounds.x;
+ int cy = rowBounds.y;
+ int cw = rowBounds.width;
+ int ch = rowBounds.height;
+
+ // if (isFileList) {
+ // // Shrink renderer to preferred size. This is mostly used on Windows
+ // // where selection is only shown around the file name, instead of
+ // // across the whole list cell.
+ // int w = Math
+ // .min(cw, rendererComponent.getPreferredSize().width + 4);
+ // if (!isLeftToRight) {
+ // cx += (cw - w);
+ // }
+ // cw = w;
+ // }
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(list, g));
+ if (!isWatermarkBleed) {
+ Color background = rendererComponent.getBackground();
+ // optimization - only render background if it's different
+ // from the list background
+ if ((background != null)
+ && (!list.getBackground().equals(background) || this.updateInfo.isInDecorationArea)) {
+ g2d.setColor(background);
+ g2d.fillRect(cx, cy, cw, ch);
+ }
+ } else {
+ BackgroundPaintingUtils.fillAndWatermark(g2d, this.list,
+ rendererComponent.getBackground(), new Rectangle(cx, cy,
+ cw, ch));
+ }
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = getModelStateInfo(
+ row, rendererComponent);
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ((modelStateInfo == null) ? null
+ : modelStateInfo.getStateContributionMap());
+ ComponentState currState = ((modelStateInfo == null) ? getCellState(
+ row, rendererComponent) : modelStateInfo.getCurrModelState());
+
+ // if the renderer is disabled, do not show any highlights
+ boolean hasHighlights = false;
+ if (rendererComponent.isEnabled()) {
+ if (activeStates != null) {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
+ .entrySet()) {
+ hasHighlights = (this.updateInfo
+ .getHighlightAlpha(stateEntry.getKey())
+ * stateEntry.getValue().getContribution() > 0.0f);
+ if (hasHighlights)
+ break;
+ }
+ } else {
+ hasHighlights = (this.updateInfo.getHighlightAlpha(currState) > 0.0f);
+ }
+ }
+
+ JList.DropLocation dropLocation = list.getDropLocation();
+ if (dropLocation != null && !dropLocation.isInsert()
+ && dropLocation.getIndex() == row) {
+ // mark drop location
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(list,
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ currState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(list, ColorSchemeAssociationKind.BORDER,
+ currState);
+ Rectangle cellRect = new Rectangle(cx, cy, cw, ch);
+ HighlightPainterUtils.paintHighlight(g2d, this.rendererPane,
+ rendererComponent, cellRect, 0.8f, null, fillScheme,
+ borderScheme);
+ } else {
+ if (hasHighlights) {
+ Rectangle cellRect = new Rectangle(cx, cy, cw, ch);
+ if (activeStates == null) {
+ float alpha = this.updateInfo.getHighlightAlpha(currState);
+ if (alpha > 0.0f) {
+ SubstanceColorScheme fillScheme = this.updateInfo
+ .getHighlightColorScheme(currState);
+ SubstanceColorScheme borderScheme = this.updateInfo
+ .getHighlightBorderColorScheme(currState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ list, alpha, g));
+ HighlightPainterUtils.paintHighlight(g2d,
+ this.rendererPane, rendererComponent, cellRect,
+ 0.8f, null, fillScheme, borderScheme);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ list, g));
+ }
+ } else {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = stateEntry.getKey();
+ float alpha = this.updateInfo
+ .getHighlightAlpha(activeState)
+ * stateEntry.getValue().getContribution();
+ if (alpha == 0.0f)
+ continue;
+ SubstanceColorScheme fillScheme = this.updateInfo
+ .getHighlightColorScheme(activeState);
+ SubstanceColorScheme borderScheme = this.updateInfo
+ .getHighlightBorderColorScheme(activeState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ list, alpha, g));
+ HighlightPainterUtils.paintHighlight(g2d,
+ this.rendererPane, rendererComponent, cellRect,
+ 0.8f, null, fillScheme, borderScheme);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ list, g));
+ }
+ }
+ }
+ }
+
+ // System.out.println(row + ":" + rendererComponent.getBackground());
+ rendererPane.paintComponent(g2d, rendererComponent, list, cx, cy, cw,
+ ch, true);
+ g2d.dispose();
+ }
+
+ public StateTransitionTracker getStateTransitionTracker(int row) {
+ return this.stateTransitionMultiTracker.getTracker(row);
+ }
+
+ /**
+ * Returns the current state for the specified cell.
+ *
+ * @param cellIndex
+ * Cell index.
+ * @param rendererComponent
+ * Renderer component for the specified cell index.
+ * @return The current state for the specified cell.
+ */
+ public ComponentState getCellState(int cellIndex,
+ Component rendererComponent) {
+ boolean isEnabled = this.list.isEnabled();
+ if (rendererComponent != null) {
+ isEnabled = isEnabled && rendererComponent.isEnabled();
+ }
+ StateTransitionTracker tracker = this.stateTransitionMultiTracker
+ .getTracker(cellIndex);
+ if (tracker == null) {
+ boolean isRollover = (rolledOverIndex >= 0)
+ && (rolledOverIndex == cellIndex);
+ boolean isSelected = selectedIndices.containsKey(cellIndex);
+ return ComponentState.getState(isEnabled, isRollover, isSelected);
+ } else {
+ ComponentState fromTracker = tracker.getModelStateInfo()
+ .getCurrModelState();
+ return ComponentState.getState(isEnabled,
+ fromTracker.isFacetActive(ComponentStateFacet.ROLLOVER),
+ fromTracker.isFacetActive(ComponentStateFacet.SELECTION));
+ }
+ }
+
+ public StateTransitionTracker.ModelStateInfo getModelStateInfo(int row,
+ Component rendererComponent) {
+ if (this.stateTransitionMultiTracker.size() == 0)
+ return null;
+ StateTransitionTracker tracker = this.stateTransitionMultiTracker
+ .getTracker(row);
+ if (tracker == null) {
+ return null;
+ } else {
+ return tracker.getModelStateInfo();
+ }
+ }
+
+ /**
+ * Resets the rollover index.
+ */
+ public void resetRolloverIndex() {
+ rolledOverIndex = -1;
+ }
+
+ /**
+ * Initiates the fade out effect.
+ */
+ private void fadeOutRolloverIndication() {
+ if (rolledOverIndex < 0)
+ return;
+
+ StateTransitionTracker tracker = getTracker(rolledOverIndex, true,
+ list.isSelectedIndex(rolledOverIndex));
+ tracker.getModel().setRollover(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ BackgroundPaintingUtils.updateIfOpaque(g, c);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ SubstanceStripingUtils.setup(c);
+ this.updateInfo = new UpdateOptimizationInfo(c);
+ this.paint(g2d, c);
+ SubstanceStripingUtils.tearDown(c);
+ g2d.dispose();
+ this.updateInfo = null;
+ }
+
+ // /**
+ // * Returns the current default color scheme. This method is for internal
+ // use
+ // * only.
+ // *
+ // * @return The current default color scheme.
+ // */
+ // public SubstanceColorScheme getDefaultColorScheme() {
+ // if (this.updateInfo != null)
+ // return this.updateInfo.defaultScheme;
+ // return null;
+ // }
+ //
+ // public SubstanceColorScheme getHighlightColorScheme(ComponentState state)
+ // {
+ // if (this.updateInfo != null)
+ // return updateInfo.getHighlightColorScheme(state);
+ // return null;
+ // }
+
+ private UpdateOptimizationInfo updateInfo;
+
+ // private class UpdateOptimizationInfo {
+ // public boolean toDrawWatermark;
+ //
+ // private Map<ComponentState, SubstanceColorScheme> highlightSchemeMap;
+ //
+ // private Map<ComponentState, SubstanceColorScheme> borderSchemeMap;
+ //
+ // private Map<ComponentState, Float> highlightAlphaMap;
+ //
+ // public SubstanceColorScheme defaultScheme;
+ //
+ // public DecorationAreaType decorationAreaType;
+ //
+ // public boolean isInDecorationArea;
+ //
+ // public UpdateOptimizationInfo() {
+ // this.toDrawWatermark = SubstanceCoreUtilities.toDrawWatermark(list);
+ // this.defaultScheme = SubstanceColorSchemeUtilities.getColorScheme(
+ // list, ComponentState.DEFAULT);
+ // this.highlightAlphaMap = new EnumMap<ComponentState, Float>(
+ // ComponentState.class);
+ // this.highlightSchemeMap = new EnumMap<ComponentState,
+ // SubstanceColorScheme>(
+ // ComponentState.class);
+ // this.borderSchemeMap = new EnumMap<ComponentState, SubstanceColorScheme>(
+ // ComponentState.class);
+ // this.decorationAreaType = SubstanceLookAndFeel
+ // .getDecorationType(list);
+ //
+ // SubstanceSkin skin = SubstanceCoreUtilities.getSkin(list);
+ // this.isInDecorationArea = (this.decorationAreaType != null)
+ // && skin
+ // .isRegisteredAsDecorationArea(this.decorationAreaType)
+ // && SubstanceCoreUtilities.isOpaque(list);
+ // }
+ //
+ // public SubstanceColorScheme getHighlightColorScheme(ComponentState state)
+ // {
+ // if (!this.highlightSchemeMap.containsKey(state)) {
+ // this.highlightSchemeMap.put(state,
+ // SubstanceColorSchemeUtilities.getColorScheme(list,
+ // ColorSchemeAssociationKind.HIGHLIGHT, state));
+ // }
+ // return this.highlightSchemeMap.get(state);
+ // }
+ //
+ // public SubstanceColorScheme getHighlightBorderColorScheme(
+ // ComponentState state) {
+ // if (!this.borderSchemeMap.containsKey(state)) {
+ // this.borderSchemeMap.put(state, SubstanceColorSchemeUtilities
+ // .getColorScheme(list,
+ // ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ // state));
+ // }
+ // return this.borderSchemeMap.get(state);
+ // }
+ //
+ // public float getHighlightAlpha(ComponentState state) {
+ // if (!this.highlightAlphaMap.containsKey(state)) {
+ // this.highlightAlphaMap.put(state, SubstanceColorSchemeUtilities
+ // .getHighlightAlpha(list, state));
+ // }
+ // return this.highlightAlphaMap.get(state);
+ // }
+ // }
+
+ private void syncModelContents() {
+ if (list == null)
+ return;
+ stateTransitionMultiTracker.clear();
+ selectedIndices.clear();
+ for (int i = 0; i < list.getModel().getSize(); i++) {
+ if (list.isSelectedIndex(i)) {
+ selectedIndices.put(i, list.getModel().getElementAt(i));
+ }
+ }
+ list.repaint();
+ }
+
+ private StateTransitionTracker getTracker(final int row,
+ boolean initialRollover, boolean initialSelected) {
+ StateTransitionTracker tracker = stateTransitionMultiTracker
+ .getTracker(row);
+ if (tracker == null) {
+ ButtonModel model = new DefaultButtonModel();
+ model.setSelected(initialSelected);
+ model.setRollover(initialRollover);
+ tracker = new StateTransitionTracker(list, model);
+ tracker.registerModelListeners();
+ tracker.setRepaintCallback(new RepaintCallback() {
+ @Override
+ public TimelineCallback getRepaintCallback() {
+ return new CellRepaintCallback(list, row);
+ }
+ });
+ tracker.setName("row " + row);
+ stateTransitionMultiTracker.addTracker(row, tracker);
+ }
+ return tracker;
+ }
+
+ @Override
+ public UpdateOptimizationInfo getUpdateOptimizationInfo() {
+ return this.updateInfo;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceMenuBarUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceMenuBarUI.java
new file mode 100644
index 0000000..05b55a5
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceMenuBarUI.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.Graphics;
+
+import javax.swing.JComponent;
+import javax.swing.JMenuBar;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicMenuBarUI;
+
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI for menu bars in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceMenuBarUI extends BasicMenuBarUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceMenuBarUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuBarUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+
+ SubstanceLookAndFeel.setDecorationType(this.menuBar,
+ DecorationAreaType.HEADER);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuBarUI#uninstallDefaults()
+ */
+ @Override
+ protected void uninstallDefaults() {
+ DecorationPainterUtils.clearDecorationType(this.menuBar);
+
+ super.uninstallDefaults();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ boolean isOpaque = SubstanceCoreUtilities.isOpaque(c);
+ if (isOpaque) {
+ BackgroundPaintingUtils.update(g, c, false);
+ } else {
+ super.update(g, c);
+ }
+ }
+
+ /**
+ * Returns the menu bar of this UI delegate. This method is for internal use
+ * only.
+ *
+ * @return The menu bar of this UI delegate.
+ */
+ public JMenuBar getMenuBar() {
+ return this.menuBar;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceMenuItemUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceMenuItemUI.java
new file mode 100644
index 0000000..430453a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceMenuItemUI.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicMenuItemUI;
+
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.menu.MenuUtilities;
+import org.pushingpixels.substance.internal.utils.menu.SubstanceMenu;
+import org.pushingpixels.substance.internal.utils.menu.MenuUtilities.MenuPropertyListener;
+
+/**
+ * UI for menu items in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceMenuItemUI extends BasicMenuItemUI implements
+ SubstanceMenu, TransitionAwareUI {
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Listens on all changes to the underlying menu item.
+ */
+ protected MenuPropertyListener substanceMenuPropertyListener;
+
+ /**
+ * Rollover listener.
+ */
+ protected RolloverMenuItemListener substanceRolloverListener;
+
+ protected PropertyChangeListener substancePropertyListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceMenuItemUI((JMenuItem) comp);
+ }
+
+ public SubstanceMenuItemUI(JMenuItem menuItem) {
+ this.stateTransitionTracker = new StateTransitionTracker(menuItem,
+ menuItem.getModel());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuItemUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ // Improving performance on big menus.
+ this.substanceMenuPropertyListener = new MenuPropertyListener(
+ this.menuItem);
+ this.substanceMenuPropertyListener.install();
+
+ // fix for defect 109 - storing reference to rollover listener
+ this.substanceRolloverListener = new RolloverMenuItemListener(
+ this.menuItem, this.stateTransitionTracker);
+ this.menuItem.addMouseListener(this.substanceRolloverListener);
+
+ this.stateTransitionTracker.registerModelListeners();
+
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (AbstractButton.MODEL_CHANGED_PROPERTY.equals(evt
+ .getPropertyName())) {
+ stateTransitionTracker.setModel((ButtonModel) evt
+ .getNewValue());
+ }
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (menuItem != null) {
+ menuItem.updateUI();
+ }
+ }
+ });
+ }
+ }
+ };
+ this.menuItem.addPropertyChangeListener(this.substancePropertyListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuItemUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+
+ this.defaultTextIconGap = SubstanceSizeUtils
+ .getTextIconGap(SubstanceSizeUtils
+ .getComponentFontSize(this.menuItem));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuItemUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ // Improving performance on big menus.
+ this.substanceMenuPropertyListener.uninstall();
+ this.substanceMenuPropertyListener = null;
+
+ // fix for defect 109 - unregistering rollover listener
+ this.menuItem.removeMouseListener(this.substanceRolloverListener);
+ this.substanceRolloverListener = null;
+
+ this.stateTransitionTracker.unregisterModelListeners();
+
+ this.menuItem
+ .removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getAssociatedMenuItem()
+ */
+ @Override
+ public JMenuItem getAssociatedMenuItem() {
+ return this.menuItem;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getAcceleratorFont()
+ */
+ @Override
+ public Font getAcceleratorFont() {
+ return this.acceleratorFont;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getArrowIcon()
+ */
+ @Override
+ public Icon getArrowIcon() {
+ return this.arrowIcon;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getCheckIcon()
+ */
+ @Override
+ public Icon getCheckIcon() {
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getDefaultTextIconGap()
+ */
+ @Override
+ public int getDefaultTextIconGap() {
+ return this.defaultTextIconGap;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicMenuItemUI#getPreferredMenuItemSize(javax
+ * .swing.JComponent, javax.swing.Icon, javax.swing.Icon, int)
+ */
+ @Override
+ protected Dimension getPreferredMenuItemSize(JComponent c, Icon checkIcon,
+ Icon arrowIcon, int defaultTextIconGap) {
+ Dimension superDim = super.getPreferredMenuItemSize(c, checkIcon,
+ arrowIcon, defaultTextIconGap);
+ return new Dimension(MenuUtilities.getPreferredWidth(menuItem),
+ superDim.height);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicMenuItemUI#paintMenuItem(java.awt.Graphics,
+ * javax.swing.JComponent, javax.swing.Icon, javax.swing.Icon,
+ * java.awt.Color, java.awt.Color, int)
+ */
+ @Override
+ protected void paintMenuItem(Graphics g, JComponent c, Icon checkIcon,
+ Icon arrowIcon, Color background, Color foreground,
+ int defaultTextIconGap) {
+ MenuUtilities.paintMenuItem(g, menuItem, checkIcon, arrowIcon,
+ defaultTextIconGap);
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return true;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceMenuUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceMenuUI.java
new file mode 100644
index 0000000..e55345f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceMenuUI.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicMenuUI;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.icon.MenuArrowIcon;
+import org.pushingpixels.substance.internal.utils.menu.MenuUtilities;
+import org.pushingpixels.substance.internal.utils.menu.SubstanceMenu;
+import org.pushingpixels.substance.internal.utils.menu.MenuUtilities.MenuPropertyListener;
+
+/**
+ * UI for menus in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceMenuUI extends BasicMenuUI implements SubstanceMenu,
+ TransitionAwareUI {
+ /**
+ * For rollover effects - enhancement 93.
+ */
+ protected MouseListener substanceMouseListener;
+
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Listens on all changes to the underlying menu item.
+ */
+ protected MenuPropertyListener substanceMenuPropertyListener;
+
+ /**
+ * Property change listener. Listens on changes to
+ * {@link AbstractButton#MODEL_CHANGED_PROPERTY} property.
+ */
+ protected PropertyChangeListener substancePropertyListener;
+
+ /**
+ * For rollover effects - enhancement 93.
+ */
+ protected FocusListener substanceFocusListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceMenuUI((JMenu) comp);
+ }
+
+ public SubstanceMenuUI(JMenu menuItem) {
+ this.stateTransitionTracker = new StateTransitionTracker(menuItem,
+ menuItem.getModel());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ this.menuItem.setRolloverEnabled(true);
+ // this.menuItem.setOpaque(false);
+
+ this.arrowIcon = new MenuArrowIcon((JMenu) this.menuItem);
+
+ this.defaultTextIconGap = SubstanceSizeUtils
+ .getTextIconGap(SubstanceSizeUtils
+ .getComponentFontSize(this.menuItem));
+
+ this.menuItem.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY,
+ Boolean.TRUE);
+
+ LookAndFeel.installProperty(menuItem, "opaque", Boolean.FALSE);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ // Improving performance on big menus.
+ this.substanceMenuPropertyListener = new MenuPropertyListener(
+ this.menuItem);
+ this.substanceMenuPropertyListener.install();
+
+ this.stateTransitionTracker.registerModelListeners();
+
+ // fix for enhancement 93 - rollover effects on menu items
+ this.substanceMouseListener = new MouseAdapter() {
+ // fix for defect 93 - no rollover effects on menu
+ // items that are not in the selected path
+ private boolean toRepaint() {
+ MenuElement[] selectedMenuPath = MenuSelectionManager
+ .defaultManager().getSelectedPath();
+ for (MenuElement elem : selectedMenuPath) {
+ if (elem == SubstanceMenuUI.this.menuItem) {
+ return true;
+ }
+ }
+ return (selectedMenuPath.length == 0);
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (this.toRepaint()) {
+ stateTransitionTracker.turnOffModelChangeTracking();
+ menuItem.getModel().setRollover(true);
+ stateTransitionTracker.onModelStateChanged();
+ // fix for issue 371 - repaint the menu bar since the
+ // menu is marked as flat
+ Rectangle bounds = menuItem.getBounds();
+ menuItem.getParent().repaint(bounds.x, bounds.y,
+ bounds.width, bounds.height);
+ }
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (this.toRepaint()) {
+ stateTransitionTracker.turnOffModelChangeTracking();
+ menuItem.getModel().setRollover(false);
+ stateTransitionTracker.onModelStateChanged();
+ // fix for issue 371 - repaint the menu bar since the
+ // menu is marked as flat
+ Rectangle bounds = menuItem.getBounds();
+ menuItem.getParent().repaint(bounds.x, bounds.y,
+ bounds.width, bounds.height);
+ }
+ }
+ };
+ this.menuItem.addMouseListener(this.substanceMouseListener);
+ this.substanceFocusListener = new FocusAdapter() {
+ // fix for defect 93 - no rollover effects on menu
+ // items that are not in the selected path
+ private boolean toRepaint() {
+ MenuElement[] selectedMenuPath = MenuSelectionManager
+ .defaultManager().getSelectedPath();
+ for (MenuElement elem : selectedMenuPath) {
+ if (elem == SubstanceMenuUI.this.menuItem) {
+ return true;
+ }
+ }
+ return (selectedMenuPath.length == 0);
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ if (toRepaint()) {
+ stateTransitionTracker.turnOffModelChangeTracking();
+ menuItem.getModel().setRollover(false);
+ stateTransitionTracker.onModelStateChanged();
+ // fix for issue 371 - repaint the menu bar since the
+ // menu is marked as flat
+ Rectangle bounds = menuItem.getBounds();
+ menuItem.getParent().repaint(bounds.x, bounds.y,
+ bounds.width, bounds.height);
+ }
+ }
+ };
+ this.menuItem.addFocusListener(this.substanceFocusListener);
+
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (AbstractButton.MODEL_CHANGED_PROPERTY.equals(evt
+ .getPropertyName())) {
+ stateTransitionTracker.setModel((ButtonModel) evt
+ .getNewValue());
+ }
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (menuItem != null) {
+ menuItem.updateUI();
+ }
+ }
+ });
+ }
+ }
+ };
+ this.menuItem.addPropertyChangeListener(this.substancePropertyListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ super.uninstallListeners();
+
+ // Improving performance on big menus.
+ this.substanceMenuPropertyListener.uninstall();
+ this.substanceMenuPropertyListener = null;
+
+ // fix for enhancement 93 - rollover effects on menu items
+ this.menuItem.removeMouseListener(this.substanceMouseListener);
+ this.substanceMouseListener = null;
+ this.menuItem.removeFocusListener(this.substanceFocusListener);
+ this.substanceFocusListener = null;
+
+ this.menuItem
+ .removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+
+ this.stateTransitionTracker.unregisterModelListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getAssociatedMenuItem()
+ */
+ @Override
+ public JMenuItem getAssociatedMenuItem() {
+ return this.menuItem;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getAcceleratorFont()
+ */
+ @Override
+ public Font getAcceleratorFont() {
+ return this.acceleratorFont;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getArrowIcon()
+ */
+ @Override
+ public Icon getArrowIcon() {
+ return this.arrowIcon;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getCheckIcon()
+ */
+ @Override
+ public Icon getCheckIcon() {
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getDefaultTextIconGap()
+ */
+ @Override
+ public int getDefaultTextIconGap() {
+ return this.defaultTextIconGap;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicMenuItemUI#getPreferredMenuItemSize(javax
+ * .swing.JComponent, javax.swing.Icon, javax.swing.Icon, int)
+ */
+ @Override
+ protected Dimension getPreferredMenuItemSize(JComponent c, Icon checkIcon,
+ Icon arrowIcon, int defaultTextIconGap) {
+ Dimension superDim = super.getPreferredMenuItemSize(c, checkIcon,
+ arrowIcon, defaultTextIconGap);
+
+ if (MenuUtilities.getPopupLayoutMetrics(menuItem, false) != null) {
+ return new Dimension(MenuUtilities.getPreferredWidth(menuItem),
+ superDim.height);
+ }
+
+ return superDim;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicMenuItemUI#paintMenuItem(java.awt.Graphics,
+ * javax.swing.JComponent, javax.swing.Icon, javax.swing.Icon,
+ * java.awt.Color, java.awt.Color, int)
+ */
+ @Override
+ protected void paintMenuItem(Graphics g, JComponent c, Icon checkIcon,
+ Icon arrowIcon, Color background, Color foreground,
+ int defaultTextIconGap) {
+ MenuUtilities.paintMenuItem(g, menuItem, checkIcon, arrowIcon,
+ defaultTextIconGap);
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return true;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceOptionPaneUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceOptionPaneUI.java
new file mode 100644
index 0000000..5991727
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceOptionPaneUI.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicOptionPaneUI;
+
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.animation.IconGlowTracker;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.icon.GlowingIcon;
+
+/**
+ * UI for option panes in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceOptionPaneUI extends BasicOptionPaneUI {
+ static {
+ AnimationConfigurationManager.getInstance().allowAnimations(
+ AnimationFacet.ICON_GLOW, OptionPaneLabel.class);
+ }
+
+ /**
+ * Label extension class. Due to defect 250, the option pane icon animation
+ * (glowing icon) should repaint only the icon itself and not the entire
+ * option pane. While the {@link AnimationConfigurationManager} API provides
+ * an option to enable animations on the specific component, it's better to
+ * enable it on the component class (to make the lookups faster). So, when
+ * the option pane icon label is created (in addIcon method), we use this
+ * class.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected static class OptionPaneLabel extends JLabel {
+ }
+
+ /**
+ * Icon label.
+ */
+ private OptionPaneLabel substanceIconLabel;
+
+ private IconGlowTracker iconGlowTracker;
+
+ /**
+ * Creates a new SubstanceOptionPaneUI instance.
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceOptionPaneUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ BackgroundPaintingUtils.updateIfOpaque(g, c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicOptionPaneUI#addIcon(java.awt.Container)
+ */
+ @Override
+ protected void addIcon(Container top) {
+ Icon sideIcon = (optionPane == null ? null : optionPane.getIcon());
+
+ if (sideIcon == null && optionPane != null)
+ sideIcon = super.getIconForType(optionPane.getMessageType());
+
+ if (sideIcon != null) {
+ if (!SubstanceLookAndFeel.isToUseConstantThemesOnDialogs()) {
+ sideIcon = SubstanceCoreUtilities.getThemedIcon(null, sideIcon);
+ }
+
+ this.substanceIconLabel = new OptionPaneLabel();
+ this.iconGlowTracker = new IconGlowTracker(substanceIconLabel);
+ this.substanceIconLabel.setIcon(new GlowingIcon(sideIcon,
+ this.iconGlowTracker));
+
+ this.substanceIconLabel.setName("OptionPane.iconLabel");
+ this.substanceIconLabel.setVerticalAlignment(SwingConstants.TOP);
+ top.add(this.substanceIconLabel, BorderLayout.BEFORE_LINE_BEGINS);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicOptionPaneUI#getIconForType(int)
+ */
+ @Override
+ protected Icon getIconForType(int messageType) {
+ switch (messageType) {
+ case JOptionPane.ERROR_MESSAGE:
+ return SubstanceCoreUtilities
+ .getIcon("resource/32/dialog-error.png");
+ case JOptionPane.INFORMATION_MESSAGE:
+ return SubstanceCoreUtilities
+ .getIcon("resource/32/dialog-information.png");
+ case JOptionPane.WARNING_MESSAGE:
+ return SubstanceCoreUtilities
+ .getIcon("resource/32/dialog-warning.png");
+ case JOptionPane.QUESTION_MESSAGE:
+ return SubstanceCoreUtilities
+ .getIcon("resource/32/help-browser.png");
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicOptionPaneUI#installComponents()
+ */
+ @Override
+ protected void installComponents() {
+ super.installComponents();
+ // fix for defect 265 - check that the label is not null
+ // before activating the loop.
+ if (this.substanceIconLabel != null) {
+ // Make the icon glow for three cycles. There's no need to
+ // explicitly cancel the animation when the option pane is closed
+ // before the animation is over - when the three cycles are up,
+ // the animation will be removed by the tracker.
+ if (!this.iconGlowTracker.isPlaying()) {
+ this.iconGlowTracker.play(3);
+ }
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstancePanelUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstancePanelUI.java
new file mode 100644
index 0000000..e06679d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstancePanelUI.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.Color;
+import java.awt.Graphics;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicPanelUI;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI for panels in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstancePanelUI extends BasicPanelUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstancePanelUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicPanelUI#installDefaults(javax.swing.JPanel)
+ */
+ @Override
+ protected void installDefaults(JPanel p) {
+ super.installDefaults(p);
+ // support for per-window skins
+ Color backgr = p.getBackground();
+ if ((backgr == null) || (backgr instanceof UIResource)) {
+ Color backgroundFillColor = SubstanceColorUtilities
+ .getBackgroundFillColor(p);
+ // fix for issue 436 - logic in getBackground() of
+ // custom panels can result in null value
+ if (backgroundFillColor != null) {
+ p.setBackground(new ColorUIResource(backgroundFillColor));
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return;
+
+ if (toPaintBackground(c)) {
+ BackgroundPaintingUtils.update(g, c, false);
+ }
+ super.paint(g, c);
+ }
+
+ /**
+ * Returns indication whether the panel background should be filled.
+ *
+ * @param c
+ * Component (should be {@link JPanel}).
+ * @return <code>true</code> if the panel background should be filled with
+ * the background color, <code>false</code> otherwise.
+ */
+ protected boolean toPaintBackground(JComponent c) {
+ return SubstanceCoreUtilities.isOpaque(c);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstancePasswordFieldUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstancePasswordFieldUI.java
new file mode 100644
index 0000000..dc23448
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstancePasswordFieldUI.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicBorders;
+import javax.swing.plaf.basic.BasicPasswordFieldUI;
+import javax.swing.text.*;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.border.SubstanceTextComponentBorder;
+
+/**
+ * UI for password fields in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstancePasswordFieldUI extends BasicPasswordFieldUI implements
+ TransitionAwareUI {
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * The associated password field.
+ */
+ protected JPasswordField passwordField;
+
+ /**
+ * Property change listener.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Listener for transition animations.
+ */
+ private RolloverTextControlListener substanceRolloverListener;
+
+ /**
+ * Surrogate button model for tracking the state transitions.
+ */
+ private ButtonModel transitionModel;
+
+ // private FocusListener substanceFocusListener;
+
+ /**
+ * Custom password view.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class SubstancePasswordView extends FieldView {
+ /**
+ * The associated password field.
+ */
+ private JPasswordField field;
+
+ /**
+ * Simple constructor.
+ *
+ * @param field
+ * The associated password field.
+ * @param element
+ * The element
+ */
+ public SubstancePasswordView(JPasswordField field, Element element) {
+ super(element);
+ this.field = field;
+ }
+
+ /**
+ * Draws the echo character(s) for a single password field character.
+ * The number of echo characters is defined by
+ * {@link SubstanceLookAndFeel#PASSWORD_ECHO_PER_CHAR} client property.
+ *
+ * @param g
+ * Graphics context
+ * @param x
+ * X coordinate of the first echo character to draw.
+ * @param y
+ * Y coordinate of the first echo character to draw.
+ * @param c
+ * Password field.
+ * @param isSelected
+ * Indicates whether the password field character is
+ * selected.
+ * @return The X location of the next echo character.
+ * @see SubstanceLookAndFeel#PASSWORD_ECHO_PER_CHAR
+ */
+ protected int drawEchoCharacter(Graphics g, int x, int y, char c,
+ boolean isSelected) {
+ Container container = this.getContainer();
+
+ Graphics2D graphics = (Graphics2D) g;
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ JPasswordField field = (JPasswordField) container;
+
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(this.field);
+ int dotDiameter = SubstanceSizeUtils
+ .getPasswordDotDiameter(fontSize);
+ int dotGap = SubstanceSizeUtils.getPasswordDotGap(fontSize);
+ ComponentState state = // isSelected ? ComponentState.SELECTED
+ (field.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED);
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(field, state);
+ Color topColor = isSelected ? scheme.getSelectionForegroundColor()
+ : SubstanceColorUtilities.getForegroundColor(scheme);
+ Color bottomColor = topColor.brighter();
+ graphics.setPaint(new GradientPaint(x, y - dotDiameter, topColor,
+ x, y, bottomColor));
+ int echoPerChar = SubstanceCoreUtilities.getEchoPerChar(field);
+ for (int i = 0; i < echoPerChar; i++) {
+ graphics.fillOval(x + dotGap / 2, y - dotDiameter, dotDiameter,
+ dotDiameter);
+ x += (dotDiameter + dotGap);
+ }
+ return x;
+ }
+
+ /**
+ * Returns the advance of a single password field character. The advance
+ * is the pixel distance between first echo characters of consecutive
+ * password field characters. The
+ * {@link SubstanceLookAndFeel#PASSWORD_ECHO_PER_CHAR} can be used to
+ * specify that more than one echo character is used for each password
+ * field character.
+ *
+ * @return The advance of a single password field character
+ */
+ protected int getEchoCharAdvance() {
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(this.field);
+ int dotDiameter = SubstanceSizeUtils
+ .getPasswordDotDiameter(fontSize);
+ int dotGap = SubstanceSizeUtils.getPasswordDotGap(fontSize);
+ int echoPerChar = SubstanceCoreUtilities.getEchoPerChar(field);
+ return echoPerChar * (dotDiameter + dotGap);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.text.PlainView#drawSelectedText(java.awt.Graphics,
+ * int, int, int, int)
+ */
+ @Override
+ protected int drawSelectedText(Graphics g, final int x, final int y,
+ int p0, int p1) throws BadLocationException {
+ Container c = getContainer();
+ if (c instanceof JPasswordField) {
+ JPasswordField f = (JPasswordField) c;
+ if (!f.echoCharIsSet()) {
+ return super.drawSelectedText(g, x, y, p0, p1);
+ }
+ int n = p1 - p0;
+ char echoChar = f.getEchoChar();
+ int currPos = x;
+ for (int i = 0; i < n; i++) {
+ currPos = drawEchoCharacter(g, currPos, y, echoChar, true);
+ }
+ return x + n * getEchoCharAdvance();
+ }
+ return x;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.text.PlainView#drawUnselectedText(java.awt.Graphics,
+ * int, int, int, int)
+ */
+ @Override
+ protected int drawUnselectedText(Graphics g, final int x, final int y,
+ int p0, int p1) throws BadLocationException {
+ Container c = getContainer();
+ if (c instanceof JPasswordField) {
+ JPasswordField f = (JPasswordField) c;
+ if (!f.echoCharIsSet()) {
+ return super.drawUnselectedText(g, x, y, p0, p1);
+ }
+ int n = p1 - p0;
+ char echoChar = f.getEchoChar();
+ int currPos = x;
+ for (int i = 0; i < n; i++) {
+ currPos = drawEchoCharacter(g, currPos, y, echoChar, false);
+ }
+ return x + n * getEchoCharAdvance();
+ }
+ return x;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.text.View#modelToView(int, java.awt.Shape,
+ * javax.swing.text.Position.Bias)
+ */
+ @Override
+ public Shape modelToView(int pos, Shape a, Position.Bias b)
+ throws BadLocationException {
+ Container c = this.getContainer();
+ if (c instanceof JPasswordField) {
+ JPasswordField f = (JPasswordField) c;
+ if (!f.echoCharIsSet()) {
+ return super.modelToView(pos, a, b);
+ }
+
+ Rectangle alloc = this.adjustAllocation(a).getBounds();
+ int echoPerChar = SubstanceCoreUtilities.getEchoPerChar(f);
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(this.field);
+ int dotWidth = SubstanceSizeUtils
+ .getPasswordDotDiameter(fontSize)
+ + SubstanceSizeUtils.getPasswordDotGap(fontSize);
+
+ int dx = (pos - this.getStartOffset()) * echoPerChar * dotWidth;
+ alloc.x += dx;
+ alloc.width = 1;
+ return alloc;
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.text.View#viewToModel(float, float, java.awt.Shape,
+ * javax.swing.text.Position.Bias[])
+ */
+ @Override
+ public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {
+ bias[0] = Position.Bias.Forward;
+ int n = 0;
+ Container c = this.getContainer();
+ if (c instanceof JPasswordField) {
+ JPasswordField f = (JPasswordField) c;
+ if (!f.echoCharIsSet()) {
+ return super.viewToModel(fx, fy, a, bias);
+ }
+ a = this.adjustAllocation(a);
+ Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a
+ .getBounds();
+ int echoPerChar = SubstanceCoreUtilities.getEchoPerChar(f);
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(this.field);
+ int dotWidth = SubstanceSizeUtils
+ .getPasswordDotDiameter(fontSize)
+ + SubstanceSizeUtils.getPasswordDotGap(fontSize);
+ n = ((int) fx - alloc.x) / (echoPerChar * dotWidth);
+ if (n < 0) {
+ n = 0;
+ } else {
+ if (n > (this.getStartOffset() + this.getDocument()
+ .getLength())) {
+ n = this.getDocument().getLength()
+ - this.getStartOffset();
+ }
+ }
+ }
+ return this.getStartOffset() + n;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.text.View#getPreferredSpan(int)
+ */
+ @Override
+ public float getPreferredSpan(int axis) {
+ switch (axis) {
+ case View.X_AXIS:
+ Container c = this.getContainer();
+ if (c instanceof JPasswordField) {
+ JPasswordField f = (JPasswordField) c;
+ if (f.echoCharIsSet()) {
+ int echoPerChar = SubstanceCoreUtilities
+ .getEchoPerChar(f);
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(this.field);
+ int dotWidth = SubstanceSizeUtils
+ .getPasswordDotDiameter(fontSize)
+ + SubstanceSizeUtils
+ .getPasswordDotGap(fontSize);
+ return echoPerChar * dotWidth
+ * this.getDocument().getLength();
+ }
+ }
+ }
+ return super.getPreferredSpan(axis);
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstancePasswordFieldUI(comp);
+ }
+
+ /**
+ * Creates the UI delegate for the specified component (password field).
+ *
+ * @param c
+ * Component.
+ */
+ public SubstancePasswordFieldUI(JComponent c) {
+ super();
+ this.passwordField = (JPasswordField) c;
+
+ this.transitionModel = new DefaultButtonModel();
+ this.transitionModel.setArmed(false);
+ this.transitionModel.setSelected(false);
+ this.transitionModel.setPressed(false);
+ this.transitionModel.setRollover(false);
+ this.transitionModel.setEnabled(this.passwordField.isEnabled());
+
+ this.stateTransitionTracker = new StateTransitionTracker(
+ this.passwordField, this.transitionModel);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.text.ViewFactory#create(javax.swing.text.Element)
+ */
+ @Override
+ public View create(Element elem) {
+ return new SubstancePasswordView(this.passwordField, elem);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substanceRolloverListener = new RolloverTextControlListener(
+ this.passwordField, this, this.transitionModel);
+ this.substanceRolloverListener.registerListeners();
+
+ this.stateTransitionTracker.registerModelListeners();
+ this.stateTransitionTracker.registerFocusListeners();
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // remember the caret location - issue 404
+ int caretPos = passwordField.getCaretPosition();
+ passwordField.updateUI();
+ passwordField.setCaretPosition(caretPos);
+ Container parent = passwordField.getParent();
+ if (parent != null) {
+ parent.invalidate();
+ parent.validate();
+ }
+ }
+ });
+ }
+
+ if ("enabled".equals(evt.getPropertyName())) {
+ transitionModel.setEnabled(passwordField.isEnabled());
+ }
+ }
+ };
+ this.passwordField
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.stateTransitionTracker.unregisterModelListeners();
+ this.stateTransitionTracker.unregisterFocusListeners();
+
+ this.passwordField
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ this.passwordField.removeMouseListener(this.substanceRolloverListener);
+ this.passwordField
+ .removeMouseMotionListener(this.substanceRolloverListener);
+ this.substanceRolloverListener = null;
+
+ // this.passwordField.removeFocusListener(this.substanceFocusListener);
+ // this.substanceFocusListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ Border b = this.passwordField.getBorder();
+ if (b == null || b instanceof UIResource) {
+ Border newB = new BorderUIResource.CompoundBorderUIResource(
+ new SubstanceTextComponentBorder(SubstanceSizeUtils
+ .getTextBorderInsets(SubstanceSizeUtils
+ .getComponentFontSize(this.passwordField))),
+ new BasicBorders.MarginBorder());
+ this.passwordField.setBorder(newB);
+ }
+
+ // support for per-window skins
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (passwordField == null)
+ return;
+ Color foregr = passwordField.getForeground();
+ if ((foregr == null) || (foregr instanceof UIResource)) {
+ passwordField
+ .setForeground(SubstanceColorUtilities
+ .getForegroundColor(SubstanceLookAndFeel
+ .getCurrentSkin(passwordField)
+ .getEnabledColorScheme(
+ SubstanceLookAndFeel
+ .getDecorationType(passwordField))));
+ }
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTextUI#paintBackground(java.awt.Graphics)
+ */
+ @Override
+ protected void paintBackground(Graphics g) {
+ SubstanceTextUtilities.paintTextCompBackground(g, this.passwordField);
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ return false;
+ }
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(
+ this.passwordField, 2.0f * SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(this.passwordField)),
+ null);
+ return contour.contains(me.getPoint());
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstancePopupMenuSeparatorUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstancePopupMenuSeparatorUI.java
new file mode 100644
index 0000000..d084edc
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstancePopupMenuSeparatorUI.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+
+import javax.swing.JComponent;
+import javax.swing.JSeparator;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicPopupMenuSeparatorUI;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.internal.painter.SeparatorPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.menu.MenuUtilities;
+import org.pushingpixels.substance.internal.utils.menu.SubstanceMenuBackgroundDelegate;
+
+/**
+ * UI for popup menu separators in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstancePopupMenuSeparatorUI extends BasicPopupMenuSeparatorUI {
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstancePopupMenuSeparatorUI();
+ }
+
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ JSeparator sep = (JSeparator) c;
+
+ int xOffset = MenuUtilities.getTextOffset(sep, sep.getParent());
+ SubstanceMenuBackgroundDelegate.paintBackground(graphics, c, xOffset);
+
+ Dimension s = c.getSize();
+ int startX = 0;
+ int width = s.width;
+ if (c.getComponentOrientation().isLeftToRight()) {
+ startX = xOffset - 2;
+ width = s.width - startX;
+ } else {
+ startX = 0;
+ if (xOffset > 0) {
+ width = xOffset - 4;
+ } else {
+ width = s.width;
+ }
+ }
+ graphics.translate(startX, 0);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(sep));
+ SeparatorPainterUtils.paintSeparator(sep, graphics, width, s.height,
+ sep.getOrientation(), true, 2);
+
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicPopupMenuSeparatorUI#getPreferredSize(javax
+ * .swing.JComponent)
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ float borderStrokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ int prefSize = (int) (Math.ceil(2.0 * borderStrokeWidth));
+ return new Dimension(0, prefSize);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstancePopupMenuUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstancePopupMenuUI.java
new file mode 100644
index 0000000..eabda78
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstancePopupMenuUI.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.Component;
+import java.awt.event.ContainerEvent;
+import java.awt.event.ContainerListener;
+
+import javax.swing.JComponent;
+import javax.swing.JMenuItem;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicPopupMenuUI;
+
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.menu.MenuUtilities;
+
+/**
+ * UI for popup menus in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstancePopupMenuUI extends BasicPopupMenuUI {
+ /**
+ * Tracks changes to the popup menu and invalidates precomputed text offset.
+ */
+ protected ContainerListener substanceContainerListener;
+
+ protected PopupMenuListener substancePopupMenuListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstancePopupMenuUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicPopupMenuUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substanceContainerListener = new ContainerListener() {
+ @Override
+ public void componentAdded(ContainerEvent e) {
+ MenuUtilities.cleanPopupLayoutMetrics(popupMenu);
+ }
+
+ @Override
+ public void componentRemoved(ContainerEvent e) {
+ MenuUtilities.cleanPopupLayoutMetrics(popupMenu);
+ }
+ };
+ this.popupMenu.addContainerListener(this.substanceContainerListener);
+
+ this.substancePopupMenuListener = new PopupMenuListener() {
+ @Override
+ public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+ }
+
+ @Override
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+ // go over all elements in the popup menu and mark them as
+ // unarmed if necessary
+ for (int i = 0; i < popupMenu.getComponentCount(); i++) {
+ Component comp = popupMenu.getComponent(i);
+ if (comp instanceof JMenuItem) {
+ JMenuItem menuItem = (JMenuItem) comp;
+ if (menuItem.isEnabled()
+ && menuItem.getModel().isArmed())
+ menuItem.getModel().setArmed(false);
+ }
+ }
+ }
+
+ @Override
+ public void popupMenuCanceled(PopupMenuEvent e) {
+ // go over all elements in the popup menu and mark them as
+ // unarmed if necessary
+ for (int i = 0; i < popupMenu.getComponentCount(); i++) {
+ Component comp = popupMenu.getComponent(i);
+ if (comp instanceof JMenuItem) {
+ JMenuItem menuItem = (JMenuItem) comp;
+ if (menuItem.isEnabled()
+ && menuItem.getModel().isArmed())
+ menuItem.getModel().setArmed(false);
+ }
+ }
+ }
+ };
+ this.popupMenu.addPopupMenuListener(this.substancePopupMenuListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicPopupMenuUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.popupMenu.removeContainerListener(this.substanceContainerListener);
+ this.substanceContainerListener = null;
+
+ this.popupMenu.removePopupMenuListener(this.substancePopupMenuListener);
+ this.substancePopupMenuListener = null;
+
+ super.uninstallListeners();
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceProgressBarUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceProgressBarUI.java
new file mode 100644
index 0000000..ecb7a18
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceProgressBarUI.java
@@ -0,0 +1,767 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicProgressBarUI;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.RepeatBehavior;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.TimelinePropertyBuilder.PropertySetter;
+import org.pushingpixels.trident.callback.TimelineCallback;
+import org.pushingpixels.trident.ease.Spline;
+
+/**
+ * UI for progress bars in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceProgressBarUI extends BasicProgressBarUI {
+ private static final ComponentState DETERMINATE_SELECTED = new ComponentState(
+ "determinate enabled", new ComponentStateFacet[] {
+ ComponentStateFacet.ENABLE,
+ ComponentStateFacet.DETERMINATE,
+ ComponentStateFacet.SELECTION }, null);
+
+ private static final ComponentState DETERMINATE_SELECTED_DISABLED = new ComponentState(
+ "determinate disabled", new ComponentStateFacet[] {
+ ComponentStateFacet.DETERMINATE,
+ ComponentStateFacet.SELECTION },
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE });
+
+ private static final ComponentState INDETERMINATE_SELECTED = new ComponentState(
+ "indeterminate enabled",
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE,
+ ComponentStateFacet.SELECTION },
+ new ComponentStateFacet[] { ComponentStateFacet.DETERMINATE });
+
+ private static final ComponentState INDETERMINATE_SELECTED_DISABLED = new ComponentState(
+ "indeterminate disabled", null, new ComponentStateFacet[] {
+ ComponentStateFacet.DETERMINATE,
+ ComponentStateFacet.ENABLE, ComponentStateFacet.SELECTION });
+
+ private final class SubstanceChangeListener implements ChangeListener {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ SubstanceCoreUtilities
+ .testComponentStateChangeThreadingViolation(progressBar);
+
+ int currValue = progressBar.getValue();
+ int span = progressBar.getMaximum() - progressBar.getMinimum();
+
+ int barRectWidth = progressBar.getWidth() - 2 * margin;
+ int barRectHeight = progressBar.getHeight() - 2 * margin;
+ int totalPixels = (progressBar.getOrientation() == JProgressBar.HORIZONTAL) ? barRectWidth
+ : barRectHeight;
+ // fix for defect 223 (min and max on the model are the
+ // same).
+ int pixelDelta = (span <= 0) ? 0 : (currValue - displayedValue)
+ * totalPixels / span;
+
+ if (displayTimeline != null) {
+ displayTimeline.abort();
+ }
+ displayTimeline = new Timeline(progressBar);
+ displayTimeline.addPropertyToInterpolate(Timeline
+ .<Integer> property("displayedValue").from(displayedValue)
+ .to(currValue).setWith(new PropertySetter<Integer>() {
+ @Override
+ public void set(Object obj, String fieldName,
+ Integer value) {
+ displayedValue = value;
+ progressBar.repaint();
+ }
+ }));
+ displayTimeline.setEase(new Spline(0.4f));
+ AnimationConfigurationManager.getInstance().configureTimeline(
+ displayTimeline);
+
+ // do not animate progress bars used in cell renderers
+ // since in this case it will most probably be the
+ // same progress bar used to display different
+ // values for different cells.
+ boolean isInCellRenderer = (SwingUtilities.getAncestorOfClass(
+ CellRendererPane.class, progressBar) != null);
+ if (!isInCellRenderer && Math.abs(pixelDelta) > 5) {
+ displayTimeline.play();
+ } else {
+ displayedValue = currValue;
+ progressBar.repaint();
+ }
+ }
+ }
+
+ /**
+ * Hash for computed stripe images.
+ */
+ private static LazyResettableHashMap<BufferedImage> stripeMap = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceProgressBarUI.stripeMap");
+
+ /**
+ * Hash for computed background images.
+ */
+ private static LazyResettableHashMap<BufferedImage> backgroundMap = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceProgressBarUI.backgroundMap");
+
+ /**
+ * The current position of the indeterminate animation's cycle. 0, the
+ * initial value, means paint the first frame. When the progress bar is
+ * indeterminate and showing, the {@link #indeterminateLoopTimeline} is
+ * updating this value.
+ */
+ private float animationPosition;
+
+ /**
+ * Value change listener on the associated progress bar.
+ */
+ protected ChangeListener substanceValueChangeListener;
+
+ /**
+ * Property change listener. Tracks changes to the <code>font</code>
+ * property.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Inner margin.
+ */
+ protected int margin;
+
+ /**
+ * The speed factor for the indeterminate progress bars.
+ */
+ protected float speed;
+
+ protected int displayedValue;
+
+ protected Timeline displayTimeline;
+
+ protected Timeline indeterminateLoopTimeline;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceProgressBarUI();
+ }
+
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+
+ this.displayedValue = progressBar.getValue();
+ LookAndFeel.installProperty(progressBar, "opaque", Boolean.FALSE);
+
+ this.speed = (20.0f * UIManager.getInt("ProgressBar.repaintInterval"))
+ / UIManager.getInt("ProgressBar.cycleTime");
+
+ float borderThickness = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.progressBar));
+ this.margin = (int) Math.ceil(1.5 * borderThickness);
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ substanceValueChangeListener = new SubstanceChangeListener();
+ this.progressBar.addChangeListener(this.substanceValueChangeListener);
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (progressBar != null)
+ progressBar.updateUI();
+ }
+ });
+ }
+ }
+ };
+ this.progressBar
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicProgressBarUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.progressBar
+ .removeChangeListener(this.substanceValueChangeListener);
+ this.substanceValueChangeListener = null;
+
+ this.progressBar
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /**
+ * Retrieves stripe image.
+ *
+ * @param baseSize
+ * Stripe base in pixels.
+ * @param isRotated
+ * if <code>true</code>, the resulting stripe image will be
+ * rotated.
+ * @param colorScheme
+ * Color scheme to paint the stripe image.
+ * @return Stripe image.
+ */
+ private static BufferedImage getStripe(int baseSize, boolean isRotated,
+ SubstanceColorScheme colorScheme) {
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(baseSize, isRotated,
+ colorScheme.getDisplayName());
+ BufferedImage result = SubstanceProgressBarUI.stripeMap.get(key);
+ if (result == null) {
+ result = SubstanceImageCreator.getStripe(baseSize, colorScheme
+ .getUltraLightColor());
+ if (isRotated) {
+ result = SubstanceImageCreator.getRotated(result, 1);
+ }
+ SubstanceProgressBarUI.stripeMap.put(key, result);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the background of a determinate progress bar.
+ *
+ * @param bar
+ * Progress bar.
+ * @param width
+ * Progress bar width.
+ * @param height
+ * Progress bar height.
+ * @param scheme
+ * Color scheme for the background.
+ * @param gp
+ * Gradient painter.
+ * @param orientation
+ * Progress bar orientation (vertical / horizontal).
+ * @param componentOrientation
+ * Progress bar LTR / RTL orientation.
+ * @return Background image.
+ */
+ private static BufferedImage getDeterminateBackground(JProgressBar bar,
+ int width, int height, SubstanceColorScheme scheme,
+ SubstanceFillPainter gp, int orientation,
+ ComponentOrientation componentOrientation) {
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
+ scheme.getDisplayName(), gp.getDisplayName(), orientation,
+ componentOrientation);
+ BufferedImage result = SubstanceProgressBarUI.backgroundMap.get(key);
+ if (result == null) {
+ result = SubstanceCoreUtilities.getBlankImage(width, height);
+ Graphics2D g2d = result.createGraphics();
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(width,
+ height, 0, null);
+ gp.paintContourBackground(g2d, bar, width, height, contour, false,
+ scheme, true);
+ g2d.dispose();
+
+ if (orientation == SwingConstants.VERTICAL) {
+ if (componentOrientation.isLeftToRight())
+ result = SubstanceImageCreator.getRotated(result, 3);
+ else
+ result = SubstanceImageCreator.getRotated(result, 1);
+ }
+ SubstanceProgressBarUI.backgroundMap.put(key, result);
+ }
+ return result;
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicProgressBarUI#paintDeterminate(java.awt.Graphics
+ * , javax.swing.JComponent)
+ */
+ @Override
+ public void paintDeterminate(Graphics g, JComponent c) {
+ if (!(g instanceof Graphics2D)) {
+ return;
+ }
+
+ ComponentState fillState = getFillState();
+ ComponentState progressState = getProgressState();
+
+ // final Insets insets = this.getInsets();
+ // insets.top /= 2;
+ // insets.left /= 2;
+ // insets.bottom /= 2;
+ // insets.right /= 2;
+ int barRectWidth = progressBar.getWidth() - 2 * margin;
+ int barRectHeight = progressBar.getHeight() - 2 * margin;
+
+ // amount of progress to draw
+ int amountFull = getAmountFull(new Insets(margin, margin, margin,
+ margin), barRectWidth, barRectHeight);
+
+ // System.out.println("@" + progressBar.hashCode() + " - display="
+ // + this.displayedValue + ", amount=" + amountFull);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ // install state-aware alpha channel (support for skins
+ // that use translucency on disabled states).
+ float stateAlpha = SubstanceColorSchemeUtilities.getAlpha(progressBar,
+ fillState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(progressBar,
+ stateAlpha, g));
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(progressBar, fillState);
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(progressBar);
+ if (progressBar.getOrientation() == SwingConstants.HORIZONTAL) {
+ BufferedImage back = getDeterminateBackground(progressBar,
+ barRectWidth + 1, barRectHeight + 1, fillScheme,
+ fillPainter, progressBar.getOrientation(), this.progressBar
+ .getComponentOrientation());
+ g2d.drawImage(back, margin, margin, null);
+ } else {
+ BufferedImage back = getDeterminateBackground(progressBar,
+ barRectHeight + 1, barRectWidth + 1, fillScheme,
+ fillPainter, progressBar.getOrientation(), this.progressBar
+ .getComponentOrientation());
+ g2d.drawImage(back, margin, margin, null);
+ }
+
+ if (amountFull > 0) {
+ int borderDelta = 0;
+
+ SubstanceColorScheme fillColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(progressBar, progressState);
+ if (progressBar.getOrientation() == SwingConstants.HORIZONTAL) {
+ int barWidth = amountFull - 2 * borderDelta;
+ int barHeight = barRectHeight - 2 * borderDelta;
+ if ((barWidth > 0) && (barHeight > 0)) {
+ if (progressBar.getComponentOrientation().isLeftToRight()) {
+ SubstanceImageCreator.paintRectangularBackground(
+ progressBar, g, margin + borderDelta, margin
+ + borderDelta, barWidth, barHeight,
+ fillColorScheme, 0.6f, false);
+
+ // g.setColor(Color.red);
+ // g.fillRect(insets.left + borderDelta,
+ // insets.top + borderDelta, barWidth, barHeight);
+ } else {
+ // fix for RTL determinate horizontal progress
+ // bar in 2.3
+ SubstanceImageCreator.paintRectangularBackground(
+ progressBar, g, margin + barRectWidth
+ - amountFull - 2 * borderDelta, margin
+ + borderDelta, barWidth, barHeight,
+ fillColorScheme, 0.6f, false);
+ }
+ }
+ } else { // VERTICAL
+ int barWidth = amountFull - 2 * borderDelta;
+ int barHeight = barRectWidth - 2 * borderDelta;
+ if ((amountFull > 0) && (barHeight > 0)) {
+ // fix for issue 95. Vertical bar is growing from
+ // the bottom
+ SubstanceImageCreator.paintRectangularBackground(
+ progressBar, g, margin + borderDelta, margin
+ + barRectHeight - barWidth - borderDelta,
+ barHeight, barWidth, fillColorScheme, 0.6f, true);
+ }
+ }
+ }
+
+ // Deal with possible text painting
+ if (progressBar.isStringPainted()) {
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(progressBar,
+ 1.0f, g));
+ this.paintString(g2d, margin, margin, barRectWidth, barRectHeight,
+ amountFull, new Insets(margin, margin, margin, margin));
+ }
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicProgressBarUI#getSelectionBackground()
+ */
+ @Override
+ protected Color getSelectionBackground() {
+ ComponentState fillState = getFillState();
+
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(progressBar, fillState);
+ return SubstanceColorUtilities.getForegroundColor(scheme);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicProgressBarUI#getSelectionForeground()
+ */
+ @Override
+ protected Color getSelectionForeground() {
+ ComponentState progressState = getProgressState();
+
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(progressBar, progressState);
+ return SubstanceColorUtilities.getForegroundColor(scheme);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicProgressBarUI#paintIndeterminate(java.awt
+ * .Graphics, javax.swing.JComponent)
+ */
+ @Override
+ public void paintIndeterminate(Graphics g, JComponent c) {
+ if (!(g instanceof Graphics2D)) {
+ return;
+ }
+
+ ComponentState progressState = getProgressState();
+
+ // final Insets b = this.getInsets(); // area for border
+ final int barRectWidth = progressBar.getWidth() - 2 * margin;
+ final int barRectHeight = progressBar.getHeight() - 2 * margin;
+
+ int valComplete = 0;
+ if (progressBar.getOrientation() == SwingConstants.HORIZONTAL) {
+ valComplete = (int) (this.animationPosition * (2 * barRectHeight + 1));
+ } else {
+ valComplete = (int) (this.animationPosition * (2 * barRectWidth + 1));
+ }
+
+ // final int valComplete = (int) animationIndex;
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ // install state-aware alpha channel (support for skins
+ // that use translucency on disabled states).
+ float stateAlpha = SubstanceColorSchemeUtilities.getAlpha(progressBar,
+ progressState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(progressBar,
+ stateAlpha, g));
+
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(progressBar, progressState);
+ if (progressBar.getOrientation() == SwingConstants.HORIZONTAL) {
+ SubstanceImageCreator.paintRectangularStripedBackground(
+ progressBar, g2d, margin, margin, barRectWidth,
+ barRectHeight, scheme, SubstanceProgressBarUI.getStripe(
+ barRectHeight, false, scheme), valComplete, 0.6f,
+ false);
+ } else {
+ // fix for issue 95. Vertical progress bar grows from the
+ // bottom.
+ SubstanceImageCreator.paintRectangularStripedBackground(
+ progressBar, g2d, margin, margin, barRectWidth,
+ barRectHeight, scheme, SubstanceProgressBarUI.getStripe(
+ barRectWidth, true, scheme), 2 * barRectWidth
+ - valComplete, 0.6f, true);
+ }
+
+ // Deal with possible text painting
+ if (progressBar.isStringPainted()) {
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(progressBar,
+ 1.0f, g));
+ this.paintString(g2d, margin, margin, barRectWidth, barRectHeight,
+ barRectWidth, new Insets(margin, margin, margin, margin));
+ }
+ g2d.dispose();
+ }
+
+ private ComponentState getFillState() {
+ return progressBar.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED;
+ }
+
+ private ComponentState getProgressState() {
+ if (progressBar.isIndeterminate()) {
+ return progressBar.isEnabled() ? INDETERMINATE_SELECTED
+ : INDETERMINATE_SELECTED_DISABLED;
+ } else {
+ return progressBar.isEnabled() ? DETERMINATE_SELECTED
+ : DETERMINATE_SELECTED_DISABLED;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicProgressBarUI#getBox(java.awt.Rectangle)
+ */
+ @Override
+ protected Rectangle getBox(Rectangle r) {
+ // Insets b = this.getInsets(); // area for border
+ int barRectWidth = progressBar.getWidth() - 2 * margin;
+ int barRectHeight = progressBar.getHeight() - 2 * margin;
+ return new Rectangle(margin, margin, barRectWidth, barRectHeight);
+ }
+
+ @Override
+ protected void startAnimationTimer() {
+ this.indeterminateLoopTimeline = new Timeline(this);
+ Integer cycleDuration = UIManager.getInt("ProgressBar.cycleTime");
+ if (cycleDuration == null)
+ cycleDuration = 1000;
+ this.indeterminateLoopTimeline.setDuration(cycleDuration);
+ this.indeterminateLoopTimeline.addCallback(new TimelineCallback() {
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ if ((progressBar != null) && progressBar.isVisible())
+ progressBar.repaint();
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ if ((progressBar != null) && progressBar.isVisible())
+ progressBar.repaint();
+ }
+ });
+ this.indeterminateLoopTimeline.addPropertyToInterpolate(Timeline
+ .<Float> property("animationPosition").from(0.0f).to(1.0f)
+ .setWith(new PropertySetter<Float>() {
+ @Override
+ public void set(Object obj, String fieldName, Float value) {
+ animationPosition = value;
+ }
+ }));
+ this.indeterminateLoopTimeline.playLoop(RepeatBehavior.LOOP);
+ }
+
+ @Override
+ protected void stopAnimationTimer() {
+ this.indeterminateLoopTimeline.abort();
+ }
+
+ /**
+ * Returns the memory usage string.
+ *
+ * @return The memory usage string.
+ */
+ public static String getMemoryUsage() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("SubstanceProgressBarUI: \n");
+ sb.append("\t" + SubstanceProgressBarUI.stripeMap.size() + " stripes");
+ return sb.toString();
+ }
+
+ @Override
+ protected int getAmountFull(Insets b, int width, int height) {
+ int amountFull = 0;
+ BoundedRangeModel model = progressBar.getModel();
+
+ long span = model.getMaximum() - model.getMinimum();
+ double percentComplete = (double) (this.displayedValue - model
+ .getMinimum())
+ / (double) span;
+
+ if ((model.getMaximum() - model.getMinimum()) != 0) {
+ if (this.progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+ amountFull = (int) Math.round(width * percentComplete);
+ } else {
+ amountFull = (int) Math.round(height * percentComplete);
+ }
+ }
+ return amountFull;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicProgressBarUI#getPreferredInnerHorizontal()
+ */
+ @Override
+ protected Dimension getPreferredInnerHorizontal() {
+ int size = SubstanceSizeUtils.getComponentFontSize(this.progressBar);
+ size += 2 * SubstanceSizeUtils.getAdjustedSize(size, 1, 4, 1, false);
+ return new Dimension(146 + SubstanceSizeUtils.getAdjustedSize(size, 0,
+ 1, 10, false), size);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicProgressBarUI#getPreferredInnerVertical()
+ */
+ @Override
+ protected Dimension getPreferredInnerVertical() {
+ int size = SubstanceSizeUtils.getComponentFontSize(this.progressBar);
+ size += 2 * SubstanceSizeUtils.getAdjustedSize(size, 1, 4, 1, false);
+ return new Dimension(size, 146 + SubstanceSizeUtils.getAdjustedSize(
+ size, 0, 1, 10, false));
+ }
+
+ @Override
+ protected void paintString(Graphics g, int x, int y, int width, int height,
+ int amountFull, Insets b) {
+ if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+ if (progressBar.getComponentOrientation().isLeftToRight()) {
+ if (progressBar.isIndeterminate()) {
+ boxRect = getBox(boxRect);
+ paintString(g, x, y, width, height, boxRect.x,
+ boxRect.width, b);
+ } else {
+ paintString(g, x, y, width, height, x, amountFull, b);
+ }
+ } else {
+ paintString(g, x, y, width, height, x + width - amountFull,
+ amountFull, b);
+ }
+ } else {
+ if (progressBar.isIndeterminate()) {
+ boxRect = getBox(boxRect);
+ paintString(g, x, y, width, height, boxRect.y, boxRect.height,
+ b);
+ } else {
+ paintString(g, x, y, width, height, y + height - amountFull,
+ amountFull, b);
+ }
+ }
+ }
+
+ /**
+ * Paints the progress string.
+ *
+ * @param g
+ * Graphics used for drawing.
+ * @param x
+ * x location of bounding box
+ * @param y
+ * y location of bounding box
+ * @param width
+ * width of bounding box
+ * @param height
+ * height of bounding box
+ * @param fillStart
+ * start location, in x or y depending on orientation, of the
+ * filled portion of the progress bar.
+ * @param amountFull
+ * size of the fill region, either width or height depending upon
+ * orientation.
+ * @param b
+ * Insets of the progress bar.
+ */
+ private void paintString(Graphics g, int x, int y, int width, int height,
+ int fillStart, int amountFull, Insets b) {
+ String progressString = progressBar.getString();
+ Rectangle renderRectangle = getStringRectangle(progressString, x, y,
+ width, height);
+
+ if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+ SubstanceTextUtilities.paintText(g, this.progressBar,
+ renderRectangle, progressString, -1, progressBar.getFont(),
+ getSelectionBackground(), new Rectangle(amountFull, y,
+ progressBar.getWidth() - amountFull, height));
+ SubstanceTextUtilities.paintText(g, this.progressBar,
+ renderRectangle, progressString, -1, progressBar.getFont(),
+ getSelectionForeground(), new Rectangle(fillStart, y,
+ amountFull, height));
+ } else { // VERTICAL
+ SubstanceTextUtilities.paintVerticalText(g, this.progressBar,
+ renderRectangle, progressString, -1, progressBar.getFont(),
+ getSelectionBackground(), new Rectangle(x, y, width,
+ progressBar.getHeight() - amountFull), progressBar
+ .getComponentOrientation().isLeftToRight());
+ SubstanceTextUtilities.paintVerticalText(g, this.progressBar,
+ renderRectangle, progressString, -1, progressBar.getFont(),
+ getSelectionForeground(), new Rectangle(x, fillStart,
+ width, amountFull), progressBar
+ .getComponentOrientation().isLeftToRight());
+ }
+ }
+
+ /**
+ * Returns the rectangle for the progress bar string.
+ *
+ * @param progressString
+ * Progress bar string.
+ * @param x
+ * x location of bounding box
+ * @param y
+ * y location of bounding box
+ * @param width
+ * width of bounding box
+ * @param height
+ * height of bounding box
+ * @return The rectangle for the progress bar string.
+ */
+ protected Rectangle getStringRectangle(String progressString, int x, int y,
+ int width, int height) {
+ FontMetrics fontSizer = progressBar.getFontMetrics(progressBar
+ .getFont());
+
+ int stringWidth = fontSizer.stringWidth(progressString);
+
+ if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
+ return new Rectangle(x + Math.round(width / 2 - stringWidth / 2), y
+ + (height - fontSizer.getHeight()) / 2, stringWidth,
+ fontSizer.getHeight());
+ } else {
+ return new Rectangle(x + (width - fontSizer.getHeight()) / 2, y
+ + Math.round(height / 2 - stringWidth / 2), fontSizer
+ .getHeight(), stringWidth);
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceRadioButtonMenuItemUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceRadioButtonMenuItemUI.java
new file mode 100644
index 0000000..26c6109
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceRadioButtonMenuItemUI.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicRadioButtonMenuItemUI;
+
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.icon.RadioButtonMenuItemIcon;
+import org.pushingpixels.substance.internal.utils.menu.MenuUtilities;
+import org.pushingpixels.substance.internal.utils.menu.SubstanceMenu;
+import org.pushingpixels.substance.internal.utils.menu.MenuUtilities.MenuPropertyListener;
+
+/**
+ * UI for radio button menu items in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRadioButtonMenuItemUI extends BasicRadioButtonMenuItemUI
+ implements SubstanceMenu, TransitionAwareUI {
+ /**
+ * Rollover listener.
+ */
+ protected RolloverMenuItemListener substanceRolloverListener;
+
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Property change listener. Listens on changes to
+ * {@link AbstractButton#MODEL_CHANGED_PROPERTY} property.
+ */
+ protected PropertyChangeListener substancePropertyListener;
+
+ /**
+ * Listens on all changes to the underlying menu item.
+ */
+ protected MenuPropertyListener substanceMenuPropertyListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ JRadioButtonMenuItem item = (JRadioButtonMenuItem) comp;
+ item.setRolloverEnabled(true);
+
+ return new SubstanceRadioButtonMenuItemUI((JRadioButtonMenuItem) comp);
+ }
+
+ public SubstanceRadioButtonMenuItemUI(JRadioButtonMenuItem menuItem) {
+ this.stateTransitionTracker = new StateTransitionTracker(menuItem,
+ menuItem.getModel());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuItemUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ // Improving performance on big menus.
+ this.substanceMenuPropertyListener = new MenuPropertyListener(
+ this.menuItem);
+ this.substanceMenuPropertyListener.install();
+
+ // fix for defect 109 - storing reference to rollover listener
+ this.substanceRolloverListener = new RolloverMenuItemListener(
+ this.menuItem, this.stateTransitionTracker);
+ this.menuItem.addMouseListener(this.substanceRolloverListener);
+
+ this.stateTransitionTracker.registerModelListeners();
+
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (AbstractButton.MODEL_CHANGED_PROPERTY.equals(evt
+ .getPropertyName())) {
+ stateTransitionTracker.setModel((ButtonModel) evt
+ .getNewValue());
+ }
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (menuItem != null) {
+ menuItem.updateUI();
+ }
+ }
+ });
+ }
+ }
+ };
+ this.menuItem.addPropertyChangeListener(this.substancePropertyListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuItemUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ super.uninstallListeners();
+
+ // Improving performance on big menus.
+ this.substanceMenuPropertyListener.uninstall();
+ this.substanceMenuPropertyListener = null;
+
+ // fix for defect 109 - unregistering rollover listener
+ this.menuItem.removeMouseListener(this.substanceRolloverListener);
+ this.substanceRolloverListener = null;
+
+ this.menuItem
+ .removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+
+ this.stateTransitionTracker.unregisterModelListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicMenuItemUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ if (this.checkIcon == null || this.checkIcon instanceof UIResource) {
+ this.checkIcon = new RadioButtonMenuItemIcon(this.menuItem,
+ SubstanceSizeUtils.getMenuCheckMarkSize(SubstanceSizeUtils
+ .getComponentFontSize(this.menuItem)));
+ }
+ this.defaultTextIconGap = SubstanceSizeUtils
+ .getTextIconGap(SubstanceSizeUtils
+ .getComponentFontSize(this.menuItem));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getAssociatedMenuItem()
+ */
+ @Override
+ public JMenuItem getAssociatedMenuItem() {
+ return this.menuItem;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getAcceleratorFont()
+ */
+ @Override
+ public Font getAcceleratorFont() {
+ return this.acceleratorFont;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getArrowIcon()
+ */
+ @Override
+ public Icon getArrowIcon() {
+ return this.arrowIcon;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getCheckIcon()
+ */
+ @Override
+ public Icon getCheckIcon() {
+ return this.checkIcon;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.SubstanceMenu#getDefaultTextIconGap()
+ */
+ @Override
+ public int getDefaultTextIconGap() {
+ return this.defaultTextIconGap;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicMenuItemUI#getPreferredMenuItemSize(javax
+ * .swing.JComponent, javax.swing.Icon, javax.swing.Icon, int)
+ */
+ @Override
+ protected Dimension getPreferredMenuItemSize(JComponent c, Icon checkIcon,
+ Icon arrowIcon, int defaultTextIconGap) {
+ Dimension superDim = super.getPreferredMenuItemSize(c, checkIcon,
+ arrowIcon, defaultTextIconGap);
+
+ return new Dimension(MenuUtilities.getPreferredWidth(menuItem),
+ superDim.height);
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return this.menuItem.getBounds().contains(me.getX(), me.getY());
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicMenuItemUI#paintMenuItem(java.awt.Graphics,
+ * javax.swing.JComponent, javax.swing.Icon, javax.swing.Icon,
+ * java.awt.Color, java.awt.Color, int)
+ */
+ @Override
+ protected void paintMenuItem(Graphics g, JComponent c, Icon checkIcon,
+ Icon arrowIcon, Color background, Color foreground,
+ int defaultTextIconGap) {
+ MenuUtilities.paintMenuItem(g, menuItem, checkIcon, arrowIcon,
+ defaultTextIconGap);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceRadioButtonUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceRadioButtonUI.java
new file mode 100644
index 0000000..238c2c8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceRadioButtonUI.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.*;
+import javax.swing.text.View;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * UI for radio buttons in <b>Substance </b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceRadioButtonUI extends BasicRadioButtonUI implements
+ TransitionAwareUI {
+ /**
+ * Property change listener. Listens on changes to
+ * {@link AbstractButton#MODEL_CHANGED_PROPERTY} property.
+ */
+ protected PropertyChangeListener substancePropertyListener;
+
+ /**
+ * Associated toggle button.
+ */
+ protected JToggleButton button;
+
+ /**
+ * Icons for all component states
+ */
+ private static LazyResettableHashMap<Icon> icons = new LazyResettableHashMap<Icon>(
+ "SubstanceRadioButtonUI");
+
+ protected StateTransitionTracker stateTransitionTracker;
+
+ private Rectangle viewRect = new Rectangle();
+
+ private Rectangle iconRect = new Rectangle();
+
+ private Rectangle textRect = new Rectangle();
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicButtonUI#installListeners(javax.swing.
+ * AbstractButton)
+ */
+ @Override
+ protected void installListeners(final AbstractButton b) {
+ super.installListeners(b);
+
+ this.stateTransitionTracker.registerModelListeners();
+ this.stateTransitionTracker.registerFocusListeners();
+
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (AbstractButton.MODEL_CHANGED_PROPERTY.equals(evt
+ .getPropertyName())) {
+ stateTransitionTracker.setModel((ButtonModel) evt
+ .getNewValue());
+ }
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ b.updateUI();
+ }
+ });
+ }
+ }
+ };
+ b.addPropertyChangeListener(substancePropertyListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicRadioButtonUI#installDefaults(javax.swing
+ * .AbstractButton)
+ */
+ @Override
+ protected void installDefaults(AbstractButton b) {
+ super.installDefaults(b);
+ Border border = b.getBorder();
+ if (border == null || border instanceof UIResource) {
+ b.setBorder(SubstanceSizeUtils.getRadioButtonBorder(
+ SubstanceSizeUtils.getComponentFontSize(b), b
+ .getComponentOrientation().isLeftToRight()));
+ }
+
+ button.setRolloverEnabled(true);
+
+ LookAndFeel.installProperty(b, "iconTextGap", SubstanceSizeUtils
+ .getTextIconGap(SubstanceSizeUtils.getComponentFontSize(b)));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicButtonUI#uninstallListeners(javax.swing.
+ * AbstractButton)
+ */
+ @Override
+ protected void uninstallListeners(AbstractButton b) {
+ b.removePropertyChangeListener(substancePropertyListener);
+ substancePropertyListener = null;
+
+ this.stateTransitionTracker.unregisterModelListeners();
+ this.stateTransitionTracker.unregisterFocusListeners();
+
+ super.uninstallListeners(b);
+ }
+
+ /**
+ * Returns the icon that matches the current and previous states of the
+ * radio button.
+ *
+ * @param button
+ * Button (should be {@link JRadioButton}).
+ * @param stateTransitionTracker
+ * state of the checkbox.
+ * @return Matching icon.
+ */
+ private static Icon getIcon(JToggleButton button,
+ StateTransitionTracker stateTransitionTracker) {
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(button);
+ int checkMarkSize = SubstanceSizeUtils.getRadioButtonMarkSize(fontSize);
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(button);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(button);
+ ComponentState currState = modelStateInfo.getCurrModelState();
+
+ SubstanceColorScheme baseFillColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, ColorSchemeAssociationKind.FILL,
+ currState);
+ SubstanceColorScheme baseMarkColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, ColorSchemeAssociationKind.MARK,
+ currState);
+ SubstanceColorScheme baseBorderColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, ColorSchemeAssociationKind.BORDER,
+ currState);
+ float visibility = stateTransitionTracker
+ .getFacetStrength(ComponentStateFacet.SELECTION);
+
+ HashMapKey keyBase = SubstanceCoreUtilities.getHashKey(fontSize,
+ checkMarkSize, fillPainter.getDisplayName(), borderPainter
+ .getDisplayName(),
+ baseFillColorScheme.getDisplayName(), baseMarkColorScheme
+ .getDisplayName(), baseBorderColorScheme
+ .getDisplayName(), visibility);
+ Icon iconBase = icons.get(keyBase);
+ if (iconBase == null) {
+ iconBase = new ImageIcon(SubstanceImageCreator.getRadioButton(
+ button, fillPainter, borderPainter, checkMarkSize,
+ currState, 0, baseFillColorScheme, baseMarkColorScheme,
+ baseBorderColorScheme, visibility));
+ icons.put(keyBase, iconBase);
+ }
+ if (currState.isDisabled() || (activeStates.size() == 1)) {
+ return iconBase;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(iconBase
+ .getIconWidth(), iconBase.getIconHeight());
+ Graphics2D g2d = result.createGraphics();
+ // draw the base layer
+ iconBase.paintIcon(button, g2d, 0, 0);
+
+ // draw other active layers
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ // System.out.println("Painting state " + activeState + "[curr is "
+ // + currState + "] with " + activeEntry.getValue());
+ if (activeState == currState)
+ continue;
+
+ float stateContribution = activeEntry.getValue().getContribution();
+ if (stateContribution > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(stateContribution));
+ SubstanceColorScheme fillColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button,
+ ColorSchemeAssociationKind.FILL, activeState);
+ SubstanceColorScheme markColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button,
+ ColorSchemeAssociationKind.MARK, activeState);
+ SubstanceColorScheme borderColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button,
+ ColorSchemeAssociationKind.BORDER, activeState);
+
+ HashMapKey keyLayer = SubstanceCoreUtilities.getHashKey(
+ fontSize, checkMarkSize, fillPainter.getDisplayName(),
+ borderPainter.getDisplayName(), fillColorScheme
+ .getDisplayName(), markColorScheme
+ .getDisplayName(), borderColorScheme
+ .getDisplayName(), visibility);
+ Icon iconLayer = icons.get(keyLayer);
+ if (iconLayer == null) {
+ iconLayer = new ImageIcon(SubstanceImageCreator
+ .getRadioButton(button, fillPainter, borderPainter,
+ checkMarkSize, currState, 0,
+ fillColorScheme, markColorScheme,
+ borderColorScheme, visibility));
+ icons.put(keyLayer, iconLayer);
+ }
+
+ iconLayer.paintIcon(button, g2d, 0, 0);
+ }
+ }
+
+ g2d.dispose();
+ return new ImageIcon(result);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceRadioButtonUI((JToggleButton) comp);
+ }
+
+ /**
+ * Simple constructor.
+ *
+ * @param button
+ * Associated radio button.
+ */
+ public SubstanceRadioButtonUI(JToggleButton button) {
+ this.button = button;
+ button.setRolloverEnabled(true);
+ this.stateTransitionTracker = new StateTransitionTracker(this.button,
+ this.button.getModel());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicButtonUI#createButtonListener(javax.swing
+ * .AbstractButton)
+ */
+ @Override
+ protected BasicButtonListener createButtonListener(AbstractButton b) {
+ return new RolloverButtonListener(b, this.stateTransitionTracker);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicRadioButtonUI#getDefaultIcon()
+ */
+ @Override
+ public Icon getDefaultIcon() {
+ return SubstanceRadioButtonUI.getIcon(button,
+ this.stateTransitionTracker);
+ }
+
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ AbstractButton b = (AbstractButton) c;
+
+ // boolean isOpaque = b.isOpaque();
+ // b.putClientProperty(SubstanceButtonUI.LOCK_OPACITY, Boolean.TRUE);
+ // b.setOpaque(false);
+
+ if (SubstanceCoreUtilities.isOpaque(c)) {
+ BackgroundPaintingUtils.update(g, c, false);
+ }
+
+ // b.setOpaque(isOpaque);
+
+ // b.putClientProperty(SubstanceButtonUI.LOCK_OPACITY, null);
+
+ FontMetrics fm = g.getFontMetrics();
+
+ Insets i = b.getInsets();
+
+ viewRect.x = i.left;
+ viewRect.y = i.top;
+ viewRect.width = b.getWidth() - (i.right + viewRect.x);
+ viewRect.height = b.getHeight() - (i.bottom + viewRect.y);
+
+ textRect.x = textRect.y = textRect.width = textRect.height = 0;
+ iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
+
+ Font f = b.getFont();
+ g.setFont(f);
+
+ Icon icon = SubstanceCoreUtilities.getOriginalIcon(b, getDefaultIcon());
+
+ // layout the text and icon
+ String text = SwingUtilities.layoutCompoundLabel(c, fm, b.getText(),
+ icon, b.getVerticalAlignment(), b.getHorizontalAlignment(), b
+ .getVerticalTextPosition(), b
+ .getHorizontalTextPosition(), viewRect, iconRect,
+ textRect, b.getText() == null ? 0 : b.getIconTextGap());
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ if (text != null && !text.equals("")) {
+ final View v = (View) b.getClientProperty(BasicHTML.propertyKey);
+ if (v != null) {
+ v.paint(g2d, textRect);
+ } else {
+ this.paintButtonText(g2d, b, textRect, text);
+ }
+ }
+
+ // Paint the Icon
+ if (icon != null) {
+ icon.paintIcon(c, g2d, iconRect.x, iconRect.y);
+ }
+
+ if (b.isFocusPainted()) {
+ // make sure that the focus ring is not clipped
+ int focusRingPadding = SubstanceSizeUtils
+ .getFocusRingPadding(SubstanceSizeUtils
+ .getComponentFontSize(button)) / 2;
+ SubstanceCoreUtilities.paintFocus(g2d, button, button, this, null,
+ textRect, 1.0f, focusRingPadding);
+ }
+ // g2d.setColor(Color.red);
+ // g2d.draw(iconRect);
+ // g2d.draw(viewRect);
+ // g2d.draw(textRect);
+ // g2d.setColor(Color.blue);
+ // g2d.drawRect(0, 0, button.getWidth() - 1, button.getHeight() - 1);
+
+ g2d.dispose();
+ }
+
+ /**
+ * Returns memory usage string.
+ *
+ * @return Memory usage string.
+ */
+ public static String getMemoryUsage() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("SubstanceRadioButtonUI: \n");
+ sb.append("\t" + SubstanceRadioButtonUI.icons.size() + " icons");
+ return sb.toString();
+ }
+
+ /**
+ * Paints the text.
+ *
+ * @param g
+ * Graphic context
+ * @param button
+ * Button
+ * @param textRect
+ * Text rectangle
+ * @param text
+ * Text to paint
+ */
+ protected void paintButtonText(Graphics g, AbstractButton button,
+ Rectangle textRect, String text) {
+ SubstanceTextUtilities.paintText(g, button, textRect, text, button
+ .getDisplayedMnemonicIndex());
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return true;
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceRootPaneUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceRootPaneUI.java
new file mode 100755
index 0000000..6644a65
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceRootPaneUI.java
@@ -0,0 +1,1728 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Cursor;
+import java.awt.Dialog;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.HeadlessException;
+import java.awt.Insets;
+import java.awt.LayoutManager;
+import java.awt.LayoutManager2;
+import java.awt.MouseInfo;
+import java.awt.Point;
+import java.awt.PointerInfo;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.awt.geom.RoundRectangle2D;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JInternalFrame;
+import javax.swing.JLayeredPane;
+import javax.swing.JRootPane;
+import javax.swing.LookAndFeel;
+import javax.swing.RootPaneContainer;
+import javax.swing.SwingUtilities;
+import javax.swing.event.MouseInputAdapter;
+import javax.swing.event.MouseInputListener;
+import javax.swing.plaf.ColorUIResource;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicRootPaneUI;
+
+import com.sun.awt.AWTUtilities;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.internal.animation.RootPaneDefaultButtonTracker;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.MemoryAnalyzer;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceTitlePane;
+
+/**
+ * UI for root panes in <b>Substance </b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ * @author Larry Salibra (fix for defect 198)
+ */
+public class SubstanceRootPaneUI extends BasicRootPaneUI {
+ private enum CursorState {
+ EXITED, ENTERED, NIL
+ }
+
+ /**
+ * The amount of space (in pixels) that the cursor is changed on.
+ */
+ private static final int CORNER_DRAG_WIDTH = 16;
+
+ /**
+ * Region from edges that dragging is active from.
+ */
+ private static final int BORDER_DRAG_THICKNESS = 5;
+
+ /**
+ * Window the <code>JRootPane</code> is in.
+ */
+ private Window window;
+
+ /**
+ * <code>JComponent</code> providing window decorations. This will be null
+ * if not providing window decorations.
+ */
+ private JComponent titlePane;
+
+ /**
+ * <code>MouseInputListener</code> that is added to the parent
+ * <code>Window</code> the <code>JRootPane</code> is contained in.
+ */
+ private MouseInputListener substanceMouseInputListener;
+
+ /**
+ * Mouse listener on the title pane (dragging).
+ */
+ private MouseInputListener substanceTitleMouseInputListener;
+
+ /**
+ * The <code>LayoutManager</code> that is set on the <code>JRootPane</code>.
+ */
+ private LayoutManager layoutManager;
+
+ /**
+ * <code>LayoutManager</code> of the <code>JRootPane</code> before we
+ * replaced it.
+ */
+ private LayoutManager savedOldLayout;
+
+ /**
+ * <code>JRootPane</code> providing the look and feel for.
+ */
+ protected JRootPane root;
+
+ /**
+ * Window listener that stops all Substance thread when the last frame is
+ * disposed.
+ */
+ protected WindowListener substanceWindowListener;
+
+ /**
+ * The current window.
+ */
+ protected Window substanceCurrentWindow;
+
+ /**
+ * Hierarchy listener to keep track of the associated top-level window.
+ */
+ protected HierarchyListener substanceHierarchyListener;
+
+ /**
+ * Component listener to keep track of the primary graphics configuration
+ * (for recomputing the maximized bounds) - fix for defect 213.
+ */
+ protected ComponentListener substanceWindowComponentListener;
+
+ /**
+ * The graphics configuration that contains the top-left corner of the
+ * window (fix for defect 213).
+ */
+ protected GraphicsConfiguration currentRootPaneGC;
+
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * <code>Cursor</code> used to track the cursor set by the user. This is
+ * initially <code>Cursor.DEFAULT_CURSOR</code>.
+ */
+ private Cursor lastCursor = Cursor
+ .getPredefinedCursor(Cursor.DEFAULT_CURSOR);
+
+ /**
+ * Optimization to speed up the
+ * {@link SubstanceCoreUtilities#getSkin(Component)} implementation.
+ */
+ private static int rootPanesWithCustomSkin = 0;
+
+ /**
+ * Creates a UI for a <code>JRootPane</code>.
+ *
+ * @param comp
+ * the JRootPane the RootPaneUI will be created for
+ * @return the RootPaneUI implementation for the passed in JRootPane
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceRootPaneUI();
+ }
+
+ /**
+ * Invokes supers implementation of <code>installUI</code> to install the
+ * necessary state onto the passed in <code>JRootPane</code> to render the
+ * metal look and feel implementation of <code>RootPaneUI</code>. If the
+ * <code>windowDecorationStyle</code> property of the <code>JRootPane</code>
+ * is other than <code>JRootPane.NONE</code>, this will add a custom
+ * <code>Component</code> to render the widgets to <code>JRootPane</code>,
+ * as well as installing a custom <code>Border</code> and
+ * <code>LayoutManager</code> on the <code>JRootPane</code>.
+ *
+ * @param c
+ * the JRootPane to install state onto
+ */
+ @Override
+ public void installUI(JComponent c) {
+ super.installUI(c);
+ if (SubstanceCoreUtilities.isRoundedCorners(c)) {
+ c.addHierarchyListener(RESIZE_LOADER);
+ }
+ this.root = (JRootPane) c;
+ int style = this.root.getWindowDecorationStyle();
+ if (style != JRootPane.NONE) {
+ this.installClientDecorations(this.root);
+ }
+
+ if (SubstanceCoreUtilities.isRootPaneModified(this.root)) {
+ propagateModificationState();
+ }
+
+ if (this.root.getClientProperty(SubstanceLookAndFeel.SKIN_PROPERTY) instanceof SubstanceSkin) {
+ rootPanesWithCustomSkin++;
+ }
+ }
+
+ /**
+ * Invokes supers implementation to uninstall any of its state. This will
+ * also reset the <code>LayoutManager</code> of the <code>JRootPane</code>.
+ * If a <code>Component</code> has been added to the <code>JRootPane</code>
+ * to render the window decoration style, this method will remove it.
+ * Similarly, this will revert the Border and LayoutManager of the
+ * <code>JRootPane</code> to what it was before <code>installUI</code> was
+ * invoked.
+ *
+ * @param c
+ * the JRootPane to uninstall state from
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ super.uninstallUI(c);
+ this.uninstallClientDecorations(this.root);
+
+ this.layoutManager = null;
+ this.substanceMouseInputListener = null;
+
+ if (this.root.getClientProperty(SubstanceLookAndFeel.SKIN_PROPERTY) instanceof SubstanceSkin) {
+ rootPanesWithCustomSkin--;
+ }
+
+ this.root = null;
+ }
+
+ /**
+ * Installs the appropriate <code>Border</code> onto the
+ * <code>JRootPane</code>.
+ *
+ * @param root
+ * Root pane.
+ */
+ public void installBorder(JRootPane root) {
+ int style = root.getWindowDecorationStyle();
+
+ if (style == JRootPane.NONE) {
+ LookAndFeel.uninstallBorder(root);
+ } else {
+ LookAndFeel.installBorder(root, "RootPane.border");
+ }
+ }
+
+ /**
+ * Removes any border that may have been installed.
+ *
+ * @param root
+ * Root pane.
+ */
+ private void uninstallBorder(JRootPane root) {
+ LookAndFeel.uninstallBorder(root);
+ }
+
+ @Override
+ protected void installDefaults(JRootPane c) {
+ super.installDefaults(c);
+ // support for per-window skins
+ Color backgr = c.getBackground();
+ if ((backgr == null) || (backgr instanceof UIResource)) {
+ Color backgroundFillColor = SubstanceColorUtilities
+ .getBackgroundFillColor(c);
+ // fix for issue 244 - set the root pane BG color
+ if (backgroundFillColor != null) {
+ c.setBackground(new ColorUIResource(backgroundFillColor));
+ }
+ }
+ }
+
+ @Override
+ public void update(Graphics g, JComponent c) {
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return;
+
+ // fix for issue 244 - paint the entire root pane so that it
+ // picks the correct watermark
+ if (SubstanceCoreUtilities.isOpaque(c)) {
+ BackgroundPaintingUtils.update(g, c, false);
+ }
+ super.paint(g, c);
+ }
+
+ /**
+ * Installs the necessary Listeners on the parent <code>Window</code>, if
+ * there is one.
+ * <p>
+ * This takes the parent so that cleanup can be done from
+ * <code>removeNotify</code>, at which point the parent hasn't been reset
+ * yet.
+ *
+ *
+ * @param root
+ * Root pane.
+ * @param parent
+ * The parent of the JRootPane
+ */
+ private void installWindowListeners(JRootPane root, Component parent) {
+ if (parent instanceof Window) {
+ this.window = (Window) parent;
+ } else {
+ this.window = SwingUtilities.getWindowAncestor(parent);
+ }
+ // System.out.println(titlePanes.size() + " entries in map after adding
+ // "
+ // + window.getClass().getName() + ":" + window.hashCode());
+ // for (Iterator<Map.Entry<JComponent, WeakReference<Window>>> it =
+ // titlePanes
+ // .entrySet().iterator(); it.hasNext();) {
+ // Map.Entry<JComponent, WeakReference<Window>> entry = it
+ // .next();
+ // Window w = entry.getValue().get();
+ // System.out.println("\t" + w.getClass().getName()
+ // + ":" + w.hashCode());
+ // }
+
+ if (this.window != null) {
+ if (this.substanceMouseInputListener == null) {
+ this.substanceMouseInputListener = this
+ .createWindowMouseInputListener(root);
+ }
+ this.window.addMouseListener(this.substanceMouseInputListener);
+ this.window
+ .addMouseMotionListener(this.substanceMouseInputListener);
+
+ if (this.titlePane != null) {
+ if (this.substanceTitleMouseInputListener == null) {
+ this.substanceTitleMouseInputListener = new TitleMouseInputHandler();
+ }
+ this.titlePane
+ .addMouseMotionListener(this.substanceTitleMouseInputListener);
+ this.titlePane
+ .addMouseListener(this.substanceTitleMouseInputListener);
+ }
+ this.setMaximized();
+ }
+ }
+
+ /**
+ * Uninstalls the necessary Listeners on the <code>Window</code> the
+ * Listeners were last installed on.
+ *
+ * @param root
+ * Root pane.
+ */
+ private void uninstallWindowListeners(JRootPane root) {
+ if (this.window != null) {
+ this.window.removeMouseListener(this.substanceMouseInputListener);
+ this.window
+ .removeMouseMotionListener(this.substanceMouseInputListener);
+ }
+ if (this.titlePane != null) {
+ this.titlePane
+ .removeMouseListener(this.substanceTitleMouseInputListener);
+ this.titlePane
+ .removeMouseMotionListener(this.substanceTitleMouseInputListener);
+ }
+ }
+
+ /**
+ * Installs the appropriate LayoutManager on the <code>JRootPane</code> to
+ * render the window decorations.
+ *
+ * @param root
+ * Root pane.
+ */
+ private void installLayout(JRootPane root) {
+ if (this.layoutManager == null) {
+ this.layoutManager = this.createLayoutManager();
+ }
+ this.savedOldLayout = root.getLayout();
+ root.setLayout(this.layoutManager);
+ }
+
+ @Override
+ protected void installListeners(final JRootPane root) {
+ super.installListeners(root);
+
+ // System.out.println("Listeners on root " + root.hashCode());
+
+ this.substanceHierarchyListener = new HierarchyListener() {
+ @Override
+ public void hierarchyChanged(HierarchyEvent e) {
+ Component parent = root.getParent();
+ if (parent == null) {
+ // fix for defect 271 - check for null parent
+ // as early as possible
+ return;
+ }
+ // System.out.println("Root pane " + root.hashCode()
+ // + " parent : " + parent.getClass().getName());
+ if (MemoryAnalyzer.isRunning()) {
+ MemoryAnalyzer.enqueueUsage("Root pane @" + root.hashCode()
+ + "\n"
+ + SubstanceCoreUtilities.getHierarchy(parent));
+ }
+ if (parent.getClass().getName().startsWith(
+ "org.jdesktop.jdic.tray")
+ || (parent.getClass().getName().compareTo(
+ "javax.swing.Popup$HeavyWeightWindow") == 0)) {
+ // Workaround for bug 240 - using JDIC system tray
+ // menu results in an HierarchyEvent being fired right
+ // after a MouseEvent. Somehow, the
+ // EventQueue.getCurrentEvent() returns the HierarchyEvent
+ // even when the MouseEvent is being processed, resulting
+ // in zeroed modifiers set on the ActionEvent passed
+ // to the action listeners on that menu item.
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ root
+ .removeHierarchyListener(substanceHierarchyListener);
+ // System.out.println(root.hashCode() + ":"
+ // + root.getHierarchyListeners().length);
+ substanceHierarchyListener = null;
+ }
+ });
+ }
+
+ Window currWindow;
+ if (parent instanceof Window) {
+ currWindow = (Window) parent;
+ } else {
+ currWindow = SwingUtilities.getWindowAncestor(parent);
+ }
+ if (substanceWindowListener != null) {
+ substanceCurrentWindow
+ .removeWindowListener(substanceWindowListener);
+ substanceWindowListener = null;
+ }
+ if (substanceWindowComponentListener != null) {
+ substanceCurrentWindow
+ .removeComponentListener(substanceWindowComponentListener);
+ substanceWindowComponentListener = null;
+ }
+ if (currWindow != null) {
+ // fix for bug 116 - stopping threads when all frames
+ // are not displayable
+ substanceWindowListener = new WindowAdapter() {
+ @Override
+ public void windowClosed(WindowEvent e) {
+ SubstanceCoreUtilities
+ .testWindowCloseThreadingViolation(e
+ .getWindow());
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ Frame[] frames = Frame.getFrames();
+ for (Frame frame : frames) {
+ if (frame.isDisplayable())
+ return;
+ }
+ SubstanceCoreUtilities.stopThreads();
+ }
+ });
+ }
+ };
+
+ if (!(parent instanceof JInternalFrame)) {
+ currWindow.addWindowListener(substanceWindowListener);
+ }
+
+ // fix for defect 213 - maximizing frame under multiple
+ // screens shouldn't always use insets of the primary
+ // screen.
+ substanceWindowComponentListener = new ComponentAdapter() {
+ @Override
+ public void componentMoved(ComponentEvent e) {
+ this.processNewPosition();
+ }
+
+ @Override
+ public void componentResized(ComponentEvent e) {
+ this.processNewPosition();
+ }
+
+ protected void processNewPosition() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (window == null)
+ return;
+
+ if (!window.isShowing()
+ || !window.isDisplayable()) {
+ currentRootPaneGC = null;
+ return;
+ }
+
+ GraphicsEnvironment ge = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice[] gds = ge
+ .getScreenDevices();
+ if (gds.length == 1)
+ return;
+ Point midLoc = new Point(window
+ .getLocationOnScreen().x
+ + window.getWidth() / 2, window
+ .getLocationOnScreen().y
+ + window.getHeight() / 2);
+ // System.out.println("Loc : "
+ // + window.getLocationOnScreen()
+ // + ", width : "
+ // + window.getWidth()
+ // + ", mid : " + midLoc);
+ int index = 0;
+ for (GraphicsDevice gd : gds) {
+ GraphicsConfiguration gc = gd
+ .getDefaultConfiguration();
+ Rectangle bounds = gc.getBounds();
+ // System.out.println("Bounds : "
+ // + bounds);
+ if (bounds.contains(midLoc)) {
+ if (gc != currentRootPaneGC) {
+ currentRootPaneGC = gc;
+ setMaximized();
+ // System.out.println("Set");
+ }
+ break;
+ }
+ index++;
+ }
+ }
+ });
+ }
+ };
+ // fix for defect 225 - install the listener only on
+ // JFrames.
+ if (parent instanceof JFrame) {
+ currWindow
+ .addComponentListener(substanceWindowComponentListener);
+ }
+
+ SubstanceRootPaneUI.this.window = currWindow;
+ }
+ substanceCurrentWindow = currWindow;
+ }
+ };
+ root.addHierarchyListener(this.substanceHierarchyListener);
+ // System.out.println(root.hashCode() + ":"
+ // + root.getHierarchyListeners().length);
+
+ JButton defaultButton = root.getDefaultButton();
+ if (defaultButton != null) {
+ RootPaneDefaultButtonTracker.update(defaultButton);
+ }
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("defaultButton".equals(evt.getPropertyName())) {
+ JButton prev = (JButton) evt.getOldValue();
+ JButton next = (JButton) evt.getNewValue();
+
+ RootPaneDefaultButtonTracker.update(prev);
+ RootPaneDefaultButtonTracker.update(next);
+ }
+
+ if (SubstanceLookAndFeel.WINDOW_MODIFIED.equals(evt
+ .getPropertyName())) {
+ propagateModificationState();
+ }
+ }
+ };
+ root.addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicRootPaneUI#uninstallListeners(javax.swing
+ * .JRootPane)
+ */
+ @Override
+ protected void uninstallListeners(JRootPane root) {
+ // fix for bug 116 - stopping threads when all frames are
+ // not displayable
+ if (this.window != null) {
+ this.window.removeWindowListener(this.substanceWindowListener);
+ this.substanceWindowListener = null;
+ this.window
+ .removeComponentListener(this.substanceWindowComponentListener);
+ this.substanceWindowComponentListener = null;
+ }
+ root.removeHierarchyListener(this.substanceHierarchyListener);
+ this.substanceHierarchyListener = null;
+
+ root.removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ super.uninstallListeners(root);
+ }
+
+ /**
+ * Uninstalls the previously installed <code>LayoutManager</code>.
+ *
+ * @param root
+ * Root pane.
+ */
+ private void uninstallLayout(JRootPane root) {
+ if (this.savedOldLayout != null) {
+ root.setLayout(this.savedOldLayout);
+ this.savedOldLayout = null;
+ }
+ }
+
+ /**
+ * Installs the necessary state onto the JRootPane to render client
+ * decorations. This is ONLY invoked if the <code>JRootPane</code> has a
+ * decoration style other than <code>JRootPane.NONE</code>.
+ *
+ * @param root
+ * Root pane.
+ */
+ private void installClientDecorations(JRootPane root) {
+ this.installBorder(root);
+
+ JComponent titlePane = this.createTitlePane(root);
+
+ this.setTitlePane(root, titlePane);
+ this.installWindowListeners(root, root.getParent());
+ this.installLayout(root);
+ if (this.window != null) {
+ root.revalidate();
+ root.repaint();
+ }
+ }
+
+ /**
+ * Uninstalls any state that <code>installClientDecorations</code> has
+ * installed.
+ * <p>
+ * NOTE: This may be called if you haven't installed client decorations yet
+ * (ie before <code>installClientDecorations</code> has been invoked).
+ *
+ * @param root
+ * Root pane.
+ */
+ private void uninstallClientDecorations(JRootPane root) {
+ this.uninstallBorder(root);
+ this.uninstallWindowListeners(root);
+ this.setTitlePane(root, null);
+ this.uninstallLayout(root);
+ // We have to revalidate/repaint root if the style is JRootPane.NONE
+ // only. When we needs to call revalidate/repaint with other styles
+ // the installClientDecorations is always called after this method
+ // imediatly and it will cause the revalidate/repaint at the proper
+ // time.
+ int style = root.getWindowDecorationStyle();
+ if (style == JRootPane.NONE) {
+ root.repaint();
+ root.revalidate();
+ }
+ // Reset the cursor, as we may have changed it to a resize cursor
+ if (this.window != null) {
+ this.window.setCursor(Cursor
+ .getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ this.window = null;
+ }
+
+ /**
+ * Returns the <code>JComponent</code> to render the window decoration
+ * style.
+ *
+ * @param root
+ * Root pane.
+ * @return The title pane component.
+ */
+ protected JComponent createTitlePane(JRootPane root) {
+ return new SubstanceTitlePane(root, this);
+ }
+
+ /**
+ * Returns a <code>MouseListener</code> that will be added to the
+ * <code>Window</code> containing the <code>JRootPane</code>.
+ *
+ * @param root
+ * Root pane.
+ * @return Window mouse listener.
+ */
+ private MouseInputListener createWindowMouseInputListener(JRootPane root) {
+ return new MouseInputHandler();
+ }
+
+ /**
+ * Returns a <code>LayoutManager</code> that will be set on the
+ * <code>JRootPane</code>.
+ *
+ * @return Layout manager.
+ */
+ protected LayoutManager createLayoutManager() {
+ return new SubstanceRootLayout();
+ }
+
+ /**
+ * Sets the window title pane -- the JComponent used to provide a plaf a way
+ * to override the native operating system's window title pane with one
+ * whose look and feel are controlled by the plaf. The plaf creates and sets
+ * this value; the default is null, implying a native operating system
+ * window title pane.
+ *
+ * @param root
+ * Root pane
+ * @param titlePane
+ * The <code>JComponent</code> to use for the window title pane.
+ */
+ private void setTitlePane(JRootPane root, JComponent titlePane) {
+ JLayeredPane layeredPane = root.getLayeredPane();
+ JComponent oldTitlePane = this.getTitlePane();
+
+ if (oldTitlePane != null) {
+ // fix for defect 109 - memory leak on skin change
+ if (oldTitlePane instanceof SubstanceTitlePane)
+ ((SubstanceTitlePane) oldTitlePane).uninstall();
+ // oldTitlePane.setVisible(false);
+ layeredPane.remove(oldTitlePane);
+ }
+ if (titlePane != null) {
+ layeredPane.add(titlePane, JLayeredPane.FRAME_CONTENT_LAYER);
+ titlePane.setVisible(true);
+ }
+ this.titlePane = titlePane;
+ }
+
+ /**
+ * Sets maximized bounds according to the display screen insets.
+ */
+ public void setMaximized() {
+ Component tla = this.root.getTopLevelAncestor();
+ // fix for defect 213 - maximizing frame under multiple
+ // screens shouldn't always use insets of the primary
+ // screen.
+ GraphicsConfiguration gc = (currentRootPaneGC != null) ? currentRootPaneGC
+ : tla.getGraphicsConfiguration();
+ Rectangle screenBounds = gc.getBounds();
+ screenBounds.x = 0;
+ screenBounds.y = 0;
+ Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
+ Rectangle maxBounds = new Rectangle(
+ (screenBounds.x + screenInsets.left),
+ (screenBounds.y + screenInsets.top), screenBounds.width
+ - ((screenInsets.left + screenInsets.right)),
+ screenBounds.height
+ - ((screenInsets.top + screenInsets.bottom)));
+ if (tla instanceof JFrame)
+ ((JFrame) tla).setMaximizedBounds(maxBounds);
+ if (MemoryAnalyzer.isRunning()) {
+ MemoryAnalyzer.enqueueUsage("Frame set to bounds " + maxBounds);
+ }
+ }
+
+ /**
+ * Returns the <code>JComponent</code> rendering the title pane. If this
+ * returns null, it implies there is no need to render window decorations.
+ * This method is <b>for internal use only</b>.
+ *
+ * @see #setTitlePane(javax.swing.JRootPane, javax.swing.JComponent)
+ * @return Title pane.
+ */
+ public JComponent getTitlePane() {
+ return this.titlePane;
+ }
+
+ /**
+ * Returns the <code>JRootPane</code> we're providing the look and feel for.
+ *
+ * @return The associated root pane.
+ */
+ protected JRootPane getRootPane() {
+ return this.root;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicRootPaneUI#propertyChange(java.beans.
+ * PropertyChangeEvent)
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent e) {
+ super.propertyChange(e);
+
+ String propertyName = e.getPropertyName();
+ if (propertyName == null) {
+ return;
+ }
+
+ if (propertyName.equals("windowDecorationStyle")) {
+ JRootPane root = (JRootPane) e.getSource();
+ int style = root.getWindowDecorationStyle();
+
+ this.uninstallClientDecorations(root);
+ if (style != JRootPane.NONE) {
+ this.installClientDecorations(root);
+ }
+ }
+ if (propertyName.equals("ancestor")) {
+ this.uninstallWindowListeners(this.root);
+ if (((JRootPane) e.getSource()).getWindowDecorationStyle() != JRootPane.NONE) {
+ this.installWindowListeners(this.root, this.root.getParent());
+ }
+ }
+ if (propertyName.equals("background")) {
+ SubstanceLookAndFeel.getTitlePaneComponent(window).setBackground(
+ (Color) e.getNewValue());
+ }
+
+ if (propertyName.equals(SubstanceLookAndFeel.SKIN_PROPERTY)) {
+ SubstanceSkin oldValue = (SubstanceSkin) e.getOldValue();
+ SubstanceSkin newValue = (SubstanceSkin) e.getNewValue();
+ if ((oldValue == null) && (newValue != null)) {
+ rootPanesWithCustomSkin++;
+ }
+ if ((oldValue != null) && (newValue == null)) {
+ rootPanesWithCustomSkin--;
+ }
+ }
+ }
+
+ /**
+ * A custom layout manager that is responsible for the layout of
+ * layeredPane, glassPane, menuBar and titlePane, if one has been installed.
+ */
+ protected class SubstanceRootLayout implements LayoutManager2 {
+ /**
+ * Returns the amount of space the layout would like to have.
+ *
+ *
+ * aram the Container for which this layout manager is being used
+ *
+ * @return a Dimension object containing the layout's preferred size
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ Dimension cpd, mbd, tpd;
+ int cpWidth = 0;
+ int cpHeight = 0;
+ int mbWidth = 0;
+ int mbHeight = 0;
+ int tpWidth = 0;
+ int tpHeight = 0;
+ Insets i = parent.getInsets();
+ JRootPane root = (JRootPane) parent;
+
+ if (root.getContentPane() != null) {
+ cpd = root.getContentPane().getPreferredSize();
+ } else {
+ cpd = root.getSize();
+ }
+ if (cpd != null) {
+ cpWidth = cpd.width;
+ cpHeight = cpd.height;
+ }
+
+ if (root.getJMenuBar() != null) {
+ mbd = root.getJMenuBar().getPreferredSize();
+ if (mbd != null) {
+ mbWidth = mbd.width;
+ mbHeight = mbd.height;
+ }
+ }
+
+ if ((root.getWindowDecorationStyle() != JRootPane.NONE)
+ && (root.getUI() instanceof SubstanceRootPaneUI)) {
+ JComponent titlePane = ((SubstanceRootPaneUI) root.getUI())
+ .getTitlePane();
+ if (titlePane != null) {
+ tpd = titlePane.getPreferredSize();
+ if (tpd != null) {
+ tpWidth = tpd.width;
+ tpHeight = tpd.height;
+ }
+ }
+ }
+
+ return new Dimension(Math.max(Math.max(cpWidth, mbWidth), tpWidth)
+ + i.left + i.right, cpHeight + mbHeight + tpHeight + i.top
+ + i.bottom);
+ }
+
+ /**
+ * Returns the minimum amount of space the layout needs.
+ *
+ *
+ * aram the Container for which this layout manager is being used
+ *
+ * @return a Dimension object containing the layout's minimum size
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ Dimension cpd, mbd, tpd;
+ int cpWidth = 0;
+ int cpHeight = 0;
+ int mbWidth = 0;
+ int mbHeight = 0;
+ int tpWidth = 0;
+ int tpHeight = 0;
+ Insets i = parent.getInsets();
+ JRootPane root = (JRootPane) parent;
+
+ if (root.getContentPane() != null) {
+ cpd = root.getContentPane().getMinimumSize();
+ } else {
+ cpd = root.getSize();
+ }
+ if (cpd != null) {
+ cpWidth = cpd.width;
+ cpHeight = cpd.height;
+ }
+
+ if (root.getJMenuBar() != null) {
+ mbd = root.getJMenuBar().getMinimumSize();
+ if (mbd != null) {
+ mbWidth = mbd.width;
+ mbHeight = mbd.height;
+ }
+ }
+ if ((root.getWindowDecorationStyle() != JRootPane.NONE)
+ && (root.getUI() instanceof SubstanceRootPaneUI)) {
+ JComponent titlePane = ((SubstanceRootPaneUI) root.getUI())
+ .getTitlePane();
+ if (titlePane != null) {
+ tpd = titlePane.getMinimumSize();
+ if (tpd != null) {
+ tpWidth = tpd.width;
+ tpHeight = tpd.height;
+ }
+ }
+ }
+
+ return new Dimension(Math.max(Math.max(cpWidth, mbWidth), tpWidth)
+ + i.left + i.right, cpHeight + mbHeight + tpHeight + i.top
+ + i.bottom);
+ }
+
+ /**
+ * Returns the maximum amount of space the layout can use.
+ *
+ *
+ * aram the Container for which this layout manager is being used
+ *
+ * @return a Dimension object containing the layout's maximum size
+ */
+ @Override
+ public Dimension maximumLayoutSize(Container target) {
+ Dimension cpd, mbd, tpd;
+ int cpWidth = Integer.MAX_VALUE;
+ int cpHeight = Integer.MAX_VALUE;
+ int mbWidth = Integer.MAX_VALUE;
+ int mbHeight = Integer.MAX_VALUE;
+ int tpWidth = Integer.MAX_VALUE;
+ int tpHeight = Integer.MAX_VALUE;
+ Insets i = target.getInsets();
+ JRootPane root = (JRootPane) target;
+
+ if (root.getContentPane() != null) {
+ cpd = root.getContentPane().getMaximumSize();
+ if (cpd != null) {
+ cpWidth = cpd.width;
+ cpHeight = cpd.height;
+ }
+ }
+
+ if (root.getJMenuBar() != null) {
+ mbd = root.getJMenuBar().getMaximumSize();
+ if (mbd != null) {
+ mbWidth = mbd.width;
+ mbHeight = mbd.height;
+ }
+ }
+
+ if ((root.getWindowDecorationStyle() != JRootPane.NONE)
+ && (root.getUI() instanceof SubstanceRootPaneUI)) {
+ JComponent titlePane = ((SubstanceRootPaneUI) root.getUI())
+ .getTitlePane();
+ if (titlePane != null) {
+ tpd = titlePane.getMaximumSize();
+ if (tpd != null) {
+ tpWidth = tpd.width;
+ tpHeight = tpd.height;
+ }
+ }
+ }
+
+ int maxHeight = Math.max(Math.max(cpHeight, mbHeight), tpHeight);
+ // Only overflows if 3 real non-MAX_VALUE heights, sum to >
+ // MAX_VALUE
+ // Only will happen if sums to more than 2 billion units. Not
+ // likely.
+ if (maxHeight != Integer.MAX_VALUE) {
+ maxHeight = cpHeight + mbHeight + tpHeight + i.top + i.bottom;
+ }
+
+ int maxWidth = Math.max(Math.max(cpWidth, mbWidth), tpWidth);
+ // Similar overflow comment as above
+ if (maxWidth != Integer.MAX_VALUE) {
+ maxWidth += i.left + i.right;
+ }
+
+ return new Dimension(maxWidth, maxHeight);
+ }
+
+ /**
+ * Instructs the layout manager to perform the layout for the specified
+ * container.
+ *
+ *
+ * aram the Container for which this layout manager is being used
+ */
+ @Override
+ public void layoutContainer(Container parent) {
+ JRootPane root = (JRootPane) parent;
+ Rectangle b = root.getBounds();
+ Insets i = root.getInsets();
+ int nextY = 0;
+ int w = b.width - i.right - i.left;
+ int h = b.height - i.top - i.bottom;
+
+ if (root.getLayeredPane() != null) {
+ root.getLayeredPane().setBounds(i.left, i.top, w, h);
+ }
+ if (root.getGlassPane() != null) {
+ root.getGlassPane().setBounds(i.left, i.top, w, h);
+ }
+ // Note: This is laying out the children in the layeredPane,
+ // technically, these are not our children.
+ if ((root.getWindowDecorationStyle() != JRootPane.NONE)
+ && (root.getUI() instanceof SubstanceRootPaneUI)) {
+ JComponent titlePane = ((SubstanceRootPaneUI) root.getUI())
+ .getTitlePane();
+ if (titlePane != null) {
+ Dimension tpd = titlePane.getPreferredSize();
+ if (tpd != null) {
+ int tpHeight = tpd.height;
+ titlePane.setBounds(0, 0, w, tpHeight);
+ nextY += tpHeight;
+ }
+ }
+ }
+ if (root.getJMenuBar() != null) {
+ Dimension mbd = root.getJMenuBar().getPreferredSize();
+ root.getJMenuBar().setBounds(0, nextY, w, mbd.height);
+ nextY += mbd.height;
+ }
+ if (root.getContentPane() != null) {
+ // Dimension cpd = root.getContentPane().getPreferredSize();
+ root.getContentPane().setBounds(0, nextY, w,
+ h < nextY ? 0 : h - nextY);
+ }
+ }
+
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public void addLayoutComponent(Component comp, Object constraints) {
+ }
+
+ @Override
+ public float getLayoutAlignmentX(Container target) {
+ return 0.0f;
+ }
+
+ @Override
+ public float getLayoutAlignmentY(Container target) {
+ return 0.0f;
+ }
+
+ @Override
+ public void invalidateLayout(Container target) {
+ }
+ }
+
+ /**
+ * Maps from positions to cursor type. Refer to calculateCorner and
+ * calculatePosition for details of this.
+ */
+ private static final int[] cursorMapping = new int[] {
+ Cursor.NW_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR,
+ Cursor.N_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR,
+ Cursor.NE_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, 0, 0, 0,
+ Cursor.NE_RESIZE_CURSOR, Cursor.W_RESIZE_CURSOR, 0, 0, 0,
+ Cursor.E_RESIZE_CURSOR, Cursor.SW_RESIZE_CURSOR, 0, 0, 0,
+ Cursor.SE_RESIZE_CURSOR, Cursor.SW_RESIZE_CURSOR,
+ Cursor.SW_RESIZE_CURSOR, Cursor.S_RESIZE_CURSOR,
+ Cursor.SE_RESIZE_CURSOR, Cursor.SE_RESIZE_CURSOR };
+
+ /**
+ * MouseInputHandler is responsible for handling resize/moving of the
+ * Window. It sets the cursor directly on the Window when then mouse moves
+ * over a hot spot.
+ */
+ private class MouseInputHandler implements MouseInputListener {
+ /**
+ * Set to true if the drag operation is moving the window.
+ */
+ private boolean isMovingWindow;
+
+ /**
+ * Used to determine the corner the resize is occuring from.
+ */
+ private int dragCursor;
+
+ /**
+ * X location the mouse went down on for a drag operation.
+ */
+ private int dragOffsetX;
+
+ /**
+ * Y location the mouse went down on for a drag operation.
+ */
+ private int dragOffsetY;
+
+ /**
+ * Width of the window when the drag started.
+ */
+ private int dragWidth;
+
+ /**
+ * Height of the window when the drag started.
+ */
+ private int dragHeight;
+
+ /**
+ * PrivilegedExceptionAction needed by mouseDragged method to obtain new
+ * location of window on screen during the drag.
+ */
+ @SuppressWarnings("unchecked")
+ private final PrivilegedExceptionAction getLocationAction = new PrivilegedExceptionAction() {
+ @Override
+ public Object run() throws HeadlessException {
+ PointerInfo pi = MouseInfo.getPointerInfo();
+ return pi == null ? null : pi.getLocation();
+ }
+ };
+
+ @Override
+ public void mousePressed(MouseEvent ev) {
+ JRootPane rootPane = SubstanceRootPaneUI.this.getRootPane();
+
+ if (rootPane.getWindowDecorationStyle() == JRootPane.NONE) {
+ return;
+ }
+ Point dragWindowOffset = ev.getPoint();
+ Window w = (Window) ev.getSource();
+ if (w != null) {
+ w.toFront();
+ }
+ Point convertedDragWindowOffset = SwingUtilities.convertPoint(w,
+ dragWindowOffset, SubstanceRootPaneUI.this.getTitlePane());
+
+ Frame f = null;
+ Dialog d = null;
+
+ if (w instanceof Frame) {
+ f = (Frame) w;
+ } else if (w instanceof Dialog) {
+ d = (Dialog) w;
+ }
+
+ int frameState = (f != null) ? f.getExtendedState() : 0;
+
+ if ((SubstanceRootPaneUI.this.getTitlePane() != null)
+ && SubstanceRootPaneUI.this.getTitlePane().contains(
+ convertedDragWindowOffset)) {
+ if ((((f != null) && ((frameState & Frame.MAXIMIZED_BOTH) == 0)) || (d != null))
+ && (dragWindowOffset.y >= SubstanceRootPaneUI.BORDER_DRAG_THICKNESS)
+ && (dragWindowOffset.x >= SubstanceRootPaneUI.BORDER_DRAG_THICKNESS)
+ && (dragWindowOffset.x < w.getWidth()
+ - SubstanceRootPaneUI.BORDER_DRAG_THICKNESS)) {
+ this.isMovingWindow = true;
+ this.dragOffsetX = dragWindowOffset.x;
+ this.dragOffsetY = dragWindowOffset.y;
+ }
+ } else if (((f != null) && f.isResizable() && ((frameState & Frame.MAXIMIZED_BOTH) == 0))
+ || ((d != null) && d.isResizable())) {
+ this.dragOffsetX = dragWindowOffset.x;
+ this.dragOffsetY = dragWindowOffset.y;
+ this.dragWidth = w.getWidth();
+ this.dragHeight = w.getHeight();
+ this.dragCursor = this.getCursor(this.calculateCorner(w,
+ dragWindowOffset.x, dragWindowOffset.y));
+ }
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent ev) {
+ if ((this.dragCursor != 0)
+ && (SubstanceRootPaneUI.this.window != null)
+ && !SubstanceRootPaneUI.this.window.isValid()) {
+ // Some Window systems validate as you resize, others won't,
+ // thus the check for validity before repainting.
+ SubstanceRootPaneUI.this.window.validate();
+ SubstanceRootPaneUI.this.getRootPane().repaint();
+ }
+ this.isMovingWindow = false;
+ this.dragCursor = 0;
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent ev) {
+ JRootPane root = SubstanceRootPaneUI.this.getRootPane();
+
+ if (root.getWindowDecorationStyle() == JRootPane.NONE) {
+ return;
+ }
+
+ Window w = (Window) ev.getSource();
+
+ Frame f = null;
+ Dialog d = null;
+
+ if (w instanceof Frame) {
+ f = (Frame) w;
+ } else if (w instanceof Dialog) {
+ d = (Dialog) w;
+ }
+
+ // Update the cursor
+ int cursor = this.getCursor(this.calculateCorner(w, ev.getX(), ev
+ .getY()));
+
+ if ((cursor != 0)
+ && (((f != null) && (f.isResizable() && ((f
+ .getExtendedState() & Frame.MAXIMIZED_BOTH) == 0))) || ((d != null) && d
+ .isResizable()))) {
+ w.setCursor(Cursor.getPredefinedCursor(cursor));
+ } else {
+ w.setCursor(SubstanceRootPaneUI.this.lastCursor);
+ }
+ }
+
+ /**
+ * Adjusts the bounds.
+ *
+ * @param bounds
+ * Original bounds.
+ * @param min
+ * Minimum dimension.
+ * @param deltaX
+ * Delta X.
+ * @param deltaY
+ * Delta Y.
+ * @param deltaWidth
+ * Delta width.
+ * @param deltaHeight
+ * Delta height.
+ */
+ private void adjust(Rectangle bounds, Dimension min, int deltaX,
+ int deltaY, int deltaWidth, int deltaHeight) {
+ bounds.x += deltaX;
+ bounds.y += deltaY;
+ bounds.width += deltaWidth;
+ bounds.height += deltaHeight;
+ if (min != null) {
+ if (bounds.width < min.width) {
+ int correction = min.width - bounds.width;
+ if (deltaX != 0) {
+ bounds.x -= correction;
+ }
+ bounds.width = min.width;
+ }
+ if (bounds.height < min.height) {
+ int correction = min.height - bounds.height;
+ if (deltaY != 0) {
+ bounds.y -= correction;
+ }
+ bounds.height = min.height;
+ }
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void mouseDragged(MouseEvent ev) {
+ Window w = (Window) ev.getSource();
+ Point pt = ev.getPoint();
+
+ if (this.isMovingWindow) {
+ Point windowPt;
+ try {
+ windowPt = (Point) AccessController
+ .doPrivileged(this.getLocationAction);
+ if (windowPt != null) {
+ windowPt.x = windowPt.x - this.dragOffsetX;
+ windowPt.y = windowPt.y - this.dragOffsetY;
+ w.setLocation(windowPt);
+ }
+ } catch (PrivilegedActionException ignored) {
+ }
+ } else if (this.dragCursor != 0) {
+ Rectangle r = w.getBounds();
+ Rectangle startBounds = new Rectangle(r);
+ Dimension min = w.getMinimumSize();
+
+ switch (this.dragCursor) {
+ case Cursor.E_RESIZE_CURSOR:
+ this.adjust(r, min, 0, 0, pt.x
+ + (this.dragWidth - this.dragOffsetX) - r.width, 0);
+ break;
+ case Cursor.S_RESIZE_CURSOR:
+ this.adjust(r, min, 0, 0, 0, pt.y
+ + (this.dragHeight - this.dragOffsetY) - r.height);
+ break;
+ case Cursor.N_RESIZE_CURSOR:
+ this.adjust(r, min, 0, pt.y - this.dragOffsetY, 0,
+ -(pt.y - this.dragOffsetY));
+ break;
+ case Cursor.W_RESIZE_CURSOR:
+ this.adjust(r, min, pt.x - this.dragOffsetX, 0,
+ -(pt.x - this.dragOffsetX), 0);
+ break;
+ case Cursor.NE_RESIZE_CURSOR:
+ this.adjust(r, min, 0, pt.y - this.dragOffsetY, pt.x
+ + (this.dragWidth - this.dragOffsetX) - r.width,
+ -(pt.y - this.dragOffsetY));
+ break;
+ case Cursor.SE_RESIZE_CURSOR:
+ this.adjust(r, min, 0, 0, pt.x
+ + (this.dragWidth - this.dragOffsetX) - r.width,
+ pt.y + (this.dragHeight - this.dragOffsetY)
+ - r.height);
+ break;
+ case Cursor.NW_RESIZE_CURSOR:
+ this.adjust(r, min, pt.x - this.dragOffsetX, pt.y
+ - this.dragOffsetY, -(pt.x - this.dragOffsetX),
+ -(pt.y - this.dragOffsetY));
+ break;
+ case Cursor.SW_RESIZE_CURSOR:
+ this.adjust(r, min, pt.x - this.dragOffsetX, 0,
+ -(pt.x - this.dragOffsetX), pt.y
+ + (this.dragHeight - this.dragOffsetY)
+ - r.height);
+ break;
+ default:
+ break;
+ }
+ if (!r.equals(startBounds)) {
+ w.setBounds(r);
+ // Defer repaint/validate on mouseReleased unless dynamic
+ // layout is active.
+ if (Toolkit.getDefaultToolkit().isDynamicLayoutActive()) {
+ w.validate();
+ SubstanceRootPaneUI.this.getRootPane().repaint();
+ }
+ }
+ }
+ }
+
+ private CursorState cursorState = CursorState.NIL;
+
+ @Override
+ public void mouseEntered(MouseEvent ev) {
+ Window w = (Window) ev.getSource();
+ if (cursorState == CursorState.EXITED
+ || cursorState == CursorState.NIL) {
+ // fix for defect 107
+ SubstanceRootPaneUI.this.lastCursor = w.getCursor();
+ }
+ cursorState = CursorState.ENTERED;
+ this.mouseMoved(ev);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent ev) {
+ Window w = (Window) ev.getSource();
+ w.setCursor(SubstanceRootPaneUI.this.lastCursor);
+ cursorState = CursorState.EXITED;
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent ev) {
+ Window w = (Window) ev.getSource();
+ Frame f;
+
+ if (w instanceof Frame) {
+ f = (Frame) w;
+ } else {
+ return;
+ }
+
+ JComponent windowTitlePane = SubstanceRootPaneUI.this
+ .getTitlePane();
+ // fix for issue 444 - ignore double clicks when the title pane
+ // is not showing (for example under JRootPane.NONE decoration
+ // style).
+ if (windowTitlePane == null)
+ return;
+
+ Point convertedPoint = SwingUtilities.convertPoint(w,
+ ev.getPoint(), windowTitlePane);
+
+ int state = f.getExtendedState();
+ if ((windowTitlePane != null)
+ && windowTitlePane.contains(convertedPoint)) {
+ if (((ev.getClickCount() % 2) == 0)
+ && ((ev.getModifiers() & InputEvent.BUTTON1_MASK) != 0)) {
+ if (f.isResizable()) {
+ if ((state & Frame.MAXIMIZED_BOTH) != 0) {
+ setMaximized();
+ f.setExtendedState(state & ~Frame.MAXIMIZED_BOTH);
+ } else {
+ setMaximized();
+ f.setExtendedState(state | Frame.MAXIMIZED_BOTH);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the corner that contains the point <code>x</code>,
+ * <code>y</code>, or -1 if the position doesn't match a corner.
+ *
+ * @param w
+ * Window.
+ * @param x
+ * X coordinate.
+ * @param y
+ * Y coordinate.
+ * @return Corner that contains the specified point.
+ */
+ private int calculateCorner(Window w, int x, int y) {
+ Insets insets = w.getInsets();
+ int xPosition = this.calculatePosition(x - insets.left, w
+ .getWidth()
+ - insets.left - insets.right);
+ int yPosition = this.calculatePosition(y - insets.top, w
+ .getHeight()
+ - insets.top - insets.bottom);
+
+ if ((xPosition == -1) || (yPosition == -1)) {
+ return -1;
+ }
+ return yPosition * 5 + xPosition;
+ }
+
+ /**
+ * Returns the Cursor to render for the specified corner. This returns 0
+ * if the corner doesn't map to a valid Cursor
+ *
+ * @param corner
+ * Corner
+ * @return Cursor to render for the specified corner.
+ */
+ private int getCursor(int corner) {
+ if (corner == -1) {
+ return 0;
+ }
+ return SubstanceRootPaneUI.cursorMapping[corner];
+ }
+
+ /**
+ * Returns an integer indicating the position of <code>spot</code> in
+ * <code>width</code>. The return value will be: 0 if <
+ * BORDER_DRAG_THICKNESS 1 if < CORNER_DRAG_WIDTH 2 if >=
+ * CORNER_DRAG_WIDTH && < width - BORDER_DRAG_THICKNESS 3 if >= width -
+ * CORNER_DRAG_WIDTH 4 if >= width - BORDER_DRAG_THICKNESS 5 otherwise
+ *
+ * @param spot
+ * Spot.
+ * @param width
+ * Width.
+ * @return The position of spot in width.
+ */
+ private int calculatePosition(int spot, int width) {
+ if (spot < SubstanceRootPaneUI.BORDER_DRAG_THICKNESS) {
+ return 0;
+ }
+ if (spot < SubstanceRootPaneUI.CORNER_DRAG_WIDTH) {
+ return 1;
+ }
+ if (spot >= (width - SubstanceRootPaneUI.BORDER_DRAG_THICKNESS)) {
+ return 4;
+ }
+ if (spot >= (width - SubstanceRootPaneUI.CORNER_DRAG_WIDTH)) {
+ return 3;
+ }
+ return 2;
+ }
+ }
+
+ /**
+ * Mouse handler on the title pane.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class TitleMouseInputHandler extends MouseInputAdapter {
+
+ /**
+ * Pointer location when the mouse was pressed for a drag relative to
+ * the upper-lefthand corner of the window.
+ */
+ private Point dragOffset = new Point(0, 0);
+
+ @Override
+ public void mousePressed(MouseEvent ev) {
+ JRootPane rootPane = SubstanceRootPaneUI.this.getRootPane();
+
+ if (rootPane.getWindowDecorationStyle() == JRootPane.NONE) {
+ return;
+ }
+
+ Point dragWindowOffset = ev.getPoint();
+ Component source = (Component) ev.getSource();
+
+ Point convertedDragWindowOffset = SwingUtilities.convertPoint(
+ source, dragWindowOffset, getTitlePane());
+
+ dragWindowOffset = SwingUtilities.convertPoint(source,
+ dragWindowOffset, SubstanceRootPaneUI.this.window);
+
+ if (getTitlePane() != null
+ && getTitlePane().contains(convertedDragWindowOffset)) {
+ if (SubstanceRootPaneUI.this.window != null) {
+ SubstanceRootPaneUI.this.window.toFront();
+ dragOffset = dragWindowOffset;
+ }
+ }
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent ev) {
+ Component source = (Component) ev.getSource();
+
+ // Point pt = SwingUtilities.convertPoint(source, ev.getPoint(),
+ // SubstanceRootPaneUI.this.window);
+
+ // fix for issue 198
+ Point eventLocationOnScreen = ev.getLocationOnScreen();
+ if (eventLocationOnScreen == null) {
+ eventLocationOnScreen = new Point(ev.getX()
+ + source.getLocationOnScreen().x, ev.getY()
+ + source.getLocationOnScreen().y);
+ }
+ // Fix for issue 192 - disable dragging maximized frame.
+ if (SubstanceRootPaneUI.this.window instanceof Frame) {
+ Frame f = (Frame) SubstanceRootPaneUI.this.window;
+ int frameState = (f != null) ? f.getExtendedState() : 0;
+ if ((f != null) && ((frameState & Frame.MAXIMIZED_BOTH) == 0)) {
+ SubstanceRootPaneUI.this.window.setLocation(
+ eventLocationOnScreen.x - dragOffset.x,
+ eventLocationOnScreen.y - dragOffset.y);
+ }
+ } else {
+ // fix for issue 193 - allow dragging decorated dialogs.
+ SubstanceRootPaneUI.this.window.setLocation(
+ eventLocationOnScreen.x - dragOffset.x,
+ eventLocationOnScreen.y - dragOffset.y);
+ }
+
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent ev) {
+ Frame f;
+
+ if (SubstanceRootPaneUI.this.window instanceof Frame) {
+ f = (Frame) SubstanceRootPaneUI.this.window;
+ } else {
+ return;
+ }
+
+ Point convertedPoint = SwingUtilities.convertPoint(
+ SubstanceRootPaneUI.this.window, ev.getPoint(),
+ SubstanceRootPaneUI.this.getTitlePane());
+
+ int state = f.getExtendedState();
+ if ((SubstanceRootPaneUI.this.getTitlePane() != null)
+ && SubstanceRootPaneUI.this.getTitlePane().contains(
+ convertedPoint)) {
+ if (((ev.getClickCount() % 2) == 0)
+ && ((ev.getModifiers() & InputEvent.BUTTON1_MASK) != 0)) {
+ if (f.isResizable()) {
+ if ((state & Frame.MAXIMIZED_BOTH) != 0) {
+ setMaximized();
+ f.setExtendedState(state & ~Frame.MAXIMIZED_BOTH);
+ } else {
+ setMaximized();
+ f.setExtendedState(state | Frame.MAXIMIZED_BOTH);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void propagateModificationState() {
+ JComponent titlePane = getTitlePane();
+ if (titlePane instanceof SubstanceTitlePane) {
+ ((SubstanceTitlePane) titlePane)
+ .getCloseButton()
+ .putClientProperty(
+ SubstanceLookAndFeel.WINDOW_MODIFIED,
+ root
+ .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
+ return;
+ }
+
+ JInternalFrame jif = (JInternalFrame) SwingUtilities
+ .getAncestorOfClass(JInternalFrame.class, this.root);
+ if (jif != null) {
+ SubstanceInternalFrameUI internalFrameUI = (SubstanceInternalFrameUI) jif
+ .getUI();
+ internalFrameUI.setWindowModified(Boolean.TRUE.equals(root
+ .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED)));
+ }
+ }
+
+ public static boolean hasCustomSkinOnAtLeastOneRootPane() {
+ return (rootPanesWithCustomSkin > 0);
+ }
+
+ public static ComponentListener WINDOW_ROUNDER = new ComponentAdapter() {
+
+ @Override
+ public void componentResized(ComponentEvent e) {
+ if (e.getComponent() instanceof Window) {
+ Window w = (Window) e.getComponent();
+ if ((w instanceof Frame && !((Frame)w).isUndecorated())
+ || (w instanceof Dialog && !((Dialog)w).isUndecorated()))
+ {
+ return; // don't touch the shape of decorated windows
+ }
+
+ if (w instanceof RootPaneContainer) {
+ JRootPane jrp = ((RootPaneContainer)w).getRootPane();
+ if ((jrp.getWindowDecorationStyle() == JRootPane.NONE)
+ || (!SubstanceCoreUtilities.isRoundedCorners(jrp)))
+ {
+ // special case, for undecorated windows and maximized windows
+ AWTUtilities.setWindowShape(w, null);
+ return;
+ }
+ }
+ try {
+ // only round the corners if the screen is reasonably sized, as in
+ // smaller than archival versions of The Godfather, which is at 4096x2160
+ if (SubstanceCoreUtilities.isRoundedCorners(w) && w.getWidth() * w.getHeight() < (4096*4096)) {
+ AWTUtilities.setWindowShape(w, new RoundRectangle2D.Double(0, 0, w.getWidth(), w.getHeight(), 12, 12));
+ } else {
+ AWTUtilities.setWindowShape(w, null);
+ }
+ } catch (OutOfMemoryError oome) {
+ AWTUtilities.setWindowShape(w, null);
+ //System.out.println("Rounded panel size on OOOME : " + w.getWidth() + "x" + w.getHeight() + " for an area of " + w.getWidth()*w.getHeight() + "px");
+ //throw oome;
+ }
+ }
+ }
+ };
+
+ // When a JRootPane is created, it hasn't been added to a window yet.
+ // when we are added, set the window to transparent if we are a JDialog or JFrame
+ static HierarchyListener RESIZE_LOADER = new HierarchyListener() {
+ @Override
+ public void hierarchyChanged(HierarchyEvent e) {
+ // if we are not talking about a rootpane being added to a window, quit and never try again
+ if (!((e.getChangedParent() instanceof Dialog) || (e.getChangedParent() instanceof Frame))) {
+ e.getChanged().removeHierarchyListener(this);
+ return;
+ }
+ // if we are something other than a change event, quit
+ if ((e.getID() != HierarchyEvent.HIERARCHY_CHANGED)
+ || !(e.getChanged() instanceof JRootPane))
+ {
+ return;
+ }
+ Window w = (Window) e.getChangedParent();
+ if (w != null) {
+ if (!Arrays.asList(w.getComponentListeners()).contains(WINDOW_ROUNDER)) {
+ w.addComponentListener(WINDOW_ROUNDER);
+ }
+ e.getChanged().removeHierarchyListener(this);
+ }
+ }
+ };
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceScrollBarUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceScrollBarUI.java
new file mode 100644
index 0000000..b8a4168
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceScrollBarUI.java
@@ -0,0 +1,2886 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.Adjustable;
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.EnumSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractButton;
+import javax.swing.BoundedRangeModel;
+import javax.swing.ButtonModel;
+import javax.swing.DefaultButtonModel;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JScrollBar;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceConstants;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind;
+import org.pushingpixels.substance.api.SubstanceConstants.Side;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.SimplisticFillPainter;
+import org.pushingpixels.substance.internal.painter.SimplisticSoftBorderPainter;
+import org.pushingpixels.substance.internal.utils.HashMapKey;
+import org.pushingpixels.substance.internal.utils.LazyResettableHashMap;
+import org.pushingpixels.substance.internal.utils.RolloverControlListener;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+import org.pushingpixels.substance.internal.utils.SubstanceOutlineUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.icon.ArrowButtonTransitionAwareIcon;
+import org.pushingpixels.substance.internal.utils.scroll.SubstanceScrollButton;
+
+/**
+ * UI for scroll bars in <b>Substance </b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceScrollBarUI extends BasicScrollBarUI implements
+ TransitionAwareUI {
+ /**
+ * The second decrease button. Is shown under
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#ADJACENT},
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE} and
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE_BOTH}
+ * modes.
+ *
+ * @since version 3.1
+ */
+ protected JButton mySecondDecreaseButton;
+
+ /**
+ * The second increase button. Is shown only under
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE_BOTH} mode.
+ *
+ * @since version 3.1
+ */
+ protected JButton mySecondIncreaseButton;
+
+ /**
+ * Surrogate button model for tracking the thumb transitions.
+ */
+ private ButtonModel thumbModel;
+
+ /**
+ * Stores computed images for vertical thumbs.
+ */
+ private static LazyResettableHashMap<BufferedImage> thumbVerticalMap = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceScrollBarUI.thumbVertical");
+
+ /**
+ * Stores computed images for horizontal thumbs.
+ */
+ private static LazyResettableHashMap<BufferedImage> thumbHorizontalMap = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceScrollBarUI.thumbHorizontal");
+
+ /**
+ * Mouse listener on the associated scroll bar.
+ */
+ private MouseListener substanceMouseListener;
+
+ /**
+ * Listener for thumb transition animations.
+ */
+ private RolloverControlListener substanceThumbRolloverListener;
+
+ protected StateTransitionTracker compositeStateTransitionTracker;
+
+ /**
+ * Property change listener.
+ *
+ */
+ private PropertyChangeListener substancePropertyListener;
+
+ /**
+ * Scroll bar width.
+ */
+ protected int scrollBarWidth;
+
+ /**
+ * Cache of images for horizontal tracks.
+ */
+ private static LazyResettableHashMap<BufferedImage> trackHorizontalMap = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceScrollBarUI.trackHorizontal");
+
+ /**
+ * Cache of images for vertical tracks.
+ */
+ private static LazyResettableHashMap<BufferedImage> trackVerticalMap = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceScrollBarUI.trackVertical");
+
+ /**
+ * Listener on adjustments made to the scrollbar model - this is for
+ * repaiting both scrollbars with the viewport.
+ *
+ * @since version 3.2
+ */
+ protected AdjustmentListener substanceAdjustmentListener;
+
+ /**
+ * Surrogate model to sync between rollover effects of scroll buttons and
+ * scroll track / scroll thumb.
+ *
+ * @since version 3.2
+ */
+ protected CompositeButtonModel compositeScrollTrackModel;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceScrollBarUI(comp);
+ }
+
+ /**
+ * Simple constructor.
+ *
+ * @param b
+ * Associated component.
+ */
+ protected SubstanceScrollBarUI(JComponent b) {
+ super();
+ this.thumbModel = new DefaultButtonModel();
+ this.thumbModel.setArmed(false);
+ this.thumbModel.setSelected(false);
+ this.thumbModel.setPressed(false);
+ this.thumbModel.setRollover(false);
+
+ b.setOpaque(false);
+ }
+
+ /**
+ * Creates a decrease button.
+ *
+ * @param orientation
+ * Button orientation.
+ * @param isRegular
+ * if <code>true</code>, the regular (upper / left) decrease
+ * button is created, if <code>false</code>, the additional
+ * (lower / right) decrease button is created for
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#ADJACENT}
+ * ,
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE}
+ * and
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE_BOTH}
+ * kinds.
+ * @return Decrease button.
+ */
+ protected JButton createGeneralDecreaseButton(final int orientation,
+ boolean isRegular) {
+ JButton result = new SubstanceScrollButton(orientation);
+ result.setName("Decrease " + (isRegular ? "regular" : "additional"));
+ result.setFont(this.scrollbar.getFont());
+ Icon icon = new ArrowButtonTransitionAwareIcon(result, orientation);
+ result.setIcon(icon);
+ result.setFont(scrollbar.getFont());
+
+ result.setPreferredSize(new Dimension(this.scrollBarWidth,
+ this.scrollBarWidth));
+
+ Set<Side> openSides = EnumSet.noneOf(Side.class);
+ Set<Side> straightSides = EnumSet.noneOf(Side.class);
+ switch (orientation) {
+ case NORTH:
+ openSides.add(Side.BOTTOM);
+ if (!isRegular)
+ openSides.add(Side.TOP);
+ if (isRegular)
+ straightSides.add(Side.TOP);
+ break;
+ case EAST:
+ openSides.add(Side.LEFT);
+ if (!isRegular)
+ openSides.add(Side.RIGHT);
+ if (isRegular)
+ straightSides.add(Side.RIGHT);
+ break;
+ case WEST:
+ openSides.add(Side.RIGHT);
+ if (!isRegular)
+ openSides.add(Side.LEFT);
+ if (isRegular)
+ straightSides.add(Side.LEFT);
+ break;
+ }
+ result.putClientProperty(
+ SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY, openSides);
+ result.putClientProperty(SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY,
+ straightSides);
+
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicScrollBarUI#createDecreaseButton(int)
+ */
+ @Override
+ protected JButton createDecreaseButton(int orientation) {
+ return this.createGeneralDecreaseButton(orientation, true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicScrollBarUI#createIncreaseButton(int)
+ */
+ @Override
+ protected JButton createIncreaseButton(int orientation) {
+ return this.createGeneralIncreaseButton(orientation, true);
+ }
+
+ /**
+ * Creates a increase button.
+ *
+ * @param orientation
+ * Button orientation.
+ * @param isRegular
+ * if <code>true</code>, the regular (lower / right) increase
+ * button is created, if <code>false</code>, the additional
+ * (upper / left) increase button is created for
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE_BOTH}
+ * kind.
+ * @return Increase button.
+ */
+ protected JButton createGeneralIncreaseButton(final int orientation,
+ boolean isRegular) {
+ JButton result = new SubstanceScrollButton(orientation);
+ result.setName("Increase " + (isRegular ? "regular" : "additional"));
+ result.setFont(this.scrollbar.getFont());
+ Icon icon = new ArrowButtonTransitionAwareIcon(result, orientation);
+ result.setIcon(icon);
+ result.setFont(scrollbar.getFont());
+ // JButton result = new SubstanceScrollBarButton(icon, orientation);
+ result.setPreferredSize(new Dimension(this.scrollBarWidth,
+ this.scrollBarWidth));
+
+ Set<Side> openSides = EnumSet.noneOf(Side.class);
+ Set<Side> straightSides = EnumSet.noneOf(Side.class);
+ switch (orientation) {
+ case SOUTH:
+ openSides.add(Side.TOP);
+ if (!isRegular)
+ openSides.add(Side.BOTTOM);
+ if (isRegular)
+ straightSides.add(Side.BOTTOM);
+ break;
+ case EAST:
+ openSides.add(Side.LEFT);
+ if (!isRegular)
+ openSides.add(Side.RIGHT);
+ if (isRegular)
+ straightSides.add(Side.RIGHT);
+ break;
+ case WEST:
+ openSides.add(Side.RIGHT);
+ if (!isRegular)
+ openSides.add(Side.LEFT);
+ if (isRegular)
+ straightSides.add(Side.LEFT);
+ break;
+ }
+ result.putClientProperty(
+ SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY, openSides);
+ result.putClientProperty(SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY,
+ straightSides);
+ return result;
+ }
+
+ /**
+ * Returns the image for a horizontal track.
+ *
+ * @param trackBounds
+ * Track bounds.
+ * @param leftActiveButton
+ * The closest left button in the scroll bar. May be
+ * <code>null</code>.
+ * @param rightActiveButton
+ * The closest right button in the scroll bar. May be
+ * <code>null</code> .
+ * @return Horizontal track image.
+ */
+ private void paintTrackHorizontal(Graphics g, Rectangle trackBounds,
+ SubstanceScrollButton leftActiveButton,
+ SubstanceScrollButton rightActiveButton) {
+ int width = Math.max(1, trackBounds.width);
+ int height = Math.max(1, trackBounds.height);
+
+ paintTrackBackHorizontal(g, this.scrollbar, leftActiveButton,
+ rightActiveButton, width, height);
+ BufferedImage horizontalTrack = getTrackHorizontal(this.scrollbar,
+ width, height);
+ g.drawImage(horizontalTrack, 0, 0, null);
+ }
+
+ /**
+ * Returns the image for a horizontal track.
+ *
+ * @param scrollBar
+ * Scroll bar.
+ * @param width
+ * Scroll track width.
+ * @param height
+ * Scroll track height.
+ * @return Horizontal track image.
+ */
+ private static BufferedImage getTrackHorizontal(JScrollBar scrollBar,
+ int width, int height) {
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(scrollBar);
+ SubstanceColorScheme mainScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(scrollBar,
+ scrollBar.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED);
+ SubstanceColorScheme mainBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(scrollBar, ColorSchemeAssociationKind.BORDER,
+ scrollBar.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED);
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(mainScheme
+ .getDisplayName(), mainBorderScheme.getDisplayName(), width,
+ height, shaper.getDisplayName());
+ float radius = height / 2;
+ if (shaper instanceof ClassicButtonShaper)
+ radius = SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(scrollBar));
+
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(scrollBar)) / 2.0);
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(width, height,
+ radius, null, borderDelta);
+ BufferedImage result = SubstanceScrollBarUI.trackHorizontalMap.get(key);
+ if (result == null) {
+ result = SubstanceCoreUtilities.getBlankImage(width, height);
+ SimplisticFillPainter.INSTANCE.paintContourBackground(result
+ .createGraphics(), scrollBar, width, height, contour,
+ false, mainScheme, true);
+
+ SubstanceBorderPainter borderPainter = new SimplisticSoftBorderPainter();
+ borderPainter.paintBorder(result.getGraphics(), scrollBar, width,
+ height, contour, null, mainBorderScheme);
+
+ SubstanceScrollBarUI.trackHorizontalMap.put(key, result);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the image for a horizontal track.
+ *
+ * @param scrollBar
+ * Scroll bar.
+ * @param leftActiveButton
+ * The closest left button in the scroll bar. May be
+ * <code>null</code>.
+ * @param rightActiveButton
+ * The closest right button in the scroll bar. May be
+ * <code>null</code> .
+ * @param width
+ * Scroll track width.
+ * @param height
+ * Scroll track height.
+ * @return Horizontal track image.
+ */
+ private static void paintTrackBackHorizontal(Graphics g,
+ JScrollBar scrollBar, AbstractButton leftActiveButton,
+ AbstractButton rightActiveButton, int width, int height) {
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(scrollBar);
+ int radius = height / 2;
+ if (shaper instanceof ClassicButtonShaper)
+ radius = 2;
+ SubstanceImageCreator.paintCompositeRoundedBackground(scrollBar, g,
+ width, height, radius, leftActiveButton, rightActiveButton,
+ false);
+ }
+
+ /**
+ * Returns the image for a vertical track.
+ *
+ * @param trackBounds
+ * Track bounds.
+ * @param topActiveButton
+ * The closest top button in the scroll bar. May be
+ * <code>null</code>.
+ * @param bottomActiveButton
+ * The closest bottom button in the scroll bar. May be
+ * <code>null</code>.
+ * @return Vertical track image.
+ */
+ private void paintTrackVertical(Graphics g, Rectangle trackBounds,
+ SubstanceScrollButton topActiveButton,
+ SubstanceScrollButton bottomActiveButton) {
+
+ int width = Math.max(1, trackBounds.width);
+ int height = Math.max(1, trackBounds.height);
+
+ paintTrackBackVertical(g, this.scrollbar, topActiveButton,
+ bottomActiveButton, width, height);
+ BufferedImage horizontalTrack = getTrackVertical(this.scrollbar, width,
+ height);
+ g.drawImage(horizontalTrack, 0, 0, null);
+ }
+
+ /**
+ * Returns the image for a vertical track.
+ *
+ * @param scrollBar
+ * Scroll bar.
+ * @param width
+ * Scroll track width.
+ * @param height
+ * Scroll track height.
+ * @return Vertical track image.
+ */
+ private static BufferedImage getTrackVertical(JScrollBar scrollBar,
+ int width, int height) {
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(scrollBar);
+ SubstanceColorScheme mainScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(scrollBar,
+ scrollBar.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED);
+ SubstanceColorScheme mainBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(scrollBar, ColorSchemeAssociationKind.BORDER,
+ scrollBar.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED);
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(mainScheme
+ .getDisplayName(), mainBorderScheme.getDisplayName(), width,
+ height, shaper.getDisplayName());
+ BufferedImage result = SubstanceScrollBarUI.trackVerticalMap.get(key);
+ if (result == null) {
+ float radius = width / 2;
+ if (shaper instanceof ClassicButtonShaper)
+ radius = SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(scrollBar));
+
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(scrollBar)) / 2.0);
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(height,
+ width, radius, null, borderDelta);
+
+ result = SubstanceCoreUtilities.getBlankImage(height, width);
+ SimplisticFillPainter.INSTANCE.paintContourBackground(result
+ .createGraphics(), scrollBar, height, width, contour,
+ false, mainScheme, true);
+
+ SubstanceBorderPainter borderPainter = new SimplisticSoftBorderPainter();
+ borderPainter.paintBorder(result.getGraphics(), scrollBar, height,
+ width, contour, null, mainBorderScheme);
+ result = SubstanceImageCreator.getRotated(result, 3);
+
+ SubstanceScrollBarUI.trackVerticalMap.put(key, result);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the image for a vertical track.
+ *
+ * @param scrollBar
+ * Scroll bar.
+ * @param topActiveButton
+ * The closest top button in the scroll bar. May be
+ * <code>null</code>.
+ * @param bottomActiveButton
+ * The closest bottom button in the scroll bar. May be
+ * <code>null</code>.
+ * @param width
+ * Scroll track width.
+ * @param height
+ * Scroll track height.
+ * @return Vertical track image.
+ */
+ private static void paintTrackBackVertical(Graphics g,
+ JScrollBar scrollBar, AbstractButton topActiveButton,
+ AbstractButton bottomActiveButton, int width, int height) {
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(scrollBar);
+ int radius = width / 2;
+ if (shaper instanceof ClassicButtonShaper)
+ radius = 2;
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ AffineTransform at = AffineTransform.getTranslateInstance(0, height);
+ at.rotate(-Math.PI / 2);
+ g2d.transform(at);
+ SubstanceImageCreator.paintCompositeRoundedBackground(scrollBar, g2d,
+ height, width, radius, topActiveButton, bottomActiveButton,
+ true);
+ g2d.dispose();
+ }
+
+ /**
+ * Retrieves image for vertical thumb.
+ *
+ * @param thumbBounds
+ * Thumb bounding rectangle.
+ * @return Image for vertical thumb.
+ */
+ private BufferedImage getThumbVertical(Rectangle thumbBounds) {
+ int width = Math.max(1, thumbBounds.width);
+ int height = Math.max(1, thumbBounds.height);
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = this.compositeStateTransitionTracker
+ .getModelStateInfo();
+ ComponentState currState = modelStateInfo.getCurrModelState();
+
+ // enabled scroll bar is always painted as active
+ SubstanceColorScheme baseFillScheme = (currState != ComponentState.ENABLED) ? SubstanceColorSchemeUtilities
+ .getColorScheme(this.scrollbar, currState)
+ : SubstanceColorSchemeUtilities.getActiveColorScheme(
+ this.scrollbar, currState);
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.scrollbar,
+ ColorSchemeAssociationKind.BORDER, currState);
+ BufferedImage baseLayer = getThumbVertical(this.scrollbar, width,
+ height, baseFillScheme, baseBorderScheme);
+
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ if (currState.isDisabled() || (activeStates.size() == 1)) {
+ return baseLayer;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(baseLayer
+ .getWidth(), baseLayer.getHeight());
+ Graphics2D g2d = result.createGraphics();
+ g2d.drawImage(baseLayer, 0, 0, null);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == modelStateInfo.getCurrModelState())
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
+
+ SubstanceColorScheme fillScheme = (activeState != ComponentState.ENABLED) ? SubstanceColorSchemeUtilities
+ .getColorScheme(this.scrollbar, activeState)
+ : SubstanceColorSchemeUtilities.getActiveColorScheme(
+ this.scrollbar, activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.scrollbar,
+ ColorSchemeAssociationKind.BORDER, activeState);
+ BufferedImage layer = getThumbVertical(this.scrollbar, width,
+ height, fillScheme, borderScheme);
+ g2d.drawImage(layer, 0, 0, null);
+ }
+
+ g2d.dispose();
+ return result;
+ }
+
+ /**
+ * Retrieves image for vertical thumb.
+ *
+ * @param scrollBar
+ * Scroll bar.
+ * @param width
+ * Thumb width.
+ * @param height
+ * Thumb height.
+ * @param scheme
+ * The first color scheme.
+ * @param borderScheme
+ * The first border color scheme.
+ * @return Image for vertical thumb.
+ */
+ private static BufferedImage getThumbVertical(JScrollBar scrollBar,
+ int width, int height, SubstanceColorScheme scheme,
+ SubstanceColorScheme borderScheme) {
+ SubstanceFillPainter painter = SubstanceCoreUtilities
+ .getFillPainter(scrollBar);
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(scrollBar);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(scrollBar);
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
+ scheme.getDisplayName(), borderScheme.getDisplayName(), painter
+ .getDisplayName(), shaper.getDisplayName(),
+ borderPainter.getDisplayName());
+ BufferedImage result = SubstanceScrollBarUI.thumbVerticalMap.get(key);
+ if (result == null) {
+ // System.out.println("Cache miss - computing");
+ // System.out.println("New image for vertical thumb");
+ float radius = width / 2;
+ if (shaper instanceof ClassicButtonShaper)
+ radius = SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(scrollBar));
+
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(scrollBar)) / 2.0);
+ GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(
+ height, width, radius, null, borderDelta);
+
+ result = SubstanceCoreUtilities.getBlankImage(height, width);
+ painter.paintContourBackground(result.createGraphics(), scrollBar,
+ height, width, contour, false, scheme, true);
+
+ // int borderThickness = (int) SubstanceSizeUtils
+ // .getBorderStrokeWidth(SubstanceSizeUtils
+ // .getComponentFontSize(scrollBar));
+ // GeneralPath contourInner = SubstanceOutlineUtilities
+ // .getBaseOutline(height, width, radius, null,
+ // borderThickness + borderDelta);
+ borderPainter.paintBorder(result.getGraphics(), scrollBar, height,
+ width, contour, null, borderScheme);
+ result = SubstanceImageCreator.getRotated(result, 3);
+ // System.out.println(key);
+ SubstanceScrollBarUI.thumbVerticalMap.put(key, result);
+ }
+
+ return result;
+ }
+
+ /**
+ * Retrieves image for horizontal thumb.
+ *
+ * @param thumbBounds
+ * Thumb bounding rectangle.
+ * @return Image for horizontal thumb.
+ */
+ private BufferedImage getThumbHorizontal(Rectangle thumbBounds) {
+ int width = Math.max(1, thumbBounds.width);
+ int height = Math.max(1, thumbBounds.height);
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = this.compositeStateTransitionTracker
+ .getModelStateInfo();
+ ComponentState currState = modelStateInfo.getCurrModelState();
+ // enabled scroll bar is always painted as active
+ // if (currState == ComponentState.ENABLED)
+ // currState = ComponentState.SELECTED;
+
+ SubstanceColorScheme baseFillScheme = (currState != ComponentState.ENABLED) ? SubstanceColorSchemeUtilities
+ .getColorScheme(this.scrollbar, currState)
+ : SubstanceColorSchemeUtilities.getActiveColorScheme(
+ this.scrollbar, currState);
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.scrollbar,
+ ColorSchemeAssociationKind.BORDER, currState);
+ BufferedImage baseLayer = getThumbHorizontal(this.scrollbar, width,
+ height, baseFillScheme, baseBorderScheme);
+
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ if (currState.isDisabled() || (activeStates.size() == 1)) {
+ return baseLayer;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(baseLayer
+ .getWidth(), baseLayer.getHeight());
+ Graphics2D g2d = result.createGraphics();
+ g2d.drawImage(baseLayer, 0, 0, null);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == modelStateInfo.getCurrModelState())
+ continue;
+ // if (activeState == ComponentState.ENABLED)
+ // activeState = ComponentState.SELECTED;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
+
+ SubstanceColorScheme fillScheme = (activeState != ComponentState.ENABLED) ? SubstanceColorSchemeUtilities
+ .getColorScheme(this.scrollbar, activeState)
+ : SubstanceColorSchemeUtilities.getActiveColorScheme(
+ this.scrollbar, activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.scrollbar,
+ ColorSchemeAssociationKind.BORDER, activeState);
+ BufferedImage layer = getThumbHorizontal(this.scrollbar, width,
+ height, fillScheme, borderScheme);
+ g2d.drawImage(layer, 0, 0, null);
+ }
+
+ g2d.dispose();
+ return result;
+ }
+
+ /**
+ * Retrieves image for horizontal thumb.
+ *
+ * @param scrollBar
+ * Scroll bar.
+ * @param width
+ * Thumb width.
+ * @param height
+ * Thumb height.
+ * @param scheme
+ * The first color scheme.
+ * @param borderScheme
+ * The first border color scheme.
+ * @return Image for horizontal thumb.
+ */
+ private static BufferedImage getThumbHorizontal(JScrollBar scrollBar,
+ int width, int height, SubstanceColorScheme scheme,
+ SubstanceColorScheme borderScheme) {
+ SubstanceFillPainter painter = SubstanceCoreUtilities
+ .getFillPainter(scrollBar);
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(scrollBar);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(scrollBar);
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
+ scheme.getDisplayName(), borderScheme.getDisplayName(), painter
+ .getDisplayName(), shaper.getDisplayName(),
+ borderPainter.getDisplayName());
+
+ float radius = height / 2;
+ if (shaper instanceof ClassicButtonShaper)
+ radius = SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(scrollBar));
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(scrollBar)) / 2.0);
+ GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(width,
+ height, radius, null, borderDelta);
+ BufferedImage opaque = SubstanceScrollBarUI.thumbHorizontalMap.get(key);
+ if (opaque == null) {
+ // System.out.println("New image for horizontal thumb");
+
+ opaque = SubstanceCoreUtilities.getBlankImage(width, height);
+ painter.paintContourBackground(opaque.createGraphics(), scrollBar,
+ width, height, contour, false, scheme, true);
+
+ borderPainter.paintBorder(opaque.getGraphics(), scrollBar, width,
+ height, contour, null, borderScheme);
+ SubstanceScrollBarUI.thumbHorizontalMap.put(key, opaque);
+ }
+
+ return opaque;
+ }
+
+ /**
+ * Returns the scroll button state.
+ *
+ * @param scrollButton
+ * Scroll button.
+ * @return Scroll button state.
+ */
+ protected ComponentState getState(JButton scrollButton) {
+ if (scrollButton == null)
+ return null;
+
+ ComponentState result = ((TransitionAwareUI) scrollButton.getUI())
+ .getTransitionTracker().getModelStateInfo().getCurrModelState();
+ if ((result == ComponentState.ENABLED)
+ && SubstanceCoreUtilities.hasFlatAppearance(this.scrollbar,
+ false)) {
+ result = null;
+ }
+ if (SubstanceCoreUtilities.isButtonNeverPainted(scrollButton)) {
+ result = null;
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollBarUI#paintTrack(java.awt.Graphics,
+ * javax.swing.JComponent, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ // System.out.println("Track");
+ ScrollPaneButtonPolicyKind buttonPolicy = SubstanceCoreUtilities
+ .getScrollPaneButtonsPolicyKind(this.scrollbar);
+ SubstanceScrollButton compTopState = null;
+ SubstanceScrollButton compBottomState = null;
+ if (this.decrButton.isShowing() && this.incrButton.isShowing()
+ && this.mySecondDecreaseButton.isShowing()
+ && this.mySecondIncreaseButton.isShowing()) {
+ switch (buttonPolicy) {
+ case OPPOSITE:
+ compTopState = (SubstanceScrollButton) this.decrButton;
+ compBottomState = (SubstanceScrollButton) this.incrButton;
+ break;
+ case ADJACENT:
+ compBottomState = (SubstanceScrollButton) this.mySecondDecreaseButton;
+ break;
+ case MULTIPLE:
+ compTopState = (SubstanceScrollButton) this.decrButton;
+ compBottomState = (SubstanceScrollButton) this.mySecondDecreaseButton;
+ break;
+ case MULTIPLE_BOTH:
+ compTopState = (SubstanceScrollButton) this.mySecondIncreaseButton;
+ compBottomState = (SubstanceScrollButton) this.mySecondDecreaseButton;
+ break;
+ }
+ }
+
+ graphics.translate(trackBounds.x, trackBounds.y);
+ if (this.scrollbar.getOrientation() == Adjustable.VERTICAL) {
+ paintTrackVertical(graphics, trackBounds, compTopState,
+ compBottomState);
+ } else {
+ if (this.scrollbar.getComponentOrientation().isLeftToRight()) {
+ paintTrackHorizontal(graphics, trackBounds, compTopState,
+ compBottomState);
+ } else {
+ paintTrackHorizontal(graphics, trackBounds, compBottomState,
+ compTopState);
+ }
+ // BufferedImage bi = this.scrollbar.getComponentOrientation()
+ // .isLeftToRight() ? this.getTrackHorizontal(trackBounds,
+ // compTopState, compBottomState) : this.getTrackHorizontal(
+ // trackBounds, compBottomState, compTopState);
+ // graphics.drawImage(bi, trackBounds.x, trackBounds.y, null);
+ }
+
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollBarUI#paintThumb(java.awt.Graphics,
+ * javax.swing.JComponent, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) {
+ // System.out.println("Thumb");
+ Graphics2D graphics = (Graphics2D) g.create();
+ // ControlBackgroundComposite composite = SubstanceCoreUtilities
+ // .getControlBackgroundComposite(this.scrollbar);
+
+ // JScrollBar scrollBar = (JScrollBar) c;
+ this.thumbModel.setSelected(this.thumbModel.isSelected()
+ || this.isDragging);
+ this.thumbModel.setEnabled(c.isEnabled());
+ boolean isVertical = (this.scrollbar.getOrientation() == Adjustable.VERTICAL);
+ if (isVertical) {
+ Rectangle adjustedBounds = new Rectangle(thumbBounds.x,
+ thumbBounds.y, thumbBounds.width, thumbBounds.height);
+ BufferedImage thumbImage = this.getThumbVertical(adjustedBounds);
+ graphics.drawImage(thumbImage, adjustedBounds.x, adjustedBounds.y,
+ null);
+ } else {
+ Rectangle adjustedBounds = new Rectangle(thumbBounds.x,
+ thumbBounds.y, thumbBounds.width, thumbBounds.height);
+ BufferedImage thumbImage = this.getThumbHorizontal(adjustedBounds);
+ graphics.drawImage(thumbImage, adjustedBounds.x, adjustedBounds.y,
+ null);
+ }
+ graphics.dispose();
+ }
+
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ BackgroundPaintingUtils.update(graphics, c, false);
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(this.scrollbar,
+ ComponentState.getState(this.thumbModel, this.scrollbar));
+ graphics
+ .setComposite(LafWidgetUtilities.getAlphaComposite(c, alpha, g));
+ super.paint(graphics, c);
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicScrollBarUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ this.scrollBarWidth = SubstanceSizeUtils
+ .getScrollBarWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.scrollbar));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicScrollBarUI#installComponents()
+ */
+ @Override
+ protected void installComponents() {
+ super.installComponents();
+ switch (this.scrollbar.getOrientation()) {
+ case JScrollBar.VERTICAL:
+ this.mySecondDecreaseButton = this.createGeneralDecreaseButton(
+ NORTH, false);
+ this.mySecondIncreaseButton = this.createGeneralIncreaseButton(
+ SOUTH, false);
+ break;
+
+ case JScrollBar.HORIZONTAL:
+ if (this.scrollbar.getComponentOrientation().isLeftToRight()) {
+ this.mySecondDecreaseButton = this.createGeneralDecreaseButton(
+ WEST, false);
+ this.mySecondIncreaseButton = this.createGeneralIncreaseButton(
+ EAST, false);
+ } else {
+ this.mySecondDecreaseButton = this.createGeneralDecreaseButton(
+ EAST, false);
+ this.mySecondIncreaseButton = this.createGeneralIncreaseButton(
+ WEST, false);
+ }
+ break;
+ }
+ this.scrollbar.add(this.mySecondDecreaseButton);
+ this.scrollbar.add(this.mySecondIncreaseButton);
+
+ this.compositeScrollTrackModel = new CompositeButtonModel(
+ this.thumbModel, this.incrButton, this.decrButton,
+ this.mySecondDecreaseButton, this.mySecondIncreaseButton);
+ this.compositeScrollTrackModel.registerListeners();
+
+ this.compositeStateTransitionTracker = new StateTransitionTracker(
+ this.scrollbar, this.compositeScrollTrackModel);
+ this.compositeStateTransitionTracker.registerModelListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicScrollBarUI#uninstallComponents()
+ */
+ @Override
+ protected void uninstallComponents() {
+ this.compositeScrollTrackModel.unregisterListeners();
+ this.compositeStateTransitionTracker.unregisterModelListeners();
+
+ this.scrollbar.remove(this.mySecondDecreaseButton);
+ this.scrollbar.remove(this.mySecondIncreaseButton);
+ super.uninstallComponents();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicScrollBarUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ this.substanceMouseListener = new MouseAdapter() {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ SubstanceScrollBarUI.this.scrollbar.repaint();
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ SubstanceScrollBarUI.this.scrollbar.repaint();
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ SubstanceScrollBarUI.this.scrollbar.repaint();
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ SubstanceScrollBarUI.this.scrollbar.repaint();
+ }
+ };
+
+ this.incrButton.addMouseListener(this.substanceMouseListener);
+ this.decrButton.addMouseListener(this.substanceMouseListener);
+ this.mySecondDecreaseButton
+ .addMouseListener(this.substanceMouseListener);
+ this.mySecondIncreaseButton
+ .addMouseListener(this.substanceMouseListener);
+
+ this.substanceThumbRolloverListener = new RolloverControlListener(this,
+ this.thumbModel);
+ this.scrollbar.addMouseListener(this.substanceThumbRolloverListener);
+ this.scrollbar
+ .addMouseMotionListener(this.substanceThumbRolloverListener);
+
+ // this.thumbStateTransitionTracker.registerModelListeners();
+
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if ( scrollbar != null ) scrollbar.updateUI();
+ }
+ });
+ }
+ if ("background".equals(evt.getPropertyName())) {
+ // propagate application-specific background color to the
+ // scroll buttons.
+ Color newBackgr = (Color) evt.getNewValue();
+ if (!(newBackgr instanceof UIResource)) {
+ if (mySecondDecreaseButton != null) {
+ if (mySecondDecreaseButton.getBackground() instanceof UIResource) {
+ mySecondDecreaseButton.setBackground(newBackgr);
+ }
+ }
+ if (mySecondIncreaseButton != null) {
+ if (mySecondIncreaseButton.getBackground() instanceof UIResource) {
+ mySecondIncreaseButton.setBackground(newBackgr);
+ }
+ }
+ if (incrButton != null) {
+ if (incrButton.getBackground() instanceof UIResource) {
+ incrButton.setBackground(newBackgr);
+ }
+ }
+ if (decrButton != null) {
+ if (decrButton.getBackground() instanceof UIResource) {
+ decrButton.setBackground(newBackgr);
+ }
+ }
+ }
+ }
+ }
+ };
+ this.scrollbar
+ .addPropertyChangeListener(this.substancePropertyListener);
+
+ this.mySecondDecreaseButton.addMouseListener(this.buttonListener);
+ this.mySecondIncreaseButton.addMouseListener(this.buttonListener);
+
+ this.substanceAdjustmentListener = new AdjustmentListener() {
+ @Override
+ public void adjustmentValueChanged(AdjustmentEvent e) {
+ SubstanceCoreUtilities
+ .testComponentStateChangeThreadingViolation(scrollbar);
+ Component parent = SubstanceScrollBarUI.this.scrollbar
+ .getParent();
+ if (parent instanceof JScrollPane) {
+ JScrollPane jsp = (JScrollPane) parent;
+ JScrollBar hor = jsp.getHorizontalScrollBar();
+ JScrollBar ver = jsp.getVerticalScrollBar();
+
+ JScrollBar other = null;
+ if (SubstanceScrollBarUI.this.scrollbar == hor) {
+ other = ver;
+ }
+ if (SubstanceScrollBarUI.this.scrollbar == ver) {
+ other = hor;
+ }
+
+ if ((other != null) && other.isVisible())
+ other.repaint();
+ SubstanceScrollBarUI.this.scrollbar.repaint();
+ }
+ }
+ };
+ this.scrollbar.addAdjustmentListener(this.substanceAdjustmentListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicScrollBarUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ // fix for defect 109 - memory leak on changing skin
+ this.incrButton.removeMouseListener(this.substanceMouseListener);
+ this.decrButton.removeMouseListener(this.substanceMouseListener);
+ this.mySecondDecreaseButton
+ .removeMouseListener(this.substanceMouseListener);
+ this.mySecondIncreaseButton
+ .removeMouseListener(this.substanceMouseListener);
+ this.substanceMouseListener = null;
+
+ this.scrollbar.removeMouseListener(this.substanceThumbRolloverListener);
+ this.scrollbar
+ .removeMouseMotionListener(this.substanceThumbRolloverListener);
+ this.substanceThumbRolloverListener = null;
+
+ this.scrollbar
+ .removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+
+ this.mySecondDecreaseButton.removeMouseListener(this.buttonListener);
+ this.mySecondIncreaseButton.removeMouseListener(this.buttonListener);
+
+ this.scrollbar
+ .removeAdjustmentListener(this.substanceAdjustmentListener);
+ this.substanceAdjustmentListener = null;
+
+ super.uninstallListeners();
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ Rectangle trackB = this.getTrackBounds();
+ if (trackB == null)
+ return false;
+ return trackB.contains(me.getX(), me.getY());
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.compositeStateTransitionTracker;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicScrollBarUI#scrollByBlock(int)
+ */
+ @Override
+ public void scrollByBlock(int direction) {
+ // This method is called from SubstanceScrollPaneUI to implement wheel
+ // scrolling.
+ int oldValue = this.scrollbar.getValue();
+ int blockIncrement = this.scrollbar.getBlockIncrement(direction);
+ int delta = blockIncrement * ((direction > 0) ? +1 : -1);
+ int newValue = oldValue + delta;
+
+ // Check for overflow.
+ if ((delta > 0) && (newValue < oldValue)) {
+ newValue = this.scrollbar.getMaximum();
+ } else if ((delta < 0) && (newValue > oldValue)) {
+ newValue = this.scrollbar.getMinimum();
+ }
+
+ this.scrollbar.setValue(newValue);
+ }
+
+ /**
+ * Scrolls the associated scroll bar.
+ *
+ * @param direction
+ * Direction.
+ * @param units
+ * Scroll units.
+ */
+ public void scrollByUnits(int direction, int units) {
+ // This method is called from SubstanceScrollPaneUI to implement wheel
+ // scrolling.
+ int delta;
+
+ for (int i = 0; i < units; i++) {
+ if (direction > 0) {
+ delta = this.scrollbar.getUnitIncrement(direction);
+ } else {
+ delta = -this.scrollbar.getUnitIncrement(direction);
+ }
+
+ int oldValue = this.scrollbar.getValue();
+ int newValue = oldValue + delta;
+
+ // Check for overflow.
+ if ((delta > 0) && (newValue < oldValue)) {
+ newValue = this.scrollbar.getMaximum();
+ } else if ((delta < 0) && (newValue > oldValue)) {
+ newValue = this.scrollbar.getMinimum();
+ }
+ if (oldValue == newValue) {
+ break;
+ }
+ this.scrollbar.setValue(newValue);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollBarUI#layoutVScrollbar(javax.swing.
+ * JScrollBar)
+ */
+ @Override
+ protected void layoutVScrollbar(JScrollBar sb) {
+ ScrollPaneButtonPolicyKind buttonPolicy = SubstanceCoreUtilities
+ .getScrollPaneButtonsPolicyKind(this.scrollbar);
+ this.mySecondDecreaseButton.setBounds(0, 0, 0, 0);
+ this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
+ switch (buttonPolicy) {
+ case OPPOSITE:
+ super.layoutVScrollbar(sb);
+ break;
+ case NONE:
+ this.layoutVScrollbarNone(sb);
+ break;
+ case ADJACENT:
+ this.layoutVScrollbarAdjacent(sb);
+ break;
+ case MULTIPLE:
+ this.layoutVScrollbarMultiple(sb);
+ break;
+ case MULTIPLE_BOTH:
+ this.layoutVScrollbarMultipleBoth(sb);
+ break;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollBarUI#layoutHScrollbar(javax.swing.
+ * JScrollBar)
+ */
+ @Override
+ protected void layoutHScrollbar(JScrollBar sb) {
+ this.mySecondDecreaseButton.setBounds(0, 0, 0, 0);
+ this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
+ ScrollPaneButtonPolicyKind buttonPolicy = SubstanceCoreUtilities
+ .getScrollPaneButtonsPolicyKind(this.scrollbar);
+ switch (buttonPolicy) {
+ case OPPOSITE:
+ super.layoutHScrollbar(sb);
+ break;
+ case NONE:
+ this.layoutHScrollbarNone(sb);
+ break;
+ case ADJACENT:
+ this.layoutHScrollbarAdjacent(sb);
+ break;
+ case MULTIPLE:
+ this.layoutHScrollbarMultiple(sb);
+ break;
+ case MULTIPLE_BOTH:
+ this.layoutHScrollbarMultipleBoth(sb);
+ break;
+ }
+ }
+
+ /**
+ * Lays out the vertical scroll bar when the button policy is
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#ADJACENT}.
+ *
+ * @param sb
+ * Scroll bar.
+ */
+ protected void layoutVScrollbarAdjacent(JScrollBar sb) {
+ Dimension sbSize = sb.getSize();
+ Insets sbInsets = sb.getInsets();
+
+ /*
+ * Width and left edge of the buttons and thumb.
+ */
+ int itemW = sbSize.width - (sbInsets.left + sbInsets.right);
+ int itemX = sbInsets.left;
+
+ /*
+ * Nominal locations of the buttons, assuming their preferred size will
+ * fit.
+ */
+ int incrButtonH = itemW;
+ int incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
+
+ int decrButton2H = itemW;
+ int decrButton2Y = incrButtonY - decrButton2H;
+
+ /*
+ * The thumb must fit within the height left over after we subtract the
+ * preferredSize of the buttons and the insets.
+ */
+ int sbInsetsH = sbInsets.top + sbInsets.bottom;
+ int sbButtonsH = decrButton2H + incrButtonH;
+ float trackH = sbSize.height - (sbInsetsH + sbButtonsH);
+
+ /*
+ * Compute the height and origin of the thumb. The case where the thumb
+ * is at the bottom edge is handled specially to avoid numerical
+ * problems in computing thumbY. Enforce the thumbs min/max dimensions.
+ * If the thumb doesn't fit in the track (trackH) we'll hide it later.
+ */
+ float min = sb.getMinimum();
+ float extent = sb.getVisibleAmount();
+ float range = sb.getMaximum() - min;
+ float value = sb.getValue();
+
+ int thumbH = (range <= 0) ? this.getMaximumThumbSize().height
+ : (int) (trackH * (extent / range));
+ thumbH = Math.max(thumbH, this.getMinimumThumbSize().height);
+ thumbH = Math.min(thumbH, this.getMaximumThumbSize().height);
+
+ int thumbY = decrButton2Y - thumbH;
+ if (value < (sb.getMaximum() - sb.getVisibleAmount())) {
+ float thumbRange = trackH - thumbH;
+ thumbY = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
+ }
+
+ /*
+ * If the buttons don't fit, allocate half of the available space to
+ * each and move the lower one (incrButton) down.
+ */
+ int sbAvailButtonH = (sbSize.height - sbInsetsH);
+ if (sbAvailButtonH < sbButtonsH) {
+ incrButtonH = decrButton2H = sbAvailButtonH / 2;
+ incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
+ }
+ this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
+ this.decrButton.setBounds(0, 0, 0, 0);
+ this.mySecondDecreaseButton.setBounds(itemX,
+ incrButtonY - decrButton2H, itemW, decrButton2H);
+ this.incrButton.setBounds(itemX, incrButtonY - 1, itemW,
+ incrButtonH + 1);
+
+ /*
+ * Update the trackRect field.
+ */
+ int itrackY = 0;
+ int itrackH = decrButton2Y - itrackY;
+ this.trackRect.setBounds(itemX, itrackY, itemW, itrackH);
+
+ /*
+ * If the thumb isn't going to fit, zero it's bounds. Otherwise make
+ * sure it fits between the buttons. Note that setting the thumbs bounds
+ * will cause a repaint.
+ */
+ if (thumbH >= (int) trackH) {
+ this.setThumbBounds(0, 0, 0, 0);
+ } else {
+ if ((thumbY + thumbH) > decrButton2Y) {
+ thumbY = decrButton2Y - thumbH;
+ }
+ if (thumbY < 0) {
+ thumbY = 0;
+ }
+ this.setThumbBounds(itemX, thumbY, itemW, thumbH);
+ }
+ }
+
+ /**
+ * Lays out the vertical scroll bar when the button policy is
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#ADJACENT}.
+ *
+ * @param sb
+ * Scroll bar.
+ */
+ protected void layoutVScrollbarNone(JScrollBar sb) {
+ Dimension sbSize = sb.getSize();
+ Insets sbInsets = sb.getInsets();
+
+ /*
+ * Width and left edge of the buttons and thumb.
+ */
+ int itemW = sbSize.width - (sbInsets.left + sbInsets.right);
+ int itemX = sbInsets.left;
+
+ /*
+ * Nominal locations of the buttons, assuming their preferred size will
+ * fit.
+ */
+ int incrButtonH = 0;
+ int incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
+
+ int decrButton2H = 0;
+ int decrButton2Y = incrButtonY - decrButton2H;
+
+ /*
+ * The thumb must fit within the height left over after we subtract the
+ * preferredSize of the buttons and the insets.
+ */
+ int sbInsetsH = sbInsets.top + sbInsets.bottom;
+ int sbButtonsH = decrButton2H + incrButtonH;
+ float trackH = sbSize.height - (sbInsetsH + sbButtonsH);
+
+ /*
+ * Compute the height and origin of the thumb. The case where the thumb
+ * is at the bottom edge is handled specially to avoid numerical
+ * problems in computing thumbY. Enforce the thumbs min/max dimensions.
+ * If the thumb doesn't fit in the track (trackH) we'll hide it later.
+ */
+ float min = sb.getMinimum();
+ float extent = sb.getVisibleAmount();
+ float range = sb.getMaximum() - min;
+ float value = sb.getValue();
+
+ int thumbH = (range <= 0) ? this.getMaximumThumbSize().height
+ : (int) (trackH * (extent / range));
+ thumbH = Math.max(thumbH, this.getMinimumThumbSize().height);
+ thumbH = Math.min(thumbH, this.getMaximumThumbSize().height);
+
+ int thumbY = decrButton2Y - thumbH;
+ if (value < (sb.getMaximum() - sb.getVisibleAmount())) {
+ float thumbRange = trackH - thumbH;
+ thumbY = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
+ }
+
+ /*
+ * If the buttons don't fit, allocate half of the available space to
+ * each and move the lower one (incrButton) down.
+ */
+ int sbAvailButtonH = (sbSize.height - sbInsetsH);
+ if (sbAvailButtonH < sbButtonsH) {
+ incrButtonH = 0;// decrButton2H = 0;
+ // incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
+ }
+ this.decrButton.setBounds(0, 0, 0, 0);
+ this.mySecondDecreaseButton.setBounds(0, 0, 0, 0);
+ this.incrButton.setBounds(0, 0, 0, 0);
+ this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
+
+ /*
+ * Update the trackRect field.
+ */
+ int itrackY = 0;
+ int itrackH = decrButton2Y - itrackY;
+ this.trackRect.setBounds(itemX, itrackY, itemW, itrackH);
+
+ /*
+ * If the thumb isn't going to fit, zero it's bounds. Otherwise make
+ * sure it fits between the buttons. Note that setting the thumbs bounds
+ * will cause a repaint.
+ */
+ if (thumbH >= (int) trackH) {
+ this.setThumbBounds(0, 0, 0, 0);
+ } else {
+ if ((thumbY + thumbH) > decrButton2Y) {
+ thumbY = decrButton2Y - thumbH;
+ }
+ if (thumbY < 0) {
+ thumbY = 0;
+ }
+ this.setThumbBounds(itemX, thumbY, itemW, thumbH);
+ }
+ }
+
+ /**
+ * Lays out the vertical scroll bar when the button policy is
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE}.
+ *
+ * @param sb
+ * Scroll bar.
+ */
+ protected void layoutVScrollbarMultiple(JScrollBar sb) {
+ Dimension sbSize = sb.getSize();
+ Insets sbInsets = sb.getInsets();
+
+ /*
+ * Width and left edge of the buttons and thumb.
+ */
+ int itemW = sbSize.width - (sbInsets.left + sbInsets.right);
+ int itemX = sbInsets.left;
+
+ /*
+ * Nominal locations of the buttons, assuming their preferred size will
+ * fit.
+ */
+ int incrButtonH = itemW;
+ int incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
+
+ int decrButton2H = itemW;
+ int decrButton2Y = incrButtonY - decrButton2H;
+
+ int decrButtonH = itemW;
+ int decrButtonY = sbInsets.top;
+
+ /*
+ * The thumb must fit within the height left over after we subtract the
+ * preferredSize of the buttons and the insets.
+ */
+ int sbInsetsH = sbInsets.top + sbInsets.bottom;
+ int sbButtonsH = decrButton2H + incrButtonH + decrButtonH;
+ float trackH = sbSize.height - (sbInsetsH + sbButtonsH);
+
+ /*
+ * Compute the height and origin of the thumb. The case where the thumb
+ * is at the bottom edge is handled specially to avoid numerical
+ * problems in computing thumbY. Enforce the thumbs min/max dimensions.
+ * If the thumb doesn't fit in the track (trackH) we'll hide it later.
+ */
+ float min = sb.getMinimum();
+ float extent = sb.getVisibleAmount();
+ float range = sb.getMaximum() - min;
+ float value = sb.getValue();
+
+ int thumbH = (range <= 0) ? this.getMaximumThumbSize().height
+ : (int) (trackH * (extent / range));
+ thumbH = Math.max(thumbH, this.getMinimumThumbSize().height);
+ thumbH = Math.min(thumbH, this.getMaximumThumbSize().height);
+
+ int thumbY = decrButton2Y - thumbH;
+ if (value < (sb.getMaximum() - sb.getVisibleAmount())) {
+ float thumbRange = trackH - thumbH;
+ thumbY = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
+ thumbY += decrButtonY + decrButtonH;
+ }
+
+ /*
+ * If the buttons don't fit, allocate half of the available space to
+ * each and move the lower one (incrButton) down.
+ */
+ int sbAvailButtonH = (sbSize.height - sbInsetsH);
+ if (sbAvailButtonH < sbButtonsH) {
+ incrButtonH = decrButton2H = decrButtonH = sbAvailButtonH / 2;
+ incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
+ }
+ this.decrButton.setBounds(itemX, decrButtonY, itemW, decrButtonH);
+ this.mySecondDecreaseButton.setBounds(itemX,
+ incrButtonY - decrButton2H, itemW, decrButton2H);
+ this.incrButton.setBounds(itemX, incrButtonY - 1, itemW,
+ incrButtonH + 1);
+ this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
+
+ /*
+ * Update the trackRect field.
+ */
+ int itrackY = decrButtonY + decrButtonH;
+ int itrackH = decrButton2Y - itrackY;
+ this.trackRect.setBounds(itemX, itrackY, itemW, itrackH);
+
+ /*
+ * If the thumb isn't going to fit, zero it's bounds. Otherwise make
+ * sure it fits between the buttons. Note that setting the thumbs bounds
+ * will cause a repaint.
+ */
+ if (thumbH >= (int) trackH) {
+ this.setThumbBounds(0, 0, 0, 0);
+ } else {
+ if ((thumbY + thumbH) > decrButton2Y) {
+ thumbY = decrButton2Y - thumbH;
+ }
+ if (thumbY < (decrButtonY + decrButtonH)) {
+ thumbY = decrButtonY + decrButtonH + 1;
+ }
+ this.setThumbBounds(itemX, thumbY, itemW, thumbH);
+ }
+ }
+
+ /**
+ * Lays out the vertical scroll bar when the button policy is
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE_BOTH}.
+ *
+ * @param sb
+ * Scroll bar.
+ */
+ protected void layoutVScrollbarMultipleBoth(JScrollBar sb) {
+ Dimension sbSize = sb.getSize();
+ Insets sbInsets = sb.getInsets();
+
+ /*
+ * Width and left edge of the buttons and thumb.
+ */
+ int itemW = sbSize.width - (sbInsets.left + sbInsets.right);
+ int itemX = sbInsets.left;
+
+ /*
+ * Nominal locations of the buttons, assuming their preferred size will
+ * fit.
+ */
+ int incrButtonH = itemW;
+ int incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
+
+ int decrButton2H = itemW;
+ int decrButton2Y = incrButtonY - decrButton2H;
+
+ int decrButtonH = itemW;
+ int decrButtonY = sbInsets.top;
+
+ int incrButton2H = itemW;
+ int incrButton2Y = decrButtonY + decrButtonH;
+
+ /*
+ * The thumb must fit within the height left over after we subtract the
+ * preferredSize of the buttons and the insets.
+ */
+ int sbInsetsH = sbInsets.top + sbInsets.bottom;
+ int sbButtonsH = decrButton2H + incrButtonH + decrButtonH
+ + incrButton2H;
+ float trackH = sbSize.height - (sbInsetsH + sbButtonsH);
+
+ /*
+ * Compute the height and origin of the thumb. The case where the thumb
+ * is at the bottom edge is handled specially to avoid numerical
+ * problems in computing thumbY. Enforce the thumbs min/max dimensions.
+ * If the thumb doesn't fit in the track (trackH) we'll hide it later.
+ */
+ float min = sb.getMinimum();
+ float extent = sb.getVisibleAmount();
+ float range = sb.getMaximum() - min;
+ float value = sb.getValue();
+
+ int thumbH = (range <= 0) ? this.getMaximumThumbSize().height
+ : (int) (trackH * (extent / range));
+ thumbH = Math.max(thumbH, this.getMinimumThumbSize().height);
+ thumbH = Math.min(thumbH, this.getMaximumThumbSize().height);
+
+ int thumbY = decrButton2Y - thumbH;
+ if (value < (sb.getMaximum() - sb.getVisibleAmount())) {
+ float thumbRange = trackH - thumbH;
+ thumbY = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
+ thumbY += incrButton2Y + incrButton2H;
+ }
+
+ /*
+ * If the buttons don't fit, allocate half of the available space to
+ * each and move the lower one (incrButton) down.
+ */
+ int sbAvailButtonH = (sbSize.height - sbInsetsH);
+ if (sbAvailButtonH < sbButtonsH) {
+ incrButtonH = decrButton2H = decrButtonH = incrButton2H = sbAvailButtonH / 4;
+ incrButtonY = sbSize.height - (sbInsets.bottom + incrButtonH);
+ }
+ this.decrButton.setBounds(itemX, decrButtonY, itemW, decrButtonH);
+ this.mySecondDecreaseButton.setBounds(itemX,
+ incrButtonY - decrButton2H, itemW, decrButton2H);
+ this.incrButton.setBounds(itemX, incrButtonY - 1, itemW,
+ incrButtonH + 1);
+ this.mySecondIncreaseButton.setBounds(itemX, decrButtonY + decrButtonH
+ - 1, itemW, incrButton2H + 1);
+
+ /*
+ * Update the trackRect field.
+ */
+ int itrackY = incrButton2Y + incrButton2H;
+ int itrackH = decrButton2Y - itrackY;
+ this.trackRect.setBounds(itemX, itrackY, itemW, itrackH);
+
+ /*
+ * If the thumb isn't going to fit, zero it's bounds. Otherwise make
+ * sure it fits between the buttons. Note that setting the thumbs bounds
+ * will cause a repaint.
+ */
+ if (thumbH >= (int) trackH) {
+ this.setThumbBounds(0, 0, 0, 0);
+ } else {
+ if ((thumbY + thumbH) > decrButton2Y) {
+ thumbY = decrButton2Y - thumbH;
+ }
+ if (thumbY < (incrButton2Y + incrButton2H)) {
+ thumbY = incrButton2Y + incrButton2H + 1;
+ }
+ this.setThumbBounds(itemX, thumbY, itemW, thumbH);
+ }
+ }
+
+ /**
+ * Lays out the horizontal scroll bar when the button policy is
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#ADJACENT}.
+ *
+ * @param sb
+ * Scroll bar.
+ */
+ protected void layoutHScrollbarAdjacent(JScrollBar sb) {
+ Dimension sbSize = sb.getSize();
+ Insets sbInsets = sb.getInsets();
+
+ /*
+ * Height and top edge of the buttons and thumb.
+ */
+ int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom);
+ int itemY = sbInsets.top;
+
+ boolean ltr = sb.getComponentOrientation().isLeftToRight();
+
+ /*
+ * Nominal locations of the buttons, assuming their preferred size will
+ * fit.
+ */
+ int decrButton2W = itemH;
+ int incrButtonW = itemH;
+ int incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
+ : sbInsets.left;
+ int decrButton2X = ltr ? incrButtonX - decrButton2W : incrButtonX
+ + decrButton2W;
+
+ /*
+ * The thumb must fit within the width left over after we subtract the
+ * preferredSize of the buttons and the insets.
+ */
+ int sbInsetsW = sbInsets.left + sbInsets.right;
+ int sbButtonsW = decrButton2W + incrButtonW;
+ float trackW = sbSize.width - (sbInsetsW + sbButtonsW);
+
+ /*
+ * Compute the width and origin of the thumb. Enforce the thumbs min/max
+ * dimensions. The case where the thumb is at the right edge is handled
+ * specially to avoid numerical problems in computing thumbX. If the
+ * thumb doesn't fit in the track (trackH) we'll hide it later.
+ */
+ float min = sb.getMinimum();
+ float max = sb.getMaximum();
+ float extent = sb.getVisibleAmount();
+ float range = max - min;
+ float value = sb.getValue();
+
+ int thumbW = (range <= 0) ? this.getMaximumThumbSize().width
+ : (int) (trackW * (extent / range));
+ thumbW = Math.max(thumbW, this.getMinimumThumbSize().width);
+ thumbW = Math.min(thumbW, this.getMaximumThumbSize().width);
+
+ int thumbX = ltr ? decrButton2X - thumbW : sbInsets.left;
+ if (value < (max - sb.getVisibleAmount())) {
+ float thumbRange = trackW - thumbW;
+ if (ltr) {
+ thumbX = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
+ } else {
+ thumbX = (int) (0.5f + (thumbRange * ((max - extent - value) / (range - extent))));
+ thumbX += decrButton2X + decrButton2W;
+ }
+ }
+
+ /*
+ * If the buttons don't fit, allocate half of the available space to
+ * each and move the right one over.
+ */
+ int sbAvailButtonW = (sbSize.width - sbInsetsW);
+ if (sbAvailButtonW < sbButtonsW) {
+ incrButtonW = decrButton2W = sbAvailButtonW / 2;
+ incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
+ : sbInsets.left;
+ }
+
+ this.mySecondDecreaseButton.setBounds(decrButton2X + (ltr ? 0 : -1),
+ itemY, decrButton2W + 1, itemH);
+ this.incrButton.setBounds(incrButtonX, itemY, incrButtonW, itemH);
+ this.decrButton.setBounds(0, 0, 0, 0);
+ this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
+
+ /*
+ * Update the trackRect field.
+ */
+ if (ltr) {
+ int itrackX = sbInsets.left;
+ int itrackW = decrButton2X - itrackX;
+ this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
+ } else {
+ int itrackX = decrButton2X + decrButton2W;
+ int itrackW = sbSize.width - itrackX;
+ this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
+ }
+
+ /*
+ * Make sure the thumb fits between the buttons. Note that setting the
+ * thumbs bounds causes a repaint.
+ */
+ if (thumbW >= (int) trackW) {
+ this.setThumbBounds(0, 0, 0, 0);
+ } else {
+ if (ltr) {
+ if (thumbX + thumbW > decrButton2X) {
+ thumbX = decrButton2X - thumbW;
+ }
+ if (thumbX < 0) {
+ thumbX = 1;
+ }
+ } else {
+ if (thumbX + thumbW > (sbSize.width - sbInsets.left)) {
+ thumbX = sbSize.width - sbInsets.left - thumbW;
+ }
+ if (thumbX < (decrButton2X + decrButton2W)) {
+ thumbX = decrButton2X + decrButton2W + 1;
+ }
+ }
+ this.setThumbBounds(thumbX, itemY, thumbW, itemH);
+ }
+ }
+
+ /**
+ * Lays out the horizontal scroll bar when the button policy is
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#NONE}.
+ *
+ * @param sb
+ * Scroll bar.
+ */
+ protected void layoutHScrollbarNone(JScrollBar sb) {
+ Dimension sbSize = sb.getSize();
+ Insets sbInsets = sb.getInsets();
+
+ /*
+ * Height and top edge of the buttons and thumb.
+ */
+ int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom);
+ int itemY = sbInsets.top;
+
+ boolean ltr = sb.getComponentOrientation().isLeftToRight();
+
+ /*
+ * Nominal locations of the buttons, assuming their preferred size will
+ * fit.
+ */
+ int decrButton2W = 0;
+ int incrButtonW = 0;
+ int incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
+ : sbInsets.left;
+ int decrButton2X = ltr ? incrButtonX - decrButton2W : incrButtonX
+ + decrButton2W;
+
+ /*
+ * The thumb must fit within the width left over after we subtract the
+ * preferredSize of the buttons and the insets.
+ */
+ int sbInsetsW = sbInsets.left + sbInsets.right;
+ int sbButtonsW = decrButton2W + incrButtonW;
+ float trackW = sbSize.width - (sbInsetsW + sbButtonsW);
+
+ /*
+ * Compute the width and origin of the thumb. Enforce the thumbs min/max
+ * dimensions. The case where the thumb is at the right edge is handled
+ * specially to avoid numerical problems in computing thumbX. If the
+ * thumb doesn't fit in the track (trackH) we'll hide it later.
+ */
+ float min = sb.getMinimum();
+ float max = sb.getMaximum();
+ float extent = sb.getVisibleAmount();
+ float range = max - min;
+ float value = sb.getValue();
+
+ int thumbW = (range <= 0) ? this.getMaximumThumbSize().width
+ : (int) (trackW * (extent / range));
+ thumbW = Math.max(thumbW, this.getMinimumThumbSize().width);
+ thumbW = Math.min(thumbW, this.getMaximumThumbSize().width);
+
+ int thumbX = ltr ? decrButton2X - thumbW : sbInsets.left;
+ if (value < (max - sb.getVisibleAmount())) {
+ float thumbRange = trackW - thumbW;
+ if (ltr) {
+ thumbX = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
+ } else {
+ thumbX = (int) (0.5f + (thumbRange * ((max - extent - value) / (range - extent))));
+ thumbX += decrButton2X + decrButton2W;
+ }
+ }
+
+ /*
+ * If the buttons don't fit, allocate half of the available space to
+ * each and move the right one over.
+ */
+ int sbAvailButtonW = (sbSize.width - sbInsetsW);
+ if (sbAvailButtonW < sbButtonsW) {
+ incrButtonW = decrButton2W = 0;
+ // incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
+ // : sbInsets.left;
+ }
+
+ this.incrButton.setBounds(0, 0, 0, 0);
+ this.decrButton.setBounds(0, 0, 0, 0);
+ this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
+ this.mySecondDecreaseButton.setBounds(0, 0, 0, 0);
+
+ /*
+ * Update the trackRect field.
+ */
+ if (ltr) {
+ int itrackX = sbInsets.left;
+ int itrackW = decrButton2X - itrackX;
+ this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
+ } else {
+ int itrackX = decrButton2X + decrButton2W;
+ int itrackW = sbSize.width - itrackX;
+ this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
+ }
+
+ /*
+ * Make sure the thumb fits between the buttons. Note that setting the
+ * thumbs bounds causes a repaint.
+ */
+ if (thumbW >= (int) trackW) {
+ this.setThumbBounds(0, 0, 0, 0);
+ } else {
+ if (ltr) {
+ if (thumbX + thumbW > decrButton2X) {
+ thumbX = decrButton2X - thumbW;
+ }
+ if (thumbX < 0) {
+ thumbX = 1;
+ }
+ } else {
+ if (thumbX + thumbW > (sbSize.width - sbInsets.left)) {
+ thumbX = sbSize.width - sbInsets.left - thumbW;
+ }
+ if (thumbX < (decrButton2X + decrButton2W)) {
+ thumbX = decrButton2X + decrButton2W + 1;
+ }
+ }
+ this.setThumbBounds(thumbX, itemY, thumbW, itemH);
+ }
+ }
+
+ /**
+ * Lays out the horizontal scroll bar when the button policy is
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE}.
+ *
+ * @param sb
+ * Scroll bar.
+ */
+ protected void layoutHScrollbarMultiple(JScrollBar sb) {
+ Dimension sbSize = sb.getSize();
+ Insets sbInsets = sb.getInsets();
+
+ /*
+ * Height and top edge of the buttons and thumb.
+ */
+ int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom);
+ int itemY = sbInsets.top;
+
+ boolean ltr = sb.getComponentOrientation().isLeftToRight();
+
+ /*
+ * Nominal locations of the buttons, assuming their preferred size will
+ * fit.
+ */
+ int decrButton2W = itemH;
+ int decrButtonW = itemH;
+ int incrButtonW = itemH;
+ int incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
+ : sbInsets.left;
+ int decrButton2X = ltr ? incrButtonX - decrButton2W : incrButtonX
+ + decrButton2W;
+ int decrButtonX = ltr ? sbInsets.left : sbSize.width - sbInsets.right
+ - decrButtonW;
+
+ /*
+ * The thumb must fit within the width left over after we subtract the
+ * preferredSize of the buttons and the insets.
+ */
+ int sbInsetsW = sbInsets.left + sbInsets.right;
+ int sbButtonsW = decrButton2W + incrButtonW + decrButtonW;
+ float trackW = sbSize.width - (sbInsetsW + sbButtonsW);
+
+ /*
+ * Compute the width and origin of the thumb. Enforce the thumbs min/max
+ * dimensions. The case where the thumb is at the right edge is handled
+ * specially to avoid numerical problems in computing thumbX. If the
+ * thumb doesn't fit in the track (trackH) we'll hide it later.
+ */
+ float min = sb.getMinimum();
+ float max = sb.getMaximum();
+ float extent = sb.getVisibleAmount();
+ float range = max - min;
+ float value = sb.getValue();
+
+ int thumbW = (range <= 0) ? this.getMaximumThumbSize().width
+ : (int) (trackW * (extent / range));
+ thumbW = Math.max(thumbW, this.getMinimumThumbSize().width);
+ thumbW = Math.min(thumbW, this.getMaximumThumbSize().width);
+
+ int thumbX = ltr ? decrButton2X - thumbW : sbInsets.left;
+ if (value < (max - sb.getVisibleAmount())) {
+ float thumbRange = trackW - thumbW;
+ if (ltr) {
+ thumbX = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
+ thumbX += decrButtonX + decrButtonW;
+ } else {
+ thumbX = (int) (0.5f + (thumbRange * ((max - extent - value) / (range - extent))));
+ thumbX += decrButton2X + decrButton2W;
+ }
+ }
+
+ /*
+ * If the buttons don't fit, allocate half of the available space to
+ * each and move the right one over.
+ */
+ int sbAvailButtonW = (sbSize.width - sbInsetsW);
+ if (sbAvailButtonW < sbButtonsW) {
+ incrButtonW = decrButton2W = decrButtonW = sbAvailButtonW / 2;
+ incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
+ : sbInsets.left;
+ }
+
+ this.mySecondDecreaseButton.setBounds(decrButton2X + (ltr ? 0 : -1),
+ itemY, decrButton2W + 1, itemH);
+ this.incrButton.setBounds(incrButtonX, itemY, incrButtonW, itemH);
+ this.decrButton.setBounds(decrButtonX, itemY, decrButtonW, itemH);
+ this.mySecondIncreaseButton.setBounds(0, 0, 0, 0);
+
+ /*
+ * Update the trackRect field.
+ */
+ if (ltr) {
+ int itrackX = decrButtonX + decrButtonW;
+ int itrackW = decrButton2X - itrackX;
+ this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
+ } else {
+ int itrackX = decrButton2X + decrButton2W;
+ int itrackW = decrButtonX - itrackX;
+ this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
+ }
+
+ /*
+ * Make sure the thumb fits between the buttons. Note that setting the
+ * thumbs bounds causes a repaint.
+ */
+ if (thumbW >= (int) trackW) {
+ this.setThumbBounds(0, 0, 0, 0);
+ } else {
+ if (ltr) {
+ if (thumbX + thumbW > decrButton2X) {
+ thumbX = decrButton2X - thumbW;
+ }
+ if (thumbX < (decrButtonX + decrButtonW)) {
+ thumbX = decrButtonX + decrButtonW + 1;
+ }
+ } else {
+ if (thumbX + thumbW > decrButtonX) {
+ thumbX = decrButtonX - thumbW;
+ }
+ if (thumbX < (decrButton2X + decrButton2W)) {
+ thumbX = decrButton2X + decrButton2W + 1;
+ }
+ }
+ this.setThumbBounds(thumbX, itemY, thumbW, itemH);
+ }
+ }
+
+ /**
+ * Lays out the horizontal scroll bar when the button policy is
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind#MULTIPLE}.
+ *
+ * @param sb
+ * Scroll bar.
+ */
+ protected void layoutHScrollbarMultipleBoth(JScrollBar sb) {
+ Dimension sbSize = sb.getSize();
+ Insets sbInsets = sb.getInsets();
+
+ /*
+ * Height and top edge of the buttons and thumb.
+ */
+ int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom);
+ int itemY = sbInsets.top;
+
+ boolean ltr = sb.getComponentOrientation().isLeftToRight();
+
+ /*
+ * Nominal locations of the buttons, assuming their preferred size will
+ * fit.
+ */
+ int decrButton2W = itemH;
+ int incrButton2W = itemH;
+ int decrButtonW = itemH;
+ int incrButtonW = itemH;
+
+ int incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
+ : sbInsets.left;
+ int decrButton2X = ltr ? incrButtonX - decrButton2W : incrButtonX
+ + decrButton2W;
+ int decrButtonX = ltr ? sbInsets.left : sbSize.width - sbInsets.right
+ - decrButtonW;
+ int incrButton2X = ltr ? decrButtonX + decrButtonW : decrButtonX
+ - incrButton2W;
+
+ /*
+ * The thumb must fit within the width left over after we subtract the
+ * preferredSize of the buttons and the insets.
+ */
+ int sbInsetsW = sbInsets.left + sbInsets.right;
+ int sbButtonsW = decrButton2W + incrButtonW + decrButtonW
+ + incrButton2W;
+ float trackW = sbSize.width - (sbInsetsW + sbButtonsW);
+
+ /*
+ * Compute the width and origin of the thumb. Enforce the thumbs min/max
+ * dimensions. The case where the thumb is at the right edge is handled
+ * specially to avoid numerical problems in computing thumbX. If the
+ * thumb doesn't fit in the track (trackH) we'll hide it later.
+ */
+ float min = sb.getMinimum();
+ float max = sb.getMaximum();
+ float extent = sb.getVisibleAmount();
+ float range = max - min;
+ float value = sb.getValue();
+
+ int thumbW = (range <= 0) ? this.getMaximumThumbSize().width
+ : (int) (trackW * (extent / range));
+ thumbW = Math.max(thumbW, this.getMinimumThumbSize().width);
+ thumbW = Math.min(thumbW, this.getMaximumThumbSize().width);
+
+ int thumbX = ltr ? decrButton2X - thumbW : sbInsets.left;
+ if (value < (max - sb.getVisibleAmount())) {
+ float thumbRange = trackW - thumbW;
+ if (ltr) {
+ thumbX = (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
+ thumbX += incrButton2X + incrButton2W;
+ } else {
+ thumbX = (int) (0.5f + (thumbRange * ((max - extent - value) / (range - extent))));
+ thumbX += decrButton2X + decrButton2W;
+ }
+ }
+
+ /*
+ * If the buttons don't fit, allocate half of the available space to
+ * each and move the right one over.
+ */
+ int sbAvailButtonW = (sbSize.width - sbInsetsW);
+ if (sbAvailButtonW < sbButtonsW) {
+ incrButtonW = decrButton2W = decrButtonW = incrButton2W = sbAvailButtonW / 4;
+ incrButtonX = ltr ? sbSize.width - (sbInsets.right + incrButtonW)
+ : sbInsets.left;
+ }
+
+ this.mySecondDecreaseButton.setBounds(decrButton2X + (ltr ? 0 : -1),
+ itemY, decrButton2W + 1, itemH);
+ this.mySecondIncreaseButton.setBounds(incrButton2X + (ltr ? -1 : 0),
+ itemY, incrButton2W + 1, itemH);
+ this.incrButton.setBounds(incrButtonX, itemY, incrButtonW, itemH);
+ this.decrButton.setBounds(decrButtonX, itemY, decrButtonW, itemH);
+
+ /*
+ * Update the trackRect field.
+ */
+ if (ltr) {
+ int itrackX = incrButton2X + incrButton2W;
+ int itrackW = decrButton2X - itrackX;
+ this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
+ } else {
+ int itrackX = decrButton2X + decrButton2W;
+ int itrackW = incrButton2X - itrackX;
+ this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
+ }
+
+ /*
+ * Make sure the thumb fits between the buttons. Note that setting the
+ * thumbs bounds causes a repaint.
+ */
+ if (thumbW >= (int) trackW) {
+ this.setThumbBounds(0, 0, 0, 0);
+ } else {
+ if (ltr) {
+ if (thumbX + thumbW > decrButton2X) {
+ thumbX = decrButton2X - thumbW;
+ }
+ if (thumbX < (incrButton2X + incrButton2W)) {
+ thumbX = incrButton2X + incrButton2W + 1;
+ }
+ } else {
+ if (thumbX + thumbW > incrButton2X) {
+ thumbX = incrButton2X - thumbW;
+ }
+ if (thumbX < (decrButton2X + decrButton2W)) {
+ thumbX = decrButton2X + decrButton2W + 1;
+ }
+ }
+ this.setThumbBounds(thumbX, itemY, thumbW, itemH);
+ }
+ }
+
+ /**
+ * Returns the memory usage string.
+ *
+ * @return The memory usage string.
+ */
+ public static String getMemoryUsage() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("SubstanceScrollBarUI: \n");
+ sb.append("\t" + thumbHorizontalMap.size() + " thumb horizontal, "
+ + thumbVerticalMap.size() + " thumb vertical");
+ sb.append("\t" + trackHorizontalMap.size() + " track horizontal, "
+ + trackVerticalMap.size() + " track vertical");
+ return sb.toString();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicScrollBarUI#createTrackListener()
+ */
+ @Override
+ protected TrackListener createTrackListener() {
+ return new SubstanceTrackListener();
+ }
+
+ /**
+ * Track mouse drags. Had to take this one from BasicScrollBarUI since the
+ * setValueForm method is private.
+ */
+ protected class SubstanceTrackListener extends TrackListener {
+ /**
+ * Current scroll direction.
+ */
+ private transient int direction = +1;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mouseReleased
+ * (java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (SubstanceScrollBarUI.this.isDragging) {
+ SubstanceScrollBarUI.this.updateThumbState(e.getX(), e.getY());
+ }
+ if (SwingUtilities.isRightMouseButton(e)
+ || (!SubstanceScrollBarUI.this
+ .getSupportsAbsolutePositioning() && SwingUtilities
+ .isMiddleMouseButton(e)))
+ return;
+ if (!SubstanceScrollBarUI.this.scrollbar.isEnabled())
+ return;
+
+ Rectangle r = SubstanceScrollBarUI.this.getTrackBounds();
+ SubstanceScrollBarUI.this.scrollbar.repaint(r.x, r.y, r.width,
+ r.height);
+
+ SubstanceScrollBarUI.this.trackHighlight = NO_HIGHLIGHT;
+ SubstanceScrollBarUI.this.isDragging = false;
+ this.offset = 0;
+ SubstanceScrollBarUI.this.scrollTimer.stop();
+ SubstanceScrollBarUI.this.scrollbar.setValueIsAdjusting(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mousePressed
+ * (java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mousePressed(MouseEvent e) {
+ // If the mouse is pressed above the "thumb" component then reduce
+ // the scrollbars value by one page ("page up"), otherwise increase
+ // it by one page. If there is no thumb then page up if the mouse is
+ // in the upper half of the track.
+ if (SwingUtilities.isRightMouseButton(e)
+ || (!SubstanceScrollBarUI.this
+ .getSupportsAbsolutePositioning() && SwingUtilities
+ .isMiddleMouseButton(e)))
+ return;
+ if (!SubstanceScrollBarUI.this.scrollbar.isEnabled())
+ return;
+
+ if (!SubstanceScrollBarUI.this.scrollbar.hasFocus()
+ && SubstanceScrollBarUI.this.scrollbar
+ .isRequestFocusEnabled()) {
+ SubstanceScrollBarUI.this.scrollbar.requestFocus();
+ }
+
+ SubstanceScrollBarUI.this.scrollbar.setValueIsAdjusting(true);
+
+ this.currentMouseX = e.getX();
+ this.currentMouseY = e.getY();
+
+ // Clicked in the Thumb area?
+ if (SubstanceScrollBarUI.this.getThumbBounds().contains(
+ this.currentMouseX, this.currentMouseY)) {
+ switch (SubstanceScrollBarUI.this.scrollbar.getOrientation()) {
+ case JScrollBar.VERTICAL:
+ this.offset = this.currentMouseY
+ - SubstanceScrollBarUI.this.getThumbBounds().y;
+ break;
+ case JScrollBar.HORIZONTAL:
+ this.offset = this.currentMouseX
+ - SubstanceScrollBarUI.this.getThumbBounds().x;
+ break;
+ }
+ SubstanceScrollBarUI.this.isDragging = true;
+ return;
+ } else if (SubstanceScrollBarUI.this
+ .getSupportsAbsolutePositioning()
+ && SwingUtilities.isMiddleMouseButton(e)) {
+ switch (SubstanceScrollBarUI.this.scrollbar.getOrientation()) {
+ case JScrollBar.VERTICAL:
+ this.offset = SubstanceScrollBarUI.this.getThumbBounds().height / 2;
+ break;
+ case JScrollBar.HORIZONTAL:
+ this.offset = SubstanceScrollBarUI.this.getThumbBounds().width / 2;
+ break;
+ }
+ SubstanceScrollBarUI.this.isDragging = true;
+ this.setValueFrom(e);
+ return;
+ }
+ SubstanceScrollBarUI.this.isDragging = false;
+
+ Dimension sbSize = SubstanceScrollBarUI.this.scrollbar.getSize();
+ this.direction = +1;
+
+ switch (SubstanceScrollBarUI.this.scrollbar.getOrientation()) {
+ case JScrollBar.VERTICAL:
+ if (SubstanceScrollBarUI.this.getThumbBounds().isEmpty()) {
+ int scrollbarCenter = sbSize.height / 2;
+ this.direction = (this.currentMouseY < scrollbarCenter) ? -1
+ : +1;
+ } else {
+ int thumbY = SubstanceScrollBarUI.this.getThumbBounds().y;
+ this.direction = (this.currentMouseY < thumbY) ? -1 : +1;
+ }
+ break;
+ case JScrollBar.HORIZONTAL:
+ if (SubstanceScrollBarUI.this.getThumbBounds().isEmpty()) {
+ int scrollbarCenter = sbSize.width / 2;
+ this.direction = (this.currentMouseX < scrollbarCenter) ? -1
+ : +1;
+ } else {
+ int thumbX = SubstanceScrollBarUI.this.getThumbBounds().x;
+ this.direction = (this.currentMouseX < thumbX) ? -1 : +1;
+ }
+ if (!SubstanceScrollBarUI.this.scrollbar
+ .getComponentOrientation().isLeftToRight()) {
+ this.direction = -this.direction;
+ }
+ break;
+ }
+ SubstanceScrollBarUI.this.scrollByBlock(this.direction);
+
+ SubstanceScrollBarUI.this.scrollTimer.stop();
+ SubstanceScrollBarUI.this.scrollListener
+ .setDirection(this.direction);
+ SubstanceScrollBarUI.this.scrollListener.setScrollByBlock(true);
+ this.startScrollTimerIfNecessary();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mouseDragged
+ * (java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ // Set the models value to the position of the thumb's top of
+ // Vertical scrollbar, or the left/right of Horizontal scrollbar in
+ // LTR / RTL scrollbar relative to the origin of
+ // the track.
+ if (SwingUtilities.isRightMouseButton(e)
+ || (!SubstanceScrollBarUI.this
+ .getSupportsAbsolutePositioning() && SwingUtilities
+ .isMiddleMouseButton(e)))
+ return;
+ if (!SubstanceScrollBarUI.this.scrollbar.isEnabled()
+ || SubstanceScrollBarUI.this.getThumbBounds().isEmpty()) {
+ return;
+ }
+ if (SubstanceScrollBarUI.this.isDragging) {
+ this.setValueFrom(e);
+ } else {
+ this.currentMouseX = e.getX();
+ this.currentMouseY = e.getY();
+ SubstanceScrollBarUI.this.updateThumbState(this.currentMouseX,
+ this.currentMouseY);
+ this.startScrollTimerIfNecessary();
+ }
+ }
+
+ /**
+ * Sets the scrollbar value based on the specified mouse event.
+ *
+ * @param e
+ * Mouse event.
+ */
+ private void setValueFrom(MouseEvent e) {
+ boolean active = SubstanceScrollBarUI.this.isThumbRollover();
+ BoundedRangeModel model = SubstanceScrollBarUI.this.scrollbar
+ .getModel();
+ Rectangle thumbR = SubstanceScrollBarUI.this.getThumbBounds();
+ int thumbMin = 0, thumbMax = 0, thumbPos;
+
+ ScrollPaneButtonPolicyKind buttonPolicy = SubstanceCoreUtilities
+ .getScrollPaneButtonsPolicyKind(SubstanceScrollBarUI.this.scrollbar);
+
+ if (SubstanceScrollBarUI.this.scrollbar.getOrientation() == JScrollBar.VERTICAL) {
+ switch (buttonPolicy) {
+ case OPPOSITE:
+ thumbMin = SubstanceScrollBarUI.this.decrButton.getY()
+ + SubstanceScrollBarUI.this.decrButton.getHeight();
+ thumbMax = SubstanceScrollBarUI.this.incrButton.getY()
+ - thumbR.height;
+ break;
+ case ADJACENT:
+ thumbMin = 0;
+ thumbMax = SubstanceScrollBarUI.this.mySecondDecreaseButton
+ .getY()
+ - thumbR.height;
+ break;
+ case NONE:
+ thumbMin = 0;
+ thumbMax = SubstanceScrollBarUI.this.scrollbar.getSize().height
+ - SubstanceScrollBarUI.this.scrollbar.getInsets().bottom
+ - thumbR.height;
+ break;
+ case MULTIPLE:
+ thumbMin = SubstanceScrollBarUI.this.decrButton.getY()
+ + SubstanceScrollBarUI.this.decrButton.getHeight();
+ thumbMax = SubstanceScrollBarUI.this.mySecondDecreaseButton
+ .getY()
+ - thumbR.height;
+ break;
+ case MULTIPLE_BOTH:
+ thumbMin = SubstanceScrollBarUI.this.mySecondIncreaseButton
+ .getY()
+ + SubstanceScrollBarUI.this.mySecondIncreaseButton
+ .getHeight();
+ thumbMax = SubstanceScrollBarUI.this.mySecondDecreaseButton
+ .getY()
+ - thumbR.height;
+ break;
+ }
+
+ thumbPos = Math.min(thumbMax, Math.max(thumbMin,
+ (e.getY() - this.offset)));
+ SubstanceScrollBarUI.this.setThumbBounds(thumbR.x, thumbPos,
+ thumbR.width, thumbR.height);
+ } else {
+ if (SubstanceScrollBarUI.this.scrollbar
+ .getComponentOrientation().isLeftToRight()) {
+ switch (buttonPolicy) {
+ case OPPOSITE:
+ thumbMin = SubstanceScrollBarUI.this.decrButton.getX()
+ + SubstanceScrollBarUI.this.decrButton
+ .getWidth();
+ thumbMax = SubstanceScrollBarUI.this.incrButton.getX()
+ - thumbR.width;
+ break;
+ case ADJACENT:
+ thumbMin = 0;
+ thumbMax = SubstanceScrollBarUI.this.mySecondDecreaseButton
+ .getX()
+ - thumbR.width;
+ break;
+ case MULTIPLE:
+ thumbMin = SubstanceScrollBarUI.this.decrButton.getX()
+ + SubstanceScrollBarUI.this.decrButton
+ .getWidth();
+ thumbMax = SubstanceScrollBarUI.this.mySecondDecreaseButton
+ .getX()
+ - thumbR.width;
+ break;
+ case MULTIPLE_BOTH:
+ thumbMin = SubstanceScrollBarUI.this.mySecondIncreaseButton
+ .getX()
+ + SubstanceScrollBarUI.this.mySecondIncreaseButton
+ .getWidth();
+ thumbMax = SubstanceScrollBarUI.this.mySecondDecreaseButton
+ .getX()
+ - thumbR.width;
+ break;
+ case NONE:
+ thumbMin = 0;
+ thumbMax = SubstanceScrollBarUI.this.scrollbar
+ .getSize().width
+ - SubstanceScrollBarUI.this.scrollbar
+ .getInsets().right - thumbR.width;
+ break;
+ }
+ } else {
+ switch (buttonPolicy) {
+ case OPPOSITE:
+ thumbMin = SubstanceScrollBarUI.this.incrButton.getX()
+ + SubstanceScrollBarUI.this.incrButton
+ .getWidth();
+ thumbMax = SubstanceScrollBarUI.this.decrButton.getX()
+ - thumbR.width;
+ break;
+ case ADJACENT:
+ thumbMin = SubstanceScrollBarUI.this.mySecondDecreaseButton
+ .getX()
+ + SubstanceScrollBarUI.this.mySecondDecreaseButton
+ .getWidth();
+ thumbMax = SubstanceScrollBarUI.this.scrollbar
+ .getSize().width
+ - SubstanceScrollBarUI.this.scrollbar
+ .getInsets().right - thumbR.width;
+ break;
+ case MULTIPLE:
+ thumbMin = SubstanceScrollBarUI.this.mySecondDecreaseButton
+ .getX()
+ + SubstanceScrollBarUI.this.mySecondDecreaseButton
+ .getWidth();
+ thumbMax = SubstanceScrollBarUI.this.decrButton.getX()
+ - thumbR.width;
+ break;
+ case MULTIPLE_BOTH:
+ thumbMin = SubstanceScrollBarUI.this.mySecondDecreaseButton
+ .getX()
+ + SubstanceScrollBarUI.this.mySecondDecreaseButton
+ .getWidth();
+ thumbMax = SubstanceScrollBarUI.this.mySecondIncreaseButton
+ .getX()
+ - thumbR.width;
+ break;
+ case NONE:
+ thumbMin = 0;
+ thumbMax = SubstanceScrollBarUI.this.scrollbar
+ .getSize().width
+ - SubstanceScrollBarUI.this.scrollbar
+ .getInsets().right - thumbR.width;
+ break;
+ }
+ }
+ // System.out.println(thumbMin + " : " + thumbMax + " : "
+ // + (e.getX() - offset));
+ thumbPos = Math.min(thumbMax, Math.max(thumbMin,
+ (e.getX() - this.offset)));
+ SubstanceScrollBarUI.this.setThumbBounds(thumbPos, thumbR.y,
+ thumbR.width, thumbR.height);
+ }
+
+ /*
+ * Set the scrollbars value. If the thumb has reached the end of the
+ * scrollbar, then just set the value to its maximum. Otherwise
+ * compute the value as accurately as possible.
+ */
+ if (thumbPos == thumbMax) {
+ if (SubstanceScrollBarUI.this.scrollbar.getOrientation() == JScrollBar.VERTICAL
+ || SubstanceScrollBarUI.this.scrollbar
+ .getComponentOrientation().isLeftToRight()) {
+ SubstanceScrollBarUI.this.scrollbar.setValue(model
+ .getMaximum()
+ - model.getExtent());
+ } else {
+ SubstanceScrollBarUI.this.scrollbar.setValue(model
+ .getMinimum());
+ }
+ } else {
+ float valueMax = model.getMaximum() - model.getExtent();
+ float valueRange = valueMax - model.getMinimum();
+ float thumbValue = thumbPos - thumbMin;
+ float thumbRange = thumbMax - thumbMin;
+ int value;
+ if (SubstanceScrollBarUI.this.scrollbar.getOrientation() == JScrollBar.VERTICAL
+ || SubstanceScrollBarUI.this.scrollbar
+ .getComponentOrientation().isLeftToRight()) {
+ value = (int) (0.5 + ((thumbValue / thumbRange) * valueRange));
+ } else {
+ value = (int) (0.5 + (((thumbMax - thumbPos) / thumbRange) * valueRange));
+ }
+
+ SubstanceScrollBarUI.this.scrollbar.setValue(value
+ + model.getMinimum());
+ }
+ SubstanceScrollBarUI.this.setThumbRollover(active);
+ }
+
+ /**
+ * If necessary, starts the scroll timer.
+ */
+ private void startScrollTimerIfNecessary() {
+ if (SubstanceScrollBarUI.this.scrollTimer.isRunning()) {
+ return;
+ }
+ switch (SubstanceScrollBarUI.this.scrollbar.getOrientation()) {
+ case JScrollBar.VERTICAL:
+ if (this.direction > 0) {
+ if (SubstanceScrollBarUI.this.getThumbBounds().y
+ + SubstanceScrollBarUI.this.getThumbBounds().height < ((SubstanceTrackListener) SubstanceScrollBarUI.this.trackListener).currentMouseY) {
+ SubstanceScrollBarUI.this.scrollTimer.start();
+ }
+ } else if (SubstanceScrollBarUI.this.getThumbBounds().y > ((SubstanceTrackListener) SubstanceScrollBarUI.this.trackListener).currentMouseY) {
+ SubstanceScrollBarUI.this.scrollTimer.start();
+ }
+ break;
+ case JScrollBar.HORIZONTAL:
+ if (this.direction > 0) {
+ if (SubstanceScrollBarUI.this.getThumbBounds().x
+ + SubstanceScrollBarUI.this.getThumbBounds().width < ((SubstanceTrackListener) SubstanceScrollBarUI.this.trackListener).currentMouseX) {
+ SubstanceScrollBarUI.this.scrollTimer.start();
+ }
+ } else if (SubstanceScrollBarUI.this.getThumbBounds().x > ((SubstanceTrackListener) SubstanceScrollBarUI.this.trackListener).currentMouseX) {
+ SubstanceScrollBarUI.this.scrollTimer.start();
+ }
+ break;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mouseMoved(
+ * java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ if (!SubstanceScrollBarUI.this.isDragging) {
+ SubstanceScrollBarUI.this.updateThumbState(e.getX(), e.getY());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mouseExited
+ * (java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (!SubstanceScrollBarUI.this.isDragging) {
+ SubstanceScrollBarUI.this.setThumbRollover(false);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicScrollBarUI#createArrowButtonListener()
+ */
+ @Override
+ protected ArrowButtonListener createArrowButtonListener() {
+ return new SubstanceArrowButtonListener();
+ }
+
+ /**
+ * Listener on arrow buttons. Need to override the super implementation for
+ * the {@link ScrollPaneButtonPolicyKind#MULTIPLE_BOTH} policy.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class SubstanceArrowButtonListener extends ArrowButtonListener {
+ /**
+ * Because we are handling both mousePressed and Actions we need to make
+ * sure we don't fire under both conditions. (keyfocus on scrollbars
+ * causes action without mousePress
+ */
+ boolean handledEvent;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollBarUI$ArrowButtonListener#mousePressed
+ * (java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (!SubstanceScrollBarUI.this.scrollbar.isEnabled()) {
+ return;
+ }
+ // not an unmodified left mouse button
+ // if(e.getModifiers() != InputEvent.BUTTON1_MASK) {return; }
+ if (!SwingUtilities.isLeftMouseButton(e)) {
+ return;
+ }
+
+ int direction = ((e.getSource() == SubstanceScrollBarUI.this.incrButton) || (e
+ .getSource() == SubstanceScrollBarUI.this.mySecondIncreaseButton)) ? 1
+ : -1;
+
+ SubstanceScrollBarUI.this.scrollByUnit(direction);
+ SubstanceScrollBarUI.this.scrollTimer.stop();
+ SubstanceScrollBarUI.this.scrollListener.setDirection(direction);
+ SubstanceScrollBarUI.this.scrollListener.setScrollByBlock(false);
+ SubstanceScrollBarUI.this.scrollTimer.start();
+
+ this.handledEvent = true;
+ if (!SubstanceScrollBarUI.this.scrollbar.hasFocus()
+ && SubstanceScrollBarUI.this.scrollbar
+ .isRequestFocusEnabled()) {
+ SubstanceScrollBarUI.this.scrollbar.requestFocus();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollBarUI$ArrowButtonListener#mouseReleased
+ * (java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ SubstanceScrollBarUI.this.scrollTimer.stop();
+ this.handledEvent = false;
+ SubstanceScrollBarUI.this.scrollbar.setValueIsAdjusting(false);
+ }
+ }
+
+ /**
+ * Updates the thumb state based on the coordinates.
+ *
+ * @param x
+ * X coordinate.
+ * @param y
+ * Y coordinate.
+ */
+ private void updateThumbState(int x, int y) {
+ Rectangle rect = this.getThumbBounds();
+
+ this.setThumbRollover(rect.contains(x, y));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollBarUI#getPreferredSize(javax.swing.
+ * JComponent)
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
+ return new Dimension(scrollBarWidth, Math.max(48,
+ 5 * scrollBarWidth));
+ } else {
+ return new Dimension(Math.max(48, 5 * scrollBarWidth),
+ scrollBarWidth);
+ }
+ }
+
+ /**
+ * Composite button model that tracks changes to one primary and any number
+ * of secondary button models for composite rollover effects. This model can
+ * be used to "simulate" rollover effects on the primary component when the
+ * actual rollover happens on one of the secondary components. An example is
+ * a scroll bar. When the mouse enters one of the scroll buttons, the scroll
+ * track is highlighted as well.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class CompositeButtonModel extends DefaultButtonModel {
+ /**
+ * The primary model.
+ */
+ protected ButtonModel primaryModel;
+
+ /**
+ * The secondary models.
+ */
+ protected ButtonModel[] secondaryModels;
+
+ protected ChangeListener listener;
+
+ /**
+ * Creates a new composite button model.
+ *
+ * @param primaryModel
+ * The primary model.
+ * @param secondaryButtons
+ * The secondary buttons.
+ */
+ public CompositeButtonModel(ButtonModel primaryModel,
+ AbstractButton... secondaryButtons) {
+ this.primaryModel = primaryModel;
+ List<ButtonModel> bmList = new LinkedList<ButtonModel>();
+ for (AbstractButton secondary : secondaryButtons) {
+ if (secondary != null) {
+ bmList.add(secondary.getModel());
+ }
+ }
+ this.secondaryModels = bmList.toArray(new ButtonModel[0]);
+
+ this.listener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ syncModels();
+ }
+ };
+ syncModels();
+ }
+
+ private void syncModels() {
+ this.setEnabled(this.primaryModel.isEnabled());
+ this.setSelected(this.primaryModel.isSelected());
+
+ boolean isArmed = this.primaryModel.isArmed();
+ for (ButtonModel secondary : this.secondaryModels) {
+ isArmed = isArmed || secondary.isArmed();
+ }
+ this.setArmed(isArmed);
+
+ boolean isPressed = this.primaryModel.isPressed();
+ for (ButtonModel secondary : this.secondaryModels) {
+ isPressed = isPressed || secondary.isPressed();
+ }
+ this.setPressed(isPressed);
+
+ boolean isRollover = this.primaryModel.isRollover();
+ for (ButtonModel secondary : this.secondaryModels) {
+ isRollover = isRollover || secondary.isRollover();
+ }
+ this.setRollover(isRollover);
+ }
+
+ public void registerListeners() {
+ this.primaryModel.addChangeListener(this.listener);
+ for (ButtonModel secondary : this.secondaryModels) {
+ secondary.addChangeListener(this.listener);
+ }
+ }
+
+ public void unregisterListeners() {
+ this.primaryModel.removeChangeListener(this.listener);
+ for (ButtonModel secondary : this.secondaryModels) {
+ secondary.removeChangeListener(this.listener);
+ }
+ this.listener = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceScrollPaneUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceScrollPaneUI.java
new file mode 100644
index 0000000..2e7efcb
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceScrollPaneUI.java
@@ -0,0 +1,691 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicScrollPaneUI;
+import javax.swing.table.JTableHeader;
+
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.scroll.SubstanceScrollPaneBorder;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
+import org.pushingpixels.trident.ease.TimelineEase;
+
+/**
+ * UI for scroll panes in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceScrollPaneUI extends BasicScrollPaneUI {
+ /**
+ * Property change listener on
+ * {@link SubstanceLookAndFeel#SCROLL_PANE_BUTTONS_POLICY},
+ * {@link SubstanceLookAndFeel#WATERMARK_VISIBLE} and
+ * <code>layoutManager</code> properties.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Listener on the vertical scroll bar. Installed for the smart tree scroll
+ * (see {@link SubstanceLookAndFeel#TREE_SMART_SCROLL_ANIMATION_KIND}.
+ */
+ protected ChangeListener substanceVerticalScrollbarChangeListener;
+
+ /**
+ * Timeline of the current horizontal scroll under smart tree scroll mode.
+ */
+ protected Timeline horizontalScrollTimeline;
+
+ /**
+ * Creates new UI delegate.
+ *
+ * @param comp
+ * Component.
+ * @return UI delegate for the component.
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceScrollPaneUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollPaneUI#installDefaults(javax.swing.
+ * JScrollPane)
+ */
+ @Override
+ protected void installDefaults(final JScrollPane scrollpane) {
+ super.installDefaults(scrollpane);
+ if (SubstanceCoreUtilities.toDrawWatermark(scrollpane)
+ && (SubstanceLookAndFeel.getCurrentSkin(scrollpane)
+ .getWatermark() != null)) {
+ scrollpane.setOpaque(false);
+ scrollpane.getViewport().setOpaque(false);
+ }
+ scrollpane.setLayout(new AdjustedLayout((ScrollPaneLayout) scrollpane
+ .getLayout()));
+
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // System.out.println("Installing");
+ installTableHeaderCornerFiller(scrollpane);
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollPaneUI#uninstallDefaults(javax.swing
+ * .JScrollPane)
+ */
+ @Override
+ protected void uninstallDefaults(JScrollPane c) {
+ Component upperRight = c.getCorner(JScrollPane.UPPER_RIGHT_CORNER);
+ if (upperRight instanceof UIResource) {
+ c.setCorner(JScrollPane.UPPER_RIGHT_CORNER, null);
+ }
+ Component upperLeft = c.getCorner(JScrollPane.UPPER_LEFT_CORNER);
+ if (upperLeft instanceof UIResource) {
+ c.setCorner(JScrollPane.UPPER_LEFT_CORNER, null);
+ }
+
+ LayoutManager lm = scrollpane.getLayout();
+ if (lm instanceof AdjustedLayout) {
+ c.setLayout(((AdjustedLayout) lm).delegate);
+ }
+ super.uninstallDefaults(c);
+ }
+
+ // /*
+ // * (non-Javadoc)
+ // *
+ // * @see
+ // javax.swing.plaf.basic.BasicScrollPaneUI#uninstallDefaults(javax.swing.
+ // JScrollPane)
+ // */
+ // @Override
+ // protected void uninstallDefaults(JScrollPane c) {
+ // super.uninstallDefaults(c);
+ // ScrollPaneSelector.uninstallScrollPaneSelector(scrollpane);
+ // }
+ //
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollPaneUI#installListeners(javax.swing
+ * .JScrollPane)
+ */
+ @Override
+ protected void installListeners(final JScrollPane c) {
+ super.installListeners(c);
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (SubstanceLookAndFeel.SCROLL_PANE_BUTTONS_POLICY.equals(evt
+ .getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ c.getHorizontalScrollBar().doLayout();
+ c.getVerticalScrollBar().doLayout();
+ }
+ });
+ }
+ if (SubstanceLookAndFeel.WATERMARK_VISIBLE.equals(evt
+ .getPropertyName())) {
+ boolean toBleed = SubstanceCoreUtilities.toDrawWatermark(c);
+ c.setOpaque(!toBleed);
+ c.getViewport().setOpaque(!toBleed);
+ Component view = c.getViewport().getView();
+ if (view instanceof JComponent)
+ ((JComponent) view).setOpaque(!toBleed);
+ }
+ if ("layoutManager".equals(evt.getPropertyName())) {
+ if ((Boolean) evt.getNewValue()) {
+ ScrollPaneLayout currLayout = (ScrollPaneLayout) c
+ .getLayout();
+ if (!(currLayout instanceof AdjustedLayout)) {
+ c.setLayout(new AdjustedLayout((ScrollPaneLayout) c
+ .getLayout()));
+ }
+ }
+ // else {
+ // ScrollPaneLayout currLayout = (ScrollPaneLayout) c
+ // .getLayout();
+ // if (currLayout instanceof AdjustedLayout) {
+ // c.setLayout(((AdjustedLayout) currLayout).delegate);
+ // }
+ // }
+ }
+ if ("background".equals(evt.getPropertyName())) {
+ // propagate application-specific background color to the
+ // scroll bars.
+ Color newBackgr = (Color) evt.getNewValue();
+ if (!(newBackgr instanceof UIResource)) {
+ JScrollBar vertical = scrollpane.getVerticalScrollBar();
+ if (vertical != null) {
+ if (vertical.getBackground() instanceof UIResource) {
+ vertical.setBackground(newBackgr);
+ }
+ }
+ JScrollBar horizontal = scrollpane
+ .getHorizontalScrollBar();
+ if (horizontal != null) {
+ if (horizontal.getBackground() instanceof UIResource) {
+ horizontal.setBackground(newBackgr);
+ }
+ }
+ }
+ }
+ if ("columnHeader".equals(evt.getPropertyName())
+ || "componentOrientation".equals(evt.getPropertyName())
+ || "ancestor".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // need to switch the corner filler based on the
+ // current scroll pane state.
+ if (scrollpane != null) {
+ installTableHeaderCornerFiller(scrollpane);
+ }
+ }
+ });
+ }
+ }
+ };
+ c.addPropertyChangeListener(this.substancePropertyChangeListener);
+
+ this.substanceVerticalScrollbarChangeListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ // check if it's a horizontally scrollable tree inside
+ if ((c.getHorizontalScrollBar() != null)
+ && c.getHorizontalScrollBar().isVisible()
+ && (c.getViewport().getView() instanceof JTree)) {
+ JTree tree = (JTree) c.getViewport().getView();
+ // check if the smart scroll is enabled
+ if (AnimationConfigurationManager
+ .getInstance()
+ .isAnimationAllowed(
+ SubstanceLookAndFeel.TREE_SMART_SCROLL_ANIMATION_KIND,
+ tree)) {
+ SubstanceTreeUI treeUI = (SubstanceTreeUI) tree.getUI();
+ final Rectangle viewportRect = c.getViewport()
+ .getViewRect();
+ int pivotX = treeUI.getPivotRendererX(viewportRect);
+ int currPivotX = viewportRect.x;// + viewportRect.width
+ // / 2;
+ int delta = pivotX - currPivotX;
+ int finalX = viewportRect.x + delta;
+ if (finalX < 0) {
+ delta -= finalX;
+ }
+ final int finalDelta = delta;
+ if (Math.abs(finalDelta) > viewportRect.width / 6) {
+ if (horizontalScrollTimeline != null) {
+ // abort previous horizontal scroll
+ horizontalScrollTimeline.abort();
+ }
+ horizontalScrollTimeline = new Timeline(tree);
+ horizontalScrollTimeline
+ .addCallback(new UIThreadTimelineCallbackAdapter() {
+ @Override
+ public void onTimelinePulse(
+ float durationFraction,
+ float timelinePosition) {
+ if (timelinePosition >= 0.5) {
+ int nudge = (int) (finalDelta * (timelinePosition - 0.5));
+ c
+ .getViewport()
+ .setViewPosition(
+ new Point(
+ viewportRect.x
+ + nudge,
+ viewportRect.y));
+ }
+ }
+ });
+ horizontalScrollTimeline
+ .setEase(new TimelineEase() {
+ @Override
+ public float map(float durationFraction) {
+ if (durationFraction < 0.5)
+ return 0.5f * durationFraction;
+ return 0.25f + (durationFraction - 0.5f) * 0.75f / 0.5f;
+ }
+ });
+ AnimationConfigurationManager
+ .getInstance()
+ .configureTimeline(horizontalScrollTimeline);
+ horizontalScrollTimeline
+ .setDuration(2 * horizontalScrollTimeline
+ .getDuration());
+ horizontalScrollTimeline.play();
+ }
+ }
+ }
+ }
+ };
+ c.getVerticalScrollBar().getModel().addChangeListener(
+ this.substanceVerticalScrollbarChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicScrollPaneUI#uninstallListeners(javax.swing
+ * .JComponent)
+ */
+ @Override
+ protected void uninstallListeners(JComponent c) {
+ c.removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ JScrollPane jsp = (JScrollPane) c;
+ jsp.getVerticalScrollBar().getModel().removeChangeListener(
+ this.substanceVerticalScrollbarChangeListener);
+ this.substanceVerticalScrollbarChangeListener = null;
+
+ super.uninstallListeners(c);
+ }
+
+ /**
+ * Layout manager to adjust the bounds of scrollbars and the viewport when
+ * the default ({@link SubstanceScrollPaneBorder}) border is set on the
+ * relevant {@link JScrollPane}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected static class AdjustedLayout extends ScrollPaneLayout implements
+ UIResource {
+ /**
+ * The delegate layout.
+ */
+ protected ScrollPaneLayout delegate;
+
+ /**
+ * Creates a new layout for adjusting the bounds of scrollbars and the
+ * viewport.
+ *
+ * @param delegate
+ * The original (delegate) layout.
+ */
+ public AdjustedLayout(ScrollPaneLayout delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void addLayoutComponent(String s, Component c) {
+ delegate.addLayoutComponent(s, c);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return delegate.equals(obj);
+ }
+
+ @Override
+ public JViewport getColumnHeader() {
+ return delegate.getColumnHeader();
+ }
+
+ @Override
+ public Component getCorner(String key) {
+ return delegate.getCorner(key);
+ }
+
+ @Override
+ public JScrollBar getHorizontalScrollBar() {
+ return delegate.getHorizontalScrollBar();
+ }
+
+ @Override
+ public int getHorizontalScrollBarPolicy() {
+ return delegate.getHorizontalScrollBarPolicy();
+ }
+
+ @Override
+ public JViewport getRowHeader() {
+ return delegate.getRowHeader();
+ }
+
+ @Override
+ public JScrollBar getVerticalScrollBar() {
+ return delegate.getVerticalScrollBar();
+ }
+
+ @Override
+ public int getVerticalScrollBarPolicy() {
+ return delegate.getVerticalScrollBarPolicy();
+ }
+
+ @Override
+ public JViewport getViewport() {
+ return delegate.getViewport();
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public Rectangle getViewportBorderBounds(JScrollPane scrollpane) {
+ return delegate.getViewportBorderBounds(scrollpane);
+ }
+
+ @Override
+ public int hashCode() {
+ return delegate.hashCode();
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return delegate.minimumLayoutSize(parent);
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ return delegate.preferredLayoutSize(parent);
+ }
+
+ @Override
+ public void removeLayoutComponent(Component c) {
+ delegate.removeLayoutComponent(c);
+ }
+
+ @Override
+ public void setHorizontalScrollBarPolicy(int x) {
+ delegate.setHorizontalScrollBarPolicy(x);
+ }
+
+ @Override
+ public void setVerticalScrollBarPolicy(int x) {
+ delegate.setVerticalScrollBarPolicy(x);
+ }
+
+ @Override
+ public void syncWithScrollPane(JScrollPane sp) {
+ delegate.syncWithScrollPane(sp);
+ }
+
+ @Override
+ public String toString() {
+ return delegate.toString();
+ }
+
+ // ScrollPaneLayout.UIResource {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.ScrollPaneLayout#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container parent) {
+ delegate.layoutContainer(parent);
+
+ JScrollPane scrollPane = (JScrollPane) parent;
+ Border border = scrollPane.getBorder();
+ boolean toAdjust = (border instanceof SubstanceScrollPaneBorder);
+ if (toAdjust) {
+ JScrollBar vertical = scrollPane.getVerticalScrollBar();
+ JScrollBar horizontal = scrollPane.getHorizontalScrollBar();
+
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(scrollPane)) / 2.0);
+ int borderWidth = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(scrollPane));
+ int dx = 0, dy = 0, dw = 0, dh = 0;
+ if (scrollPane.getComponentOrientation().isLeftToRight()) {
+ if ((vertical != null) && vertical.isVisible()) {
+ Rectangle vBounds = vertical.getBounds();
+ dw += (1 + borderDelta);
+ vertical.setBounds(vBounds.x + 1 + borderDelta,
+ vBounds.y + 1 - 2 * borderWidth, vBounds.width,
+ vBounds.height + 2 * borderWidth);
+ }
+ if ((horizontal != null) && horizontal.isVisible()) {
+ dh += (1 + borderDelta);
+ Rectangle hBounds = horizontal.getBounds();
+ horizontal.setBounds(hBounds.x
+ + ((scrollPane.getRowHeader() == null) ? 1 : 2)
+ - 2 * borderWidth, hBounds.y + 1, hBounds.width
+ + 2 * borderWidth, hBounds.height);
+ }
+
+ if (delegate.getCorner(ScrollPaneLayout.LOWER_RIGHT_CORNER) != null) {
+ Rectangle lrBounds = delegate.getCorner(
+ ScrollPaneLayout.LOWER_RIGHT_CORNER)
+ .getBounds();
+ delegate.getCorner(ScrollPaneLayout.LOWER_RIGHT_CORNER)
+ .setBounds(lrBounds.x + 1 + borderDelta,
+ lrBounds.y + 1 + borderDelta,
+ lrBounds.width, lrBounds.height);
+ }
+ if (delegate.getCorner(ScrollPaneLayout.UPPER_RIGHT_CORNER) != null) {
+ Rectangle urBounds = delegate.getCorner(
+ ScrollPaneLayout.UPPER_RIGHT_CORNER)
+ .getBounds();
+ delegate.getCorner(ScrollPaneLayout.UPPER_RIGHT_CORNER)
+ .setBounds(urBounds.x + 1 + borderDelta,
+ urBounds.y + borderDelta,
+ urBounds.width - 1, urBounds.height);
+ }
+ } else {
+ if ((vertical != null) && vertical.isVisible()) {
+ dx -= (1 + borderDelta);
+ dw += (1 + borderDelta);
+ Rectangle vBounds = vertical.getBounds();
+ vertical.setBounds(vBounds.x - 1 - borderDelta,
+ vBounds.y - 1 - borderDelta, vBounds.width,
+ vBounds.height + 2 * borderWidth);
+ }
+ if ((horizontal != null) && horizontal.isVisible()) {
+ dh += (1 + borderDelta);
+ Rectangle hBounds = horizontal.getBounds();
+ horizontal
+ .setBounds(
+ hBounds.x
+ - ((scrollPane.getRowHeader() == null) ? 1
+ : 2) - borderDelta,
+ hBounds.y + 1 + borderDelta,
+ hBounds.width + 2 * borderWidth,
+ hBounds.height);
+ }
+ if (delegate.getCorner(ScrollPaneLayout.LOWER_LEFT_CORNER) != null) {
+ Rectangle llBounds = delegate.getCorner(
+ ScrollPaneLayout.LOWER_LEFT_CORNER).getBounds();
+ delegate.getCorner(ScrollPaneLayout.LOWER_LEFT_CORNER)
+ .setBounds(llBounds.x - 1 - borderDelta,
+ llBounds.y - 1 - borderDelta,
+ llBounds.width, llBounds.height);
+ }
+ if (delegate.getCorner(ScrollPaneLayout.UPPER_LEFT_CORNER) != null) {
+ Rectangle ulBounds = delegate.getCorner(
+ ScrollPaneLayout.UPPER_LEFT_CORNER).getBounds();
+ delegate.getCorner(ScrollPaneLayout.UPPER_LEFT_CORNER)
+ .setBounds(ulBounds.x - borderDelta,
+ ulBounds.y - borderDelta,
+ ulBounds.width - 1, ulBounds.height);
+ }
+ }
+
+ if (delegate.getViewport() != null) {
+ Rectangle vpBounds = delegate.getViewport().getBounds();
+ delegate.getViewport().setBounds(
+ new Rectangle(vpBounds.x + dx, vpBounds.y + dy,
+ vpBounds.width + dw, vpBounds.height + dh));
+ }
+ if (delegate.getColumnHeader() != null) {
+ Rectangle columnHeaderBounds = delegate.getColumnHeader()
+ .getBounds();
+ delegate.getColumnHeader().setBounds(
+ new Rectangle(columnHeaderBounds.x + dx,
+ columnHeaderBounds.y + dy,
+ columnHeaderBounds.width + dw,
+ columnHeaderBounds.height));
+ }
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ BackgroundPaintingUtils.updateIfOpaque(g, c);
+ JScrollPane jsp = (JScrollPane) c;
+ // if (SubstanceCoreUtilities.hasOverlayProperty(jsp)) {
+ // JViewport viewport = jsp.getViewport();
+ // int dx = -viewport.getX() + viewport.getViewRect().x;
+ // int dy = viewport.getY() - viewport.getViewRect().y;
+ // Graphics2D graphics = (Graphics2D) g.create();
+ //
+ // Area clip = new Area();
+ // if ((jsp.getVerticalScrollBar() != null)
+ // && jsp.getVerticalScrollBar().isVisible())
+ // clip.add(new Area(jsp.getVerticalScrollBar().getBounds()));
+ // if ((jsp.getHorizontalScrollBar() != null)
+ // && jsp.getHorizontalScrollBar().isVisible())
+ // clip.add(new Area(jsp.getHorizontalScrollBar().getBounds()));
+ // graphics.setClip(clip);
+ //
+ // graphics.translate(-dx, dy);
+ // JComponent view = (JComponent) viewport.getView();
+ // // fix for defect 201 - set non-double buffer for the viewport
+ // // recursively. Takes care of desktop pane wrapped in scroll pane.
+ // Map<Component, Boolean> dbSnapshot = new HashMap<Component,
+ // Boolean>();
+ // SubstanceCoreUtilities.makeNonDoubleBuffered(view, dbSnapshot);
+ // view.paint(graphics);
+ // // restore the double buffer.
+ // SubstanceCoreUtilities.restoreDoubleBuffered(view, dbSnapshot);
+ // graphics.translate(dx, -dy);
+ // graphics.dispose();
+ // }
+
+ LayoutManager lm = jsp.getLayout();
+ ScrollPaneLayout scrollLm = null;
+ if (lm instanceof ScrollPaneLayout) {
+ scrollLm = (ScrollPaneLayout) lm;
+ }
+
+ if (scrollLm != null) {
+ Set<Component> corners = new HashSet<Component>();
+ if (scrollLm.getCorner(ScrollPaneLayout.LOWER_LEFT_CORNER) != null) {
+ corners.add(scrollLm
+ .getCorner(ScrollPaneLayout.LOWER_LEFT_CORNER));
+ }
+ if (scrollLm.getCorner(ScrollPaneLayout.LOWER_RIGHT_CORNER) != null) {
+ corners.add(scrollLm
+ .getCorner(ScrollPaneLayout.LOWER_RIGHT_CORNER));
+ }
+ if (scrollLm.getCorner(ScrollPaneLayout.UPPER_LEFT_CORNER) != null) {
+ corners.add(scrollLm
+ .getCorner(ScrollPaneLayout.UPPER_LEFT_CORNER));
+ }
+ if (scrollLm.getCorner(ScrollPaneLayout.UPPER_RIGHT_CORNER) != null) {
+ corners.add(scrollLm
+ .getCorner(ScrollPaneLayout.UPPER_RIGHT_CORNER));
+ }
+
+ if (SubstanceCoreUtilities.isOpaque(c)) {
+ for (Component corner : corners) {
+ BackgroundPaintingUtils.fillAndWatermark(g, c, c
+ .getBackground(), corner.getBounds());
+ }
+ }
+ }
+
+ super.paint(g, c);
+ }
+
+ /**
+ * Installs a corner filler that matches the table header. This is done to
+ * provide a continuous appearance for tables with table headers placed in
+ * scroll panes.
+ *
+ * @param scrollpane
+ * Scroll pane.
+ */
+ protected static void installTableHeaderCornerFiller(JScrollPane scrollpane) {
+ // install custom scroll pane corner filler
+ // for continuous painting of table headers
+ JViewport columnHeader = scrollpane.getColumnHeader();
+ // System.out.println("Column header " + columnHeader);
+ if (columnHeader == null)
+ return;
+ Component columnHeaderComp = columnHeader.getView();
+ // System.out.println("Column header comp " + columnHeaderComp);
+ if (!(columnHeaderComp instanceof JTableHeader))
+ return;
+ JTableHeader tableHeader = (JTableHeader) columnHeaderComp;
+ TableHeaderUI tableHeaderUI = tableHeader.getUI();
+ if (!(tableHeaderUI instanceof SubstanceTableHeaderUI))
+ return;
+ SubstanceTableHeaderUI ui = (SubstanceTableHeaderUI) tableHeaderUI;
+ JComponent scrollPaneCornerFiller = ui.getScrollPaneCornerFiller();
+ String cornerKey = scrollpane.getComponentOrientation().isLeftToRight() ? JScrollPane.UPPER_RIGHT_CORNER
+ : JScrollPane.UPPER_LEFT_CORNER;
+ Component cornerComp = scrollpane.getCorner(cornerKey);
+ // Corner component can be replaced when the current one is null or
+ // UIResource
+ boolean canReplace = (cornerComp == null)
+ || (cornerComp instanceof UIResource);
+ // System.out.println(canReplace + ":" + cornerComp);
+ if (canReplace) {
+ scrollpane.setCorner(cornerKey, scrollPaneCornerFiller);
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceSeparatorUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceSeparatorUI.java
new file mode 100644
index 0000000..27a574f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceSeparatorUI.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicSeparatorUI;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.internal.painter.SeparatorPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.menu.MenuUtilities;
+import org.pushingpixels.substance.internal.utils.menu.SubstanceMenuBackgroundDelegate;
+
+/**
+ * UI for separators in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceSeparatorUI extends BasicSeparatorUI {
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceSeparatorUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSeparatorUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+
+ Component parent = c.getParent();
+ if (!(parent instanceof JPopupMenu)) {
+ SeparatorPainterUtils.paintSeparator(c, g, c.getWidth(), c
+ .getHeight(), ((JSeparator) c).getOrientation());
+ return;
+ }
+
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ int xOffset = MenuUtilities.getTextOffset(c, parent);
+ SubstanceMenuBackgroundDelegate.paintBackground(graphics, c, xOffset);
+ Dimension s = c.getSize();
+ int startX = 0;
+ int width = s.width;
+ if (parent.getComponentOrientation().isLeftToRight()) {
+ startX = xOffset - 2;
+ width = s.width - startX;
+ } else {
+ startX = 0;
+ width = xOffset - 4;
+ }
+ graphics.translate(startX, 0);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(parent));
+ SeparatorPainterUtils.paintSeparator(c, graphics, width, s.height,
+ ((JSeparator) c).getOrientation(), true, 2);
+
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicSeparatorUI#getPreferredSize(javax.swing.
+ * JComponent)
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ float borderStrokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ int prefSize = (int) (Math.ceil(2.0 * borderStrokeWidth));
+ if (((JSeparator) c).getOrientation() == SwingConstants.VERTICAL)
+ return new Dimension(prefSize, 0);
+ else
+ return new Dimension(0, prefSize);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceSliderUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceSliderUI.java
new file mode 100755
index 0000000..f0c96b2
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceSliderUI.java
@@ -0,0 +1,952 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicSliderUI;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.ClassicFillPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.SeparatorPainterUtils;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.icon.SubstanceIconFactory;
+
+/**
+ * UI for sliders in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceSliderUI extends BasicSliderUI implements
+ TransitionAwareUI {
+ /**
+ * Surrogate button model for tracking the thumb transitions.
+ */
+ private ButtonModel thumbModel;
+
+ /**
+ * Listener for transition animations.
+ */
+ private RolloverControlListener substanceRolloverListener;
+
+ /**
+ * Listener on property change events.
+ */
+ private PropertyChangeListener substancePropertyChangeListener;
+
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Icon for horizontal sliders.
+ */
+ protected Icon horizontalIcon;
+
+ /**
+ * Icon for sliders without labels and ticks.
+ */
+ protected Icon roundIcon;
+
+ /**
+ * Icon for vertical sliders.
+ */
+ protected Icon verticalIcon;
+
+ /**
+ * Cache of track images.
+ */
+ protected static final LazyResettableHashMap<BufferedImage> trackCache = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceSliderUI.track");
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceSliderUI((JSlider) comp);
+ }
+
+ /**
+ * Simple constructor.
+ *
+ * @param slider
+ * Slider.
+ */
+ public SubstanceSliderUI(JSlider slider) {
+ super(null);
+ this.thumbModel = new DefaultButtonModel();
+ this.thumbModel.setArmed(false);
+ this.thumbModel.setSelected(false);
+ this.thumbModel.setPressed(false);
+ this.thumbModel.setRollover(false);
+ this.thumbModel.setEnabled(slider.isEnabled());
+
+ this.stateTransitionTracker = new StateTransitionTracker(slider,
+ this.thumbModel);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#calculateTrackRect()
+ */
+ @Override
+ protected void calculateTrackRect() {
+ super.calculateTrackRect();
+ if (this.slider.getOrientation() == SwingConstants.HORIZONTAL) {
+ this.trackRect.y = 3
+ + (int) Math.ceil(SubstanceSizeUtils
+ .getFocusStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.slider)))
+ + this.insetCache.top;
+ }
+ }
+
+ /**
+ * Returns the rectangle of track for painting.
+ *
+ * @return The rectangle of track for painting.
+ */
+ private Rectangle getPaintTrackRect() {
+ int trackLeft = 0;
+ int trackRight;
+ int trackTop = 0;
+ int trackBottom;
+ int trackWidth = this.getTrackWidth();
+ if (this.slider.getOrientation() == SwingConstants.HORIZONTAL) {
+ trackTop = 3 + this.insetCache.top + 2 * this.focusInsets.top;
+ trackBottom = trackTop + trackWidth - 1;
+ trackRight = this.trackRect.width;
+ return new Rectangle(this.trackRect.x + trackLeft, trackTop,
+ trackRight - trackLeft, trackBottom - trackTop);
+ } else {
+ if (this.slider.getPaintLabels() || this.slider.getPaintTicks()) {
+ if (this.slider.getComponentOrientation().isLeftToRight()) {
+ trackLeft = trackRect.x + this.insetCache.left
+ + this.focusInsets.left;
+ trackRight = trackLeft + trackWidth - 1;
+ } else {
+ trackRight = trackRect.x + trackRect.width
+ - this.insetCache.right - this.focusInsets.right;
+ trackLeft = trackRight - trackWidth - 1;
+ }
+ } else {
+ // horizontally center the track
+ if (this.slider.getComponentOrientation().isLeftToRight()) {
+ trackLeft = (this.insetCache.left + this.focusInsets.left
+ + this.slider.getWidth() - this.insetCache.right - this.focusInsets.right)
+ / 2 - trackWidth / 2;
+ trackRight = trackLeft + trackWidth - 1;
+ } else {
+ trackRight = (this.insetCache.left + this.focusInsets.left
+ + this.slider.getWidth() - this.insetCache.right - this.focusInsets.right)
+ / 2 + trackWidth / 2;
+ trackLeft = trackRight - trackWidth - 1;
+ }
+ }
+ trackBottom = this.trackRect.height - 1;
+ return new Rectangle(trackLeft, this.trackRect.y + trackTop,
+ trackRight - trackLeft, trackBottom - trackTop);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#paintTrack(java.awt.Graphics)
+ */
+ @Override
+ public void paintTrack(Graphics g) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ boolean drawInverted = this.drawInverted();
+
+ Rectangle paintRect = this.getPaintTrackRect();
+
+ // Width and height of the painting rectangle.
+ int width = paintRect.width;
+ int height = paintRect.height;
+
+ if (this.slider.getOrientation() == JSlider.VERTICAL) {
+ // apply rotation / translate transformation on vertical
+ // slider tracks
+ int temp = width;
+ //noinspection SuspiciousNameCombination
+ width = height;
+ height = temp;
+ AffineTransform at = graphics.getTransform();
+ at.translate(paintRect.x, width + paintRect.y);
+ at.rotate(-Math.PI / 2);
+ graphics.setTransform(at);
+ } else {
+ graphics.translate(paintRect.x, paintRect.y);
+ }
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = this.stateTransitionTracker
+ .getModelStateInfo();
+
+ SubstanceColorScheme trackSchemeUnselected = SubstanceColorSchemeUtilities
+ .getColorScheme(this.slider,
+ this.slider.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED);
+ SubstanceColorScheme trackBorderSchemeUnselected = SubstanceColorSchemeUtilities
+ .getColorScheme(this.slider, ColorSchemeAssociationKind.BORDER,
+ this.slider.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED);
+ this.paintSliderTrack(graphics, drawInverted, trackSchemeUnselected,
+ trackBorderSchemeUnselected, width, height);
+
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (!activeState.isActive())
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.slider, contribution, g));
+
+ SubstanceColorScheme activeFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.slider, activeState);
+ SubstanceColorScheme activeBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.slider,
+ ColorSchemeAssociationKind.BORDER, activeState);
+ this.paintSliderTrackSelected(graphics, drawInverted, paintRect,
+ activeFillScheme, activeBorderScheme, width, height);
+ }
+
+ graphics.dispose();
+ }
+
+ /**
+ * Paints the slider track.
+ *
+ * @param graphics
+ * Graphics.
+ * @param drawInverted
+ * Indicates whether the value-range shown for the slider is
+ * reversed.
+ * @param fillColorScheme
+ * Fill color scheme.
+ * @param borderScheme
+ * Border color scheme.
+ * @param width
+ * Track width.
+ * @param height
+ * Track height.
+ */
+ private void paintSliderTrack(Graphics2D graphics, boolean drawInverted,
+ SubstanceColorScheme fillColorScheme,
+ SubstanceColorScheme borderScheme, int width, int height) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+
+ SubstanceFillPainter fillPainter = ClassicFillPainter.INSTANCE;
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(this.slider);
+
+ int componentFontSize = SubstanceSizeUtils
+ .getComponentFontSize(this.slider);
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize) / 2.0);
+ float radius = SubstanceSizeUtils
+ .getClassicButtonCornerRadius(componentFontSize) / 2.0f;
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
+ radius, borderDelta, borderThickness, fillColorScheme
+ .getDisplayName(), borderScheme.getDisplayName());
+
+ BufferedImage trackImage = trackCache.get(key);
+ if (trackImage == null) {
+ trackImage = SubstanceCoreUtilities.getBlankImage(width + 1,
+ height + 1);
+ Graphics2D cacheGraphics = trackImage.createGraphics();
+
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(width + 1,
+ height + 1, radius, null, borderDelta);
+
+ fillPainter.paintContourBackground(cacheGraphics, slider, width,
+ height, contour, false, fillColorScheme, false);
+
+ GeneralPath contourInner = SubstanceOutlineUtilities
+ .getBaseOutline(width + 1, height + 1, radius
+ - borderThickness, null, borderThickness
+ + borderDelta);
+ borderPainter.paintBorder(cacheGraphics, slider, width + 1,
+ height + 1, contour, contourInner, borderScheme);
+
+ trackCache.put(key, trackImage);
+ cacheGraphics.dispose();
+ }
+
+ g2d.drawImage(trackImage, 0, 0, null);
+
+ g2d.dispose();
+ }
+
+ /**
+ * Paints the selected part of the slider track.
+ *
+ * @param graphics
+ * Graphics.
+ * @param drawInverted
+ * Indicates whether the value-range shown for the slider is
+ * reversed.
+ * @param paintRect
+ * Selected portion.
+ * @param fillScheme
+ * Fill color scheme.
+ * @param borderScheme
+ * Border color scheme.
+ * @param width
+ * Track width.
+ * @param height
+ * Track height.
+ */
+ private void paintSliderTrackSelected(Graphics2D graphics,
+ boolean drawInverted, Rectangle paintRect,
+ SubstanceColorScheme fillScheme, SubstanceColorScheme borderScheme,
+ int width, int height) {
+
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ Insets insets = this.slider.getInsets();
+ insets.top /= 2;
+ insets.left /= 2;
+ insets.bottom /= 2;
+ insets.right /= 2;
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(this.slider);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(this.slider);
+ float radius = SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(slider)) / 2.0f;
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(slider)) / 2.0);
+
+ // fill selected portion
+ if (this.slider.isEnabled()) {
+ if (this.slider.getOrientation() == SwingConstants.HORIZONTAL) {
+ int middleOfThumb = this.thumbRect.x
+ + (this.thumbRect.width / 2) - paintRect.x;
+ int fillMinX;
+ int fillMaxX;
+
+ if (drawInverted) {
+ fillMinX = middleOfThumb;
+ fillMaxX = width;
+ } else {
+ fillMinX = 0;
+ fillMaxX = middleOfThumb;
+ }
+
+ int fillWidth = fillMaxX - fillMinX;
+ int fillHeight = height + 1;
+ if ((fillWidth > 0) && (fillHeight > 0)) {
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(
+ fillWidth, fillHeight, radius, null, borderDelta);
+ g2d.translate(fillMinX, 0);
+ fillPainter.paintContourBackground(g2d, this.slider,
+ fillWidth, fillHeight, contour, false, fillScheme,
+ false);
+ borderPainter.paintBorder(g2d, this.slider, fillWidth,
+ fillHeight, contour, null, borderScheme);
+ }
+ } else {
+ int middleOfThumb = this.thumbRect.y
+ + (this.thumbRect.height / 2) - paintRect.y;
+ int fillMin;
+ int fillMax;
+
+ if (this.drawInverted()) {
+ fillMin = 0;
+ fillMax = middleOfThumb;
+ // fix for issue 368 - inverted vertical sliders
+ g2d.translate(width + 2 - middleOfThumb, 0);
+ } else {
+ fillMin = middleOfThumb;
+ fillMax = width + 1;
+ }
+
+ int fillWidth = fillMax - fillMin;
+ int fillHeight = height + 1;
+ if ((fillWidth > 0) && (fillHeight > 0)) {
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(
+ fillWidth, fillHeight, radius, null, borderDelta);
+
+ fillPainter.paintContourBackground(g2d, this.slider,
+ fillWidth, fillHeight, contour, false, fillScheme,
+ false);
+ borderPainter.paintBorder(g2d, this.slider, fillWidth,
+ fillHeight, contour, null, borderScheme);
+ }
+ }
+ }
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#getThumbSize()
+ */
+ @Override
+ protected Dimension getThumbSize() {
+ Icon thumbIcon = this.getIcon();
+ return new Dimension(thumbIcon.getIconWidth(), thumbIcon
+ .getIconHeight());
+ }
+
+ /**
+ * Returns the thumb icon for the associated slider.
+ *
+ * @return The thumb icon for the associated slider.
+ */
+ protected Icon getIcon() {
+ if (this.slider.getOrientation() == JSlider.HORIZONTAL) {
+ if (this.slider.getPaintTicks() || this.slider.getPaintLabels())
+ return this.horizontalIcon;
+ else
+ return this.roundIcon;
+ } else {
+ if (this.slider.getPaintTicks() || this.slider.getPaintLabels())
+ return this.verticalIcon;
+ else
+ return this.roundIcon;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#paintThumb(java.awt.Graphics)
+ */
+ @Override
+ public void paintThumb(Graphics g) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ // graphics.setComposite(TransitionLayout.getAlphaComposite(slider));
+ Rectangle knobBounds = this.thumbRect;
+ // System.out.println(thumbRect);
+
+ graphics.translate(knobBounds.x, knobBounds.y);
+
+ Icon icon = this.getIcon();
+ if (this.slider.getOrientation() == JSlider.HORIZONTAL) {
+ if (icon != null)
+ icon.paintIcon(this.slider, graphics, -1, 0);
+ } else {
+ if (this.slider.getComponentOrientation().isLeftToRight()) {
+ if (icon != null)
+ icon.paintIcon(this.slider, graphics, 0, -1);
+ } else {
+ if (icon != null)
+ icon.paintIcon(this.slider, graphics, 0, 1);
+ }
+ }
+
+ // graphics.translate(-knobBounds.x, -knobBounds.y);
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, final JComponent c) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ ComponentState currState = ComponentState.getState(this.thumbModel,
+ this.slider);
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(this.slider,
+ currState);
+
+ BackgroundPaintingUtils.updateIfOpaque(graphics, c);
+
+ recalculateIfInsetsChanged();
+ recalculateIfOrientationChanged();
+ final Rectangle clip = graphics.getClipBounds();
+
+ if (!clip.intersects(trackRect) && slider.getPaintTrack())
+ calculateGeometry();
+
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(this.slider,
+ alpha, g));
+ if (slider.getPaintTrack() && clip.intersects(trackRect)) {
+ paintTrack(graphics);
+ }
+ if (slider.getPaintTicks() && clip.intersects(tickRect)) {
+ paintTicks(graphics);
+ }
+ paintFocus(graphics);
+ if (clip.intersects(thumbRect)) {
+ paintThumb(graphics);
+ }
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(this.slider,
+ 1.0f, g));
+ if (slider.getPaintLabels() && clip.intersects(labelRect)) {
+ paintLabels(graphics);
+ }
+
+ graphics.dispose();
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.Trackable#isInside(java.awt.event.MouseEvent)
+ */
+ @Override
+ public boolean isInside(MouseEvent me) {
+ Rectangle thumbB = this.thumbRect;
+ return thumbB != null && thumbB.contains(me.getX(), me.getY());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicSliderUI#installDefaults(javax.swing.JSlider)
+ */
+ @Override
+ protected void installDefaults(JSlider slider) {
+ super.installDefaults(slider);
+ Font f = slider.getFont();
+ if (f == null || f instanceof UIResource) {
+ slider.setFont(new FontUIResource(SubstanceLookAndFeel
+ .getFontPolicy().getFontSet("Substance", null)
+ .getControlFont()));
+ }
+ int size = SubstanceSizeUtils.getSliderIconSize(SubstanceSizeUtils
+ .getComponentFontSize(slider));
+ // System.out.println("Slider size : " + size);
+ this.horizontalIcon = SubstanceIconFactory.getSliderHorizontalIcon(
+ size, false);
+ this.roundIcon = SubstanceIconFactory.getSliderRoundIcon(size);
+ this.verticalIcon = SubstanceIconFactory.getSliderVerticalIcon(size,
+ false);
+
+ int focusIns = (int) Math.ceil(2.0 * SubstanceSizeUtils
+ .getFocusStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(slider)));
+ this.focusInsets = new Insets(focusIns, focusIns, focusIns, focusIns);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicSliderUI#installListeners(javax.swing.JSlider
+ * )
+ */
+ @Override
+ protected void installListeners(final JSlider slider) {
+ super.installListeners(slider);
+
+ // fix for defect 109 - memory leak on changing skin
+ this.substanceRolloverListener = new RolloverControlListener(this,
+ this.thumbModel);
+ slider.addMouseListener(this.substanceRolloverListener);
+ slider.addMouseMotionListener(this.substanceRolloverListener);
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("enabled".equals(evt.getPropertyName())) {
+ SubstanceSliderUI.this.thumbModel.setEnabled(slider
+ .isEnabled());
+ }
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ slider.updateUI();
+ }
+ });
+ }
+ }
+ };
+ this.slider
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+
+ this.stateTransitionTracker.registerModelListeners();
+ this.stateTransitionTracker.registerFocusListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicSliderUI#uninstallListeners(javax.swing.JSlider
+ * )
+ */
+ @Override
+ protected void uninstallListeners(JSlider slider) {
+ super.uninstallListeners(slider);
+
+ // fix for defect 109 - memory leak on changing skin
+ slider.removeMouseListener(this.substanceRolloverListener);
+ slider.removeMouseMotionListener(this.substanceRolloverListener);
+ this.substanceRolloverListener = null;
+
+ slider
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ this.stateTransitionTracker.unregisterModelListeners();
+ this.stateTransitionTracker.unregisterFocusListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#paintFocus(java.awt.Graphics)
+ */
+ @Override
+ public void paintFocus(Graphics g) {
+ SubstanceCoreUtilities.paintFocus(g, this.slider, this.slider, this,
+ null, null, 1.0f, (int) Math.ceil(SubstanceSizeUtils
+ .getFocusStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.slider))) / 2);
+ }
+
+ /**
+ * Returns the amount that the thumb goes past the slide bar.
+ *
+ * @return Amount that the thumb goes past the slide bar.
+ */
+ protected int getThumbOverhang() {
+ return (int) (this.getThumbSize().getHeight() - this.getTrackWidth()) / 2;
+ }
+
+ /**
+ * Returns the shorter dimension of the track.
+ *
+ * @return Shorter dimension of the track.
+ */
+ protected int getTrackWidth() {
+ return SubstanceSizeUtils.getSliderTrackSize(SubstanceSizeUtils
+ .getComponentFontSize(this.slider));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#getTickLength()
+ */
+ @Override
+ protected int getTickLength() {
+ return SubstanceSizeUtils.getSliderTickSize(SubstanceSizeUtils
+ .getComponentFontSize(this.slider));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#paintTicks(java.awt.Graphics)
+ */
+ @Override
+ public void paintTicks(Graphics g) {
+ Rectangle tickBounds = this.tickRect;
+ SubstanceColorScheme tickScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.slider,
+ ColorSchemeAssociationKind.SEPARATOR, this.slider
+ .isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED);
+ if (this.slider.getOrientation() == JSlider.HORIZONTAL) {
+ int value = this.slider.getMinimum()
+ + this.slider.getMinorTickSpacing();
+ int xPos;
+
+ if ((this.slider.getMinorTickSpacing() > 0)
+ && (this.slider.getMajorTickSpacing() > 0)) {
+ // collect x's of the minor ticks
+ java.util.List<Integer> minorXs = new ArrayList<Integer>();
+ while (value < this.slider.getMaximum()) {
+ int delta = value - this.slider.getMinimum();
+ if (delta % this.slider.getMajorTickSpacing() != 0) {
+ xPos = this.xPositionForValue(value);
+ minorXs.add(xPos - 1);
+ }
+ value += this.slider.getMinorTickSpacing();
+ }
+ // and paint them in one call
+ SeparatorPainterUtils.paintVerticalLines(g, this.slider,
+ tickScheme, tickBounds.y, minorXs,
+ tickBounds.height / 2, 0.75f);
+ }
+
+ if (this.slider.getMajorTickSpacing() > 0) {
+ // collect x's of the major ticks
+ java.util.List<Integer> majorXs = new ArrayList<Integer>();
+ value = this.slider.getMinimum()
+ + this.slider.getMajorTickSpacing();
+ while (value < this.slider.getMaximum()) {
+ xPos = this.xPositionForValue(value);
+ majorXs.add(xPos - 1);
+ value += this.slider.getMajorTickSpacing();
+ }
+ // and paint them in one call
+ SeparatorPainterUtils.paintVerticalLines(g, this.slider,
+ tickScheme, tickBounds.y, majorXs, tickBounds.height,
+ 0.75f);
+ }
+ } else {
+ g.translate(tickBounds.x, 0);
+
+ int value = this.slider.getMinimum()
+ + this.slider.getMinorTickSpacing();
+ int yPos;
+
+ boolean ltr = this.slider.getComponentOrientation().isLeftToRight();
+ if (this.slider.getMinorTickSpacing() > 0) {
+ // collect y's of the minor ticks
+ java.util.List<Integer> minorYs = new ArrayList<Integer>();
+ int offset = 0;
+ if (!ltr) {
+ offset = tickBounds.width - tickBounds.width / 2;
+ }
+
+ while (value < this.slider.getMaximum()) {
+ yPos = this.yPositionForValue(value);
+ minorYs.add(yPos);
+ value += this.slider.getMinorTickSpacing();
+ }
+
+ // and paint them in one call
+ SeparatorPainterUtils.paintHorizontalLines(g, this.slider,
+ tickScheme, offset, minorYs, tickBounds.width / 2,
+ ltr ? 0.75f : 0.25f, ltr);
+ }
+
+ if (this.slider.getMajorTickSpacing() > 0) {
+ // collect y's of the major ticks
+ java.util.List<Integer> majorYs = new ArrayList<Integer>();
+ value = this.slider.getMinimum()
+ + this.slider.getMajorTickSpacing();
+
+ while (value < this.slider.getMaximum()) {
+ yPos = this.yPositionForValue(value);
+ majorYs.add(yPos);
+ value += this.slider.getMajorTickSpacing();
+ }
+
+ // and paint them in one call
+ SeparatorPainterUtils.paintHorizontalLines(g, this.slider,
+ tickScheme, 0, majorYs, tickBounds.width, ltr ? 0.75f
+ : 0.25f, ltr);
+ }
+ g.translate(-tickBounds.x, 0);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#calculateTickRect()
+ */
+ @Override
+ protected void calculateTickRect() {
+ if (this.slider.getOrientation() == JSlider.HORIZONTAL) {
+ this.tickRect.x = this.trackRect.x;
+ this.tickRect.y = this.trackRect.y + this.trackRect.height;
+ this.tickRect.width = this.trackRect.width;
+ this.tickRect.height = (this.slider.getPaintTicks()) ? this
+ .getTickLength() : 0;
+ } else {
+ this.tickRect.width = (this.slider.getPaintTicks()) ? this
+ .getTickLength() : 0;
+ if (this.slider.getComponentOrientation().isLeftToRight()) {
+ this.tickRect.x = this.trackRect.x + this.trackRect.width;
+ } else {
+ this.tickRect.x = this.trackRect.x - this.tickRect.width;
+ }
+ this.tickRect.y = this.trackRect.y;
+ this.tickRect.height = this.trackRect.height;
+ }
+
+ if (this.slider.getPaintTicks()) {
+ if (this.slider.getOrientation() == JSlider.HORIZONTAL) {
+ this.tickRect.y -= 3;
+ } else {
+ if (this.slider.getComponentOrientation().isLeftToRight()) {
+ this.tickRect.x -= 2;
+ } else {
+ this.tickRect.x += 2;
+ }
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#calculateLabelRect()
+ */
+ @Override
+ protected void calculateLabelRect() {
+ super.calculateLabelRect();
+ if ((this.slider.getOrientation() == JSlider.VERTICAL)
+ && !this.slider.getPaintTicks()
+ && this.slider.getComponentOrientation().isLeftToRight()) {
+ this.labelRect.x += 3;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#calculateThumbLocation()
+ */
+ @Override
+ protected void calculateThumbLocation() {
+ super.calculateThumbLocation();
+ Rectangle trackRect = this.getPaintTrackRect();
+ if (slider.getOrientation() == JSlider.HORIZONTAL) {
+ int valuePosition = xPositionForValue(slider.getValue());
+
+ double centerY = trackRect.y + trackRect.height / 2.0;
+ thumbRect.y = (int) (centerY - thumbRect.height / 2.0) + 1;
+
+ thumbRect.x = valuePosition - thumbRect.width / 2;
+ } else {
+ int valuePosition = yPositionForValue(slider.getValue());
+
+ double centerX = trackRect.x + trackRect.width / 2.0;
+ thumbRect.x = (int) (centerX - thumbRect.width / 2.0) + 1;
+
+ thumbRect.y = valuePosition - (thumbRect.height / 2);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicSliderUI#getPreferredSize(javax.swing.JComponent
+ * )
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ this.recalculateIfInsetsChanged();
+ Dimension d;
+ if (this.slider.getOrientation() == JSlider.VERTICAL) {
+ d = new Dimension(this.getPreferredVerticalSize());
+ d.width = this.insetCache.left + this.insetCache.right;
+ d.width += this.focusInsets.left + this.focusInsets.right;
+ d.width += this.trackRect.width;
+ if (this.slider.getPaintTicks())
+ d.width += getTickLength();
+ if (this.slider.getPaintLabels())
+ d.width += getWidthOfWidestLabel();
+ d.width += 3;
+ } else {
+ d = new Dimension(this.getPreferredHorizontalSize());
+ d.height = this.insetCache.top + this.insetCache.bottom;
+ d.height += this.focusInsets.top + this.focusInsets.bottom;
+ d.height += this.trackRect.height;
+ if (this.slider.getPaintTicks())
+ d.height += getTickLength();
+ if (this.slider.getPaintLabels())
+ d.height += getHeightOfTallestLabel();
+ d.height += 3;
+ }
+
+ return d;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#setThumbLocation(int, int)
+ */
+ @Override
+ public void setThumbLocation(int x, int y) {
+ super.setThumbLocation(x, y);
+ this.slider.repaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#getPreferredHorizontalSize()
+ */
+ @Override
+ public Dimension getPreferredHorizontalSize() {
+ return new Dimension(SubstanceSizeUtils.getAdjustedSize(
+ SubstanceSizeUtils.getComponentFontSize(this.slider), 200, 1,
+ 20, false), 21);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSliderUI#getPreferredVerticalSize()
+ */
+ @Override
+ public Dimension getPreferredVerticalSize() {
+ return new Dimension(21, SubstanceSizeUtils.getAdjustedSize(
+ SubstanceSizeUtils.getComponentFontSize(this.slider), 200, 1,
+ 20, false));
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceSpinnerUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceSpinnerUI.java
new file mode 100644
index 0000000..ea56f72
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceSpinnerUI.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.EnumSet;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicSpinnerUI;
+import javax.swing.text.JTextComponent;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.SubstanceConstants.Side;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities.TextComponentAware;
+import org.pushingpixels.substance.internal.utils.border.SubstanceTextComponentBorder;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
+
+/**
+ * UI for spinners in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceSpinnerUI extends BasicSpinnerUI {
+ /**
+ * Tracks changes to editor, removing the border as necessary.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * The next (increment) button.
+ */
+ protected SubstanceSpinnerButton nextButton;
+
+ /**
+ * The previous (decrement) button.
+ */
+ protected SubstanceSpinnerButton prevButton;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceSpinnerUI();
+ }
+
+ @Override
+ public void installUI(JComponent c) {
+ super.installUI(c);
+
+ c.putClientProperty(SubstanceCoreUtilities.TEXT_COMPONENT_AWARE,
+ new TextComponentAware<JSpinner>() {
+ @Override
+ public JTextComponent getTextComponent(JSpinner t) {
+ JComponent editor = t.getEditor();
+ if ((editor != null)
+ && (editor instanceof JSpinner.DefaultEditor)) {
+ return ((JSpinner.DefaultEditor) editor)
+ .getTextField();
+ }
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public void uninstallUI(JComponent c) {
+ c.putClientProperty(SubstanceCoreUtilities.TEXT_COMPONENT_AWARE, null);
+
+ super.uninstallUI(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSpinnerUI#createNextButton()
+ */
+ @Override
+ protected Component createNextButton() {
+ this.nextButton = new SubstanceSpinnerButton(this.spinner,
+ SwingConstants.NORTH);
+ this.nextButton.setFont(this.spinner.getFont());
+ this.nextButton.setName("Spinner.nextButton");
+
+ Icon icon = new TransitionAwareIcon(this.nextButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(nextButton);
+ return SubstanceImageCreator.getArrowIcon(
+ SubstanceSizeUtils
+ .getSpinnerArrowIconWidth(fontSize),
+ SubstanceSizeUtils
+ .getSpinnerArrowIconHeight(fontSize),
+ SubstanceSizeUtils
+ .getArrowStrokeWidth(fontSize),
+ SwingConstants.NORTH, scheme);
+ }
+ }, "substance.spinner.nextButton");
+ this.nextButton.setIcon(icon);
+
+ int spinnerButtonSize = SubstanceSizeUtils
+ .getScrollBarWidth(SubstanceSizeUtils
+ .getComponentFontSize(spinner));
+ this.nextButton.setPreferredSize(new Dimension(spinnerButtonSize,
+ spinnerButtonSize));
+ this.nextButton.setMinimumSize(new Dimension(5, 5));
+
+ this.nextButton.putClientProperty(
+ SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY, EnumSet
+ .of(Side.BOTTOM));
+ this.nextButton.putClientProperty(
+ SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY, EnumSet
+ .of(Side.BOTTOM));
+
+ this.installNextButtonListeners(this.nextButton);
+
+ Color spinnerBg = this.spinner.getBackground();
+ if (!(spinnerBg instanceof UIResource)) {
+ this.nextButton.setBackground(spinnerBg);
+ }
+
+ return this.nextButton;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSpinnerUI#createPreviousButton()
+ */
+ @Override
+ protected Component createPreviousButton() {
+ this.prevButton = new SubstanceSpinnerButton(this.spinner,
+ SwingConstants.SOUTH);
+ this.prevButton.setFont(this.spinner.getFont());
+ this.prevButton.setName("Spinner.previousButton");
+
+ Icon icon = new TransitionAwareIcon(this.prevButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(prevButton);
+ float spinnerArrowIconHeight = SubstanceSizeUtils
+ .getSpinnerArrowIconHeight(fontSize);
+ return SubstanceImageCreator.getArrowIcon(
+ SubstanceSizeUtils
+ .getSpinnerArrowIconWidth(fontSize),
+ spinnerArrowIconHeight, SubstanceSizeUtils
+ .getArrowStrokeWidth(fontSize),
+ SwingConstants.SOUTH, scheme);
+ }
+ }, "substance.spinner.prevButton");
+ this.prevButton.setIcon(icon);
+
+ int spinnerButtonSize = SubstanceSizeUtils
+ .getScrollBarWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.prevButton));
+ this.prevButton.setPreferredSize(new Dimension(spinnerButtonSize,
+ spinnerButtonSize));
+ this.prevButton.setMinimumSize(new Dimension(5, 5));
+
+ this.prevButton.putClientProperty(
+ SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY, EnumSet
+ .of(Side.TOP));
+ this.prevButton
+ .putClientProperty(SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY,
+ EnumSet.of(Side.TOP));
+
+ this.installPreviousButtonListeners(this.prevButton);
+
+ Color spinnerBg = this.spinner.getBackground();
+ if (!(spinnerBg instanceof UIResource)) {
+ this.nextButton.setBackground(spinnerBg);
+ }
+
+ return this.prevButton;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSpinnerUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ JComponent editor = this.spinner.getEditor();
+ if ((editor != null) && (editor instanceof JSpinner.DefaultEditor)) {
+ JTextField tf = ((JSpinner.DefaultEditor) editor).getTextField();
+ if (tf != null) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(this.spinner);
+ Insets ins = SubstanceSizeUtils
+ .getSpinnerTextBorderInsets(fontSize);
+ tf.setBorder(new EmptyBorder(ins.top, ins.left, ins.bottom,
+ ins.right));
+ tf.setFont(spinner.getFont());
+ tf.setOpaque(false);
+ }
+ }
+ if (editor != null) {
+ editor.setOpaque(false);
+ }
+
+ Border b = this.spinner.getBorder();
+ if (b == null || b instanceof UIResource) {
+ this.spinner.setBorder(new SubstanceTextComponentBorder(
+ SubstanceSizeUtils
+ .getSpinnerBorderInsets(SubstanceSizeUtils
+ .getComponentFontSize(this.spinner))));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSpinnerUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("editor".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (spinner == null)
+ return;
+ JComponent editor = spinner.getEditor();
+ if ((editor != null)
+ && (editor instanceof JSpinner.DefaultEditor)) {
+ JTextField tf = ((JSpinner.DefaultEditor) editor)
+ .getTextField();
+ if (tf != null) {
+ Insets ins = SubstanceSizeUtils
+ .getSpinnerTextBorderInsets(SubstanceSizeUtils
+ .getComponentFontSize(spinner));
+ tf.setBorder(new EmptyBorder(ins.top,
+ ins.left, ins.bottom, ins.right));
+ tf.revalidate();
+ }
+ }
+ }
+ });
+ }
+
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (spinner != null) {
+ spinner.updateUI();
+ }
+ }
+ });
+ }
+
+ if ("background".equals(evt.getPropertyName())) {
+ JComponent editor = spinner.getEditor();
+ if ((editor != null)
+ && (editor instanceof JSpinner.DefaultEditor)) {
+ JTextField tf = ((JSpinner.DefaultEditor) editor)
+ .getTextField();
+ if (tf != null) {
+ // Use SubstanceColorResource to distingish between
+ // color set by application and color set
+ // (propagated)
+ // by Substance. In the second case we can replace
+ // that color (even though it's not a UIResource).
+ Color tfBackground = tf.getBackground();
+ boolean canReplace = SubstanceCoreUtilities
+ .canReplaceChildBackgroundColor(tfBackground);
+ // fix for issue 387 - if spinner background
+ // is null, do nothing
+ if (spinner.getBackground() == null)
+ canReplace = false;
+ if (canReplace) {
+ tf.setBackground(new SubstanceColorResource(
+ spinner.getBackground()));
+ }
+ }
+ }
+ nextButton.setBackground(spinner.getBackground());
+ prevButton.setBackground(spinner.getBackground());
+ }
+ }
+ };
+ this.spinner
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSpinnerUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.spinner
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ super.paint(g, c);
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ int width = this.spinner.getWidth();
+ int height = this.spinner.getHeight();
+ int componentFontSize = SubstanceSizeUtils
+ .getComponentFontSize(this.spinner);
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize));
+ Shape contour = SubstanceOutlineUtilities
+ .getBaseOutline(
+ width,
+ height,
+ Math.max(
+ 0,
+ 2.0f
+ * SubstanceSizeUtils
+ .getClassicButtonCornerRadius(componentFontSize)
+ - borderDelta), null, borderDelta);
+
+ graphics.setColor(SubstanceTextUtilities
+ .getTextBackgroundFillColor(this.spinner));
+ graphics.fill(contour);
+
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.ComponentUI#getPreferredSize(javax.swing.JComponent)
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ Dimension nextD = this.nextButton.getPreferredSize();
+ Dimension previousD = this.prevButton.getPreferredSize();
+ Dimension editorD = spinner.getEditor().getPreferredSize();
+
+ Dimension size = new Dimension(editorD.width, editorD.height);
+ size.width += Math.max(nextD.width, previousD.width);
+ Insets insets = this.spinner.getInsets();
+ size.width += insets.left + insets.right;
+ size.height += insets.top + insets.bottom;
+ return size;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ SubstanceTextUtilities.paintTextCompBackground(g, c);
+ this.paint(g, c);
+ }
+
+ @Override
+ protected LayoutManager createLayout() {
+ return new SpinnerLayoutManager();
+ }
+
+ /**
+ * Layout manager for the spinner.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class SpinnerLayoutManager implements LayoutManager {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return this.preferredLayoutSize(parent);
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ Dimension nextD = nextButton.getPreferredSize();
+ Dimension previousD = prevButton.getPreferredSize();
+ Dimension editorD = spinner.getEditor().getPreferredSize();
+
+ /*
+ * Force the editors height to be a multiple of 2
+ */
+ editorD.height = ((editorD.height + 1) / 2) * 2;
+
+ Dimension size = new Dimension(editorD.width, editorD.height);
+ size.width += Math.max(nextD.width, previousD.width);
+ Insets insets = parent.getInsets();
+ size.width += insets.left + insets.right;
+ size.height += insets.top + insets.bottom;
+
+ Insets buttonInsets = SubstanceSizeUtils
+ .getSpinnerArrowButtonInsets(SubstanceSizeUtils
+ .getComponentFontSize(spinner));
+ size.width += (buttonInsets.left + buttonInsets.right);
+
+ return size;
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ int width = parent.getWidth();
+ int height = parent.getHeight();
+
+ Insets insets = parent.getInsets();
+ Dimension nextD = nextButton.getPreferredSize();
+ Dimension previousD = prevButton.getPreferredSize();
+ int buttonsWidth = Math.max(nextD.width, previousD.width);
+ int editorHeight = height - (insets.top + insets.bottom);
+
+ Insets buttonInsets = SubstanceSizeUtils
+ .getSpinnerArrowButtonInsets(SubstanceSizeUtils
+ .getComponentFontSize(spinner));
+
+ /*
+ * Deal with the spinner's componentOrientation property.
+ */
+ int editorX, editorWidth, buttonsX;
+ if (parent.getComponentOrientation().isLeftToRight()) {
+ editorX = insets.left;
+ editorWidth = width - insets.left - buttonsWidth;
+ buttonsX = width - buttonsWidth;// - buttonInsets.right;
+ } else {
+ buttonsX = 0;// buttonInsets.left;
+ editorX = buttonsX + buttonsWidth;
+ editorWidth = width - editorX - insets.right;
+ }
+
+ int nextY = 0;// buttonInsets.top;
+ int nextHeight = (height / 2) + (height % 2) - nextY;
+ int previousY = 0 * buttonInsets.top + nextHeight;
+ int previousHeight = height - previousY;// - buttonInsets.bottom;
+
+ spinner.getEditor().setBounds(editorX, insets.top, editorWidth,
+ editorHeight);
+ nextButton.setBounds(buttonsX, nextY, buttonsWidth, nextHeight);
+ prevButton.setBounds(buttonsX, previousY, buttonsWidth,
+ previousHeight);
+ // System.out.println("next : " + nextButton.getBounds());
+ // System.out.println("prev : " + prevButton.getBounds());
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceSplitPaneUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceSplitPaneUI.java
new file mode 100644
index 0000000..7f9942a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceSplitPaneUI.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JComponent;
+import javax.swing.JSplitPane;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicSplitPaneDivider;
+import javax.swing.plaf.basic.BasicSplitPaneUI;
+
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSplitPaneDivider;
+
+/**
+ * UI for split panes in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceSplitPaneUI extends BasicSplitPaneUI {
+ /**
+ * Property change listener that listens on changes to
+ * {@link JSplitPane#ORIENTATION_PROPERTY} property.
+ */
+ protected PropertyChangeListener substancePropertyListener;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceSplitPaneUI();
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (JSplitPane.ORIENTATION_PROPERTY.equals(evt
+ .getPropertyName())) {
+ SubstanceSplitPaneDivider substanceDivider = (SubstanceSplitPaneDivider) SubstanceSplitPaneUI.this.divider;
+ substanceDivider.updateOneTouchButtons((Integer) evt
+ .getNewValue());
+ }
+ };
+ };
+ this.splitPane
+ .addPropertyChangeListener(this.substancePropertyListener);
+ }
+
+ @Override
+ protected void uninstallListeners() {
+ this.splitPane
+ .removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSplitPaneUI#createDefaultDivider()
+ */
+ @Override
+ public BasicSplitPaneDivider createDefaultDivider() {
+ return new SubstanceSplitPaneDivider(this);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTabbedPaneUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTabbedPaneUI.java
new file mode 100755
index 0000000..fd1ac7e
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTabbedPaneUI.java
@@ -0,0 +1,2804 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.geom.*;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicTabbedPaneUI;
+import javax.swing.text.View;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.lafwidget.utils.RenderingUtils;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.SubstanceConstants.*;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.api.tabbed.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionMultiTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.RepaintCallback;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.StateContributionInfo;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
+import org.pushingpixels.substance.internal.utils.scroll.SubstanceScrollButton;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.RepeatBehavior;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.TimelineCallback;
+import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
+
+/**
+ * UI for tabbed panes in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTabbedPaneUI extends BasicTabbedPaneUI {
+ /**
+ * Current mouse location.
+ */
+ protected Point substanceMouseLocation;
+
+ /**
+ * Hash map for storing already computed backgrounds.
+ */
+ private static LazyResettableHashMap<BufferedImage> backgroundMap = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceTabbedPaneUI.background");
+
+ /**
+ * Hash map for storing already computed backgrounds.
+ */
+ private static LazyResettableHashMap<BufferedImage> closeButtonMap = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceTabbedPaneUI.closeButton");
+
+ /**
+ * Key - tab component. Value - the looping timeline that animates the tab
+ * component when it's marked as modified (with
+ * {@link SubstanceLookAndFeel#WINDOW_MODIFIED} property).
+ */
+ private Map<Component, Timeline> modifiedTimelines;
+
+ /**
+ * Currently selected index (for selection animations).
+ */
+ private int currSelectedIndex;
+
+ private StateTransitionMultiTracker<Integer> stateTransitionMultiTracker;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceTabbedPaneUI();
+ }
+
+ /**
+ * Mouse handler for rollover effects.
+ */
+ protected MouseRolloverHandler substanceRolloverHandler;
+
+ /**
+ * Tracks changes to the tabbed pane contents. Each tab component is tracked
+ * for changes on the {@link SubstanceLookAndFeel#WINDOW_MODIFIED} property.
+ */
+ protected TabbedContainerListener substanceContainerListener;
+
+ /**
+ * Listener for animation effects on tab selection.
+ */
+ protected ChangeListener substanceSelectionListener;
+
+ private boolean substanceContentOpaque;
+
+ /**
+ * Tracks changes to the tabbed pane contents. Each tab component is tracked
+ * for changes on the {@link SubstanceLookAndFeel#WINDOW_MODIFIED} property.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected final class TabbedContainerListener extends ContainerAdapter {
+ /**
+ * Property change listeners on the tab components.
+ * <p/>
+ * Fixes defect 135 - memory leaks on tabbed panes.
+ */
+ private Map<Component, List<PropertyChangeListener>> listeners = new HashMap<Component, List<PropertyChangeListener>>();
+
+ /**
+ * Creates a new container listener.
+ */
+ public TabbedContainerListener() {
+ }
+
+ /**
+ * Tracks all existing tab component.
+ */
+ protected void trackExistingTabs() {
+ // register listeners on all existing tabs
+ for (int i = 0; i < SubstanceTabbedPaneUI.this.tabPane
+ .getTabCount(); i++) {
+ this.trackTab(SubstanceTabbedPaneUI.this.tabPane
+ .getComponentAt(i));
+ }
+ }
+
+ /**
+ * Tracks changes in a single tab component.
+ *
+ * @param tabComponent
+ * Tab component.
+ */
+ protected void trackTab(final Component tabComponent) {
+ if (tabComponent == null)
+ return;
+
+ PropertyChangeListener tabModifiedListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (SubstanceLookAndFeel.WINDOW_MODIFIED.equals(evt
+ .getPropertyName())) {
+ Object oldValue = evt.getOldValue();
+ Object newValue = evt.getNewValue();
+ boolean wasModified = Boolean.TRUE.equals(oldValue);
+ boolean isModified = Boolean.TRUE.equals(newValue);
+
+ if (wasModified) {
+ if (!isModified) {
+ Timeline modifiedTimeline = modifiedTimelines
+ .get(tabComponent);
+ modifiedTimeline.cancel();
+ modifiedTimelines.remove(tabComponent);
+ }
+ } else {
+ if (isModified) {
+ int tabIndex = SubstanceTabbedPaneUI.this.tabPane
+ .indexOfComponent(tabComponent);
+ if (tabIndex >= 0) {
+ trackTabModification(tabIndex, tabComponent);
+ }
+ }
+ }
+ }
+ }
+ };
+ tabComponent.addPropertyChangeListener(tabModifiedListener);
+ // fix for defect 135 - memory leaks on tabbed panes
+ List<PropertyChangeListener> currList = this.listeners
+ .get(tabComponent);
+ if (currList == null)
+ currList = new LinkedList<PropertyChangeListener>();
+ currList.add(tabModifiedListener);
+ // System.err.println(this.hashCode() + " adding for " +
+ // tabComponent.hashCode());
+ this.listeners.put(tabComponent, currList);
+ // Fix for defect 104 - a 'modified' component is added to
+ // the tabbed pane. In this case it should be animated from the
+ // beginning.
+ if (tabComponent instanceof JComponent) {
+ if (Boolean.TRUE
+ .equals(((JComponent) tabComponent)
+ .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED))) {
+ int tabIndex = SubstanceTabbedPaneUI.this.tabPane
+ .indexOfComponent(tabComponent);
+ if (tabIndex >= 0) {
+ trackTabModification(tabIndex, tabComponent);
+ }
+ }
+ }
+ }
+
+ /**
+ * Stops tracking changes to a single tab component.
+ *
+ * @param tabComponent
+ * Tab component.
+ */
+ protected void stopTrackTab(final Component tabComponent) {
+ if (tabComponent == null)
+ return;
+
+ List<PropertyChangeListener> pclList = this.listeners
+ .get(tabComponent);
+ if (pclList != null) {
+ for (PropertyChangeListener pcl : pclList)
+ tabComponent.removePropertyChangeListener(pcl);
+ }
+
+ this.listeners.put(tabComponent, null);
+ }
+
+ /**
+ * Stops tracking all tab components.
+ */
+ protected void stopTrackExistingTabs() {
+ // register listeners on all existing tabs
+ for (int i = 0; i < SubstanceTabbedPaneUI.this.tabPane
+ .getTabCount(); i++) {
+ this.stopTrackTab(SubstanceTabbedPaneUI.this.tabPane
+ .getComponentAt(i));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejava.awt.event.ContainerAdapter#componentAdded(java.awt.event.
+ * ContainerEvent)
+ */
+ @Override
+ public void componentAdded(final ContainerEvent e) {
+ final Component tabComponent = e.getChild();
+ if (tabComponent instanceof UIResource)
+ return;
+ this.trackTab(tabComponent);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejava.awt.event.ContainerAdapter#componentRemoved(java.awt.event.
+ * ContainerEvent)
+ */
+ @Override
+ public void componentRemoved(ContainerEvent e) {
+ // fix for defect 135 - memory leaks on tabbed panes
+ final Component tabComponent = e.getChild();
+ if (tabComponent == null)
+ return;
+ // System.err.println(this.hashCode() + " removing for " +
+ // tabComponent.hashCode());
+ if (tabComponent instanceof UIResource)
+ return;
+ for (PropertyChangeListener pcl : this.listeners.get(tabComponent))
+ tabComponent.removePropertyChangeListener(pcl);
+ this.listeners.get(tabComponent).clear();
+ this.listeners.remove(tabComponent);
+
+ // has running timeline?
+ Timeline timeline = modifiedTimelines.get(tabComponent);
+ if (timeline != null) {
+ timeline.cancel();
+ modifiedTimelines.remove(tabComponent);
+ }
+
+ // this.cleanListeners(tabComponent);
+ }
+
+ }
+
+ /**
+ * Listener for rollover animation effects.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class MouseRolloverHandler implements MouseListener,
+ MouseMotionListener {
+ /**
+ * Index of the tab that was rolloed over on the previous mouse event.
+ */
+ int prevRolledOver = -1;
+
+ /**
+ * Indicates whether the previous mouse event was located in a close
+ * button.
+ */
+ boolean prevInCloseButton = false;
+
+ /**
+ * Tab index of the last mouse pressed event that happened in a close
+ * button.
+ */
+ int tabOfPressedCloseButton = -1;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ final int tabIndex = SubstanceTabbedPaneUI.this.tabForCoordinate(
+ SubstanceTabbedPaneUI.this.tabPane, e.getX(), e.getY());
+ TabCloseCallback closeCallback = SubstanceCoreUtilities
+ .getTabCloseCallback(e, SubstanceTabbedPaneUI.this.tabPane,
+ tabIndex);
+ if (closeCallback == null)
+ return;
+
+ final TabCloseKind tabCloseKind = closeCallback.onAreaClick(
+ SubstanceTabbedPaneUI.this.tabPane, tabIndex, e);
+ if (tabCloseKind == TabCloseKind.NONE)
+ return;
+
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ SubstanceTabbedPaneUI.this.tryCloseTabs(tabIndex,
+ tabCloseKind);
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent
+ * )
+ */
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ this.handleMouseMoveDrag(e);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ setRolloverTab(tabForCoordinate(tabPane, e.getX(), e.getY()));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (!tabPane.isEnabled()) {
+ return;
+ }
+ int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());
+ if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
+ Rectangle rect = new Rectangle();
+ rect = getTabBounds(tabIndex, rect);
+ Rectangle close = getCloseButtonRectangleForEvents(tabIndex,
+ rect.x, rect.y, rect.width, rect.height);
+ boolean inCloseButton = close.contains(e.getPoint());
+ this.tabOfPressedCloseButton = inCloseButton ? tabIndex : -1;
+ if (tabIndex != tabPane.getSelectedIndex()) {
+ // enhancement 307 - don't select tab on pressing its
+ // close button
+ if (inCloseButton) {
+ return;
+ }
+ // Clicking on unselected tab, change selection, do NOT
+ // request focus.
+ // This will trigger the focusIndex to change by way
+ // of stateChanged.
+ tabPane.setSelectedIndex(tabIndex);
+ } else if (tabPane.isRequestFocusEnabled()) {
+ // Clicking on selected tab, try and give the tabbedpane
+ // focus. Repaint will occur in focusGained.
+ tabPane.requestFocus();
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent
+ * )
+ */
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ this.handleMouseMoveDrag(e);
+ }
+
+ /**
+ * Handles the move and drag mouse events.
+ *
+ * @param e
+ * Mouse event to handle.
+ */
+ private void handleMouseMoveDrag(MouseEvent e) {
+ if (e.getSource() != tabPane)
+ return;
+
+ setRolloverTab(tabForCoordinate(tabPane, e.getX(), e.getY()));
+ if (!AnimationConfigurationManager.getInstance()
+ .isAnimationAllowed(AnimationFacet.ROLLOVER, tabPane)) {
+ return;
+ }
+
+ SubstanceTabbedPaneUI.this.substanceMouseLocation = e.getPoint();
+ int currRolledOver = SubstanceTabbedPaneUI.this.getRolloverTab();
+ TabCloseCallback tabCloseCallback = SubstanceCoreUtilities
+ .getTabCloseCallback(e, tabPane, currRolledOver);
+ // System.err.println("Mouse moved " + currRolledOver + ":" +
+ // prevRolledOver);
+ if (currRolledOver == this.prevRolledOver) {
+ if (currRolledOver >= 0) {
+ Rectangle rect = new Rectangle();
+ rect = getTabBounds(currRolledOver, rect);
+ Rectangle close = getCloseButtonRectangleForEvents(
+ currRolledOver, rect.x, rect.y, rect.width,
+ rect.height);
+ // System.out.println("move " + rect + " " + close + " "
+ // + e.getPoint());
+ boolean inCloseButton = close.contains(e.getPoint());
+ if (this.prevInCloseButton == inCloseButton)
+ return;
+ this.prevInCloseButton = inCloseButton;
+ if (tabCloseCallback != null) {
+ if (inCloseButton) {
+ String closeButtonTooltip = tabCloseCallback
+ .getCloseButtonTooltip(tabPane,
+ currRolledOver);
+ tabPane.setToolTipTextAt(currRolledOver,
+ closeButtonTooltip);
+ } else {
+ String areaTooltip = tabCloseCallback
+ .getAreaTooltip(tabPane, currRolledOver);
+ tabPane.setToolTipTextAt(currRolledOver,
+ areaTooltip);
+ }
+ }
+ if ((currRolledOver >= 0)
+ && (currRolledOver < tabPane.getTabCount())) {
+ StateTransitionTracker tracker = getTracker(
+ currRolledOver, true,
+ currRolledOver == currSelectedIndex);
+ tracker.getModel().setRollover(false);
+ tracker.endTransition();
+ }
+ }
+ } else {
+ if ((this.prevRolledOver >= 0)
+ && (this.prevRolledOver < tabPane.getTabCount())
+ && tabPane.isEnabledAt(this.prevRolledOver)) {
+ StateTransitionTracker tracker = getTracker(prevRolledOver,
+ true, prevRolledOver == currSelectedIndex);
+ tracker.getModel().setRollover(false);
+ }
+ if ((currRolledOver >= 0)
+ && (currRolledOver < tabPane.getTabCount())
+ && tabPane.isEnabledAt(currRolledOver)) {
+ StateTransitionTracker tracker = getTracker(currRolledOver,
+ false, currRolledOver == currSelectedIndex);
+ tracker.getModel().setRollover(true);
+ }
+ }
+ this.prevRolledOver = currRolledOver;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseExited(MouseEvent e) {
+ setRolloverTab(-1);
+ // fix for bug 69 - non-selected non-rollover tab
+ // may remain with close button after moving mouse quickly
+ // to inner JTabbedPane
+ if ((this.prevRolledOver >= 0)
+ && (this.prevRolledOver < SubstanceTabbedPaneUI.this.tabPane
+ .getTabCount())
+ && SubstanceTabbedPaneUI.this.tabPane
+ .isEnabledAt(this.prevRolledOver)) {
+ // only the previously rolled-over tab needs to be
+ // repainted (fade-out) instead of repainting the
+ // whole tab as before.
+ StateTransitionTracker tracker = getTracker(prevRolledOver,
+ true, prevRolledOver == currSelectedIndex);
+ tracker.getModel().setRollover(false);
+
+ if (SubstanceCoreUtilities.getTabCloseCallback(e, tabPane,
+ this.prevRolledOver) != null) {
+ tabPane.setToolTipTextAt(this.prevRolledOver, null);
+ }
+ }
+ this.prevRolledOver = -1;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseReleased(final MouseEvent e) {
+ // enhancement 307 - moving the tab close to be on mouse release
+ // and not on mouse press.
+ final int tabIndex = SubstanceTabbedPaneUI.this.tabForCoordinate(
+ SubstanceTabbedPaneUI.this.tabPane, e.getX(), e.getY());
+ // check that the mouse release is on the same tab as
+ // mouse press, and that the tab has close button
+ if (SubstanceCoreUtilities.hasCloseButton(
+ SubstanceTabbedPaneUI.this.tabPane, tabIndex)
+ && (tabIndex == this.tabOfPressedCloseButton)) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if ((tabIndex >= 0)
+ && SubstanceTabbedPaneUI.this.tabPane
+ .isEnabledAt(tabIndex)) {
+ Rectangle rect = new Rectangle();
+ rect = SubstanceTabbedPaneUI.this.getTabBounds(
+ tabIndex, rect);
+
+ Rectangle close = SubstanceTabbedPaneUI.this
+ .getCloseButtonRectangleForEvents(tabIndex,
+ rect.x, rect.y, rect.width,
+ rect.height);
+ // System.out.println("press " + close + " "
+ // + e.getPoint());
+ if (close.contains(e.getPoint())) {
+ TabCloseCallback closeCallback = SubstanceCoreUtilities
+ .getTabCloseCallback(
+ e,
+ SubstanceTabbedPaneUI.this.tabPane,
+ tabIndex);
+
+ TabCloseKind tabCloseKind = (closeCallback == null) ? TabCloseKind.THIS
+ : closeCallback
+ .onCloseButtonClick(
+ SubstanceTabbedPaneUI.this.tabPane,
+ tabIndex, e);
+
+ SubstanceTabbedPaneUI.this.tryCloseTabs(
+ tabIndex, tabCloseKind);
+ }
+ }
+ }
+ });
+ this.tabOfPressedCloseButton = -1;
+ }
+ }
+ }
+
+ /**
+ * Creates the new UI delegate.
+ */
+ public SubstanceTabbedPaneUI() {
+ super();
+ this.stateTransitionMultiTracker = new StateTransitionMultiTracker<Integer>();
+ this.currSelectedIndex = -1;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ // Install listener to repaint the tabbed pane
+ // on mouse move (for rollover effects).
+ this.substanceRolloverHandler = new MouseRolloverHandler();
+ this.tabPane.addMouseMotionListener(this.substanceRolloverHandler);
+ this.tabPane.addMouseListener(this.substanceRolloverHandler);
+
+ // Add container listener to wire property change listener
+ // on each tab in the tabbed pane.
+ this.substanceContainerListener = new TabbedContainerListener();
+ this.substanceContainerListener.trackExistingTabs();
+
+ for (int i = 0; i < this.tabPane.getTabCount(); i++) {
+ Component tabComp = this.tabPane.getComponentAt(i);
+ if (SubstanceCoreUtilities.isTabModified(tabComp)) {
+ trackTabModification(i, tabComp);
+ }
+ }
+
+ this.tabPane.addContainerListener(this.substanceContainerListener);
+
+ this.substanceSelectionListener = new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (SubstanceTabbedPaneUI.this.tabPane == null)
+ return;
+ int selected = SubstanceTabbedPaneUI.this.tabPane
+ .getSelectedIndex();
+
+ // fix for issue 437 - track the selection change,
+ // fading out the previously selected tab
+ if ((currSelectedIndex >= 0)
+ && (currSelectedIndex < SubstanceTabbedPaneUI.this.tabPane
+ .getTabCount())
+ && SubstanceTabbedPaneUI.this.tabPane
+ .isEnabledAt(currSelectedIndex)) {
+ StateTransitionTracker tracker = getTracker(
+ currSelectedIndex,
+ getRolloverTabIndex() == currSelectedIndex,
+ true);
+ tracker.getModel().setSelected(false);
+ }
+ currSelectedIndex = selected;
+ if ((selected >= 0)
+ && (selected < SubstanceTabbedPaneUI.this.tabPane
+ .getTabCount())
+ && SubstanceTabbedPaneUI.this.tabPane
+ .isEnabledAt(selected)) {
+ StateTransitionTracker tracker = getTracker(
+ selected,
+ getRolloverTabIndex() == selected, false);
+ tracker.getModel().setSelected(true);
+ }
+ }
+ });
+ }
+ };
+ this.tabPane.getModel().addChangeListener(
+ this.substanceSelectionListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ super.uninstallListeners();
+ if (this.substanceRolloverHandler != null) {
+ this.tabPane
+ .removeMouseMotionListener(this.substanceRolloverHandler);
+ this.tabPane.removeMouseListener(this.substanceRolloverHandler);
+ this.substanceRolloverHandler = null;
+ }
+ if (this.substanceContainerListener != null) {
+ for (Map.Entry<Component, List<PropertyChangeListener>> entry : this.substanceContainerListener.listeners
+ .entrySet()) {
+ Component comp = entry.getKey();
+ // System.out.println(this.containerListener.hashCode() +"
+ // removing all for" + comp.hashCode());
+ for (PropertyChangeListener pcl : entry.getValue()) {
+ comp.removePropertyChangeListener(pcl);
+ }
+ }
+ this.substanceContainerListener.listeners.clear();
+
+ this.tabPane
+ .removeContainerListener(this.substanceContainerListener);
+ this.substanceContainerListener = null;
+ }
+ this.tabPane.getModel().removeChangeListener(
+ this.substanceSelectionListener);
+ this.substanceSelectionListener = null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+
+ this.substanceContentOpaque = UIManager
+ .getBoolean("TabbedPane.contentOpaque");
+
+ this.modifiedTimelines = new HashMap<Component, Timeline>();
+ this.currSelectedIndex = this.tabPane.getSelectedIndex();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#uninstallDefaults()
+ */
+ @Override
+ protected void uninstallDefaults() {
+ for (Timeline timeline : this.modifiedTimelines.values())
+ timeline.cancel();
+ this.modifiedTimelines.clear();
+ super.uninstallDefaults();
+ }
+
+ /**
+ * Retrieves tab background.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param width
+ * Tab width.
+ * @param height
+ * Tab height.
+ * @param tabPlacement
+ * Tab placement.
+ * @param fillScheme
+ * Color scheme for coloring the background.
+ * @param borderScheme
+ * Color scheme for coloring the border.
+ * @param paintOnlyBorder
+ * If <code>true</code>, only the border will be painted.
+ * @return Tab background of specified parameters.
+ */
+ private static BufferedImage getTabBackground(JTabbedPane tabPane,
+ int width, int height, int tabPlacement,
+ SubstanceColorScheme fillScheme, SubstanceColorScheme borderScheme,
+ boolean paintOnlyBorder) {
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(tabPane);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(tabPane);
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(tabPane);
+
+ int borderDelta = (int) Math.ceil(2.0 * SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane)));
+ int borderInsets = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane)) / 2.0);
+ int dy = 2 + borderDelta;
+ Set<Side> straightSides = EnumSet.of(Side.BOTTOM);
+
+ int cornerRadius = height / 3;
+ if (shaper instanceof ClassicButtonShaper) {
+ cornerRadius = (int) SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(tabPane));
+ if ((tabPlacement == TOP) || (tabPlacement == BOTTOM))
+ width -= 1;
+ else
+ height -= 1;
+ }
+
+ GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(width,
+ height + dy, cornerRadius, straightSides, borderInsets);
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D resGraphics = result.createGraphics();
+
+ if (!paintOnlyBorder) {
+ fillPainter.paintContourBackground(resGraphics, tabPane, width,
+ height + dy, contour, false, fillScheme, true);
+ }
+
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane));
+ GeneralPath contourInner = borderPainter.isPaintingInnerContour() ? SubstanceOutlineUtilities
+ .getBaseOutline(width, height + dy, cornerRadius
+ - borderThickness, straightSides, borderThickness
+ + borderInsets)
+ : null;
+
+ borderPainter.paintBorder(resGraphics, tabPane, width, height + dy,
+ contour, contourInner, borderScheme);
+
+ resGraphics.dispose();
+ return result;
+ }
+
+ /**
+ * Retrieves tab background that will be shown on the screen. Unlike
+ *
+ * , the result is rotated as necessary (for {@link SwingConstants#LEFT} and
+ * {@link SwingConstants#RIGHT} placement) and blended for selected tabs.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ * @param width
+ * Tab width.
+ * @param height
+ * Tab height.
+ * @param isSelected
+ * Indication whether the tab is selected.
+ * @param tabPlacement
+ * Tab placement.
+ * @param side
+ * Tab open side.
+ * @param colorScheme
+ * Color scheme for coloring the background.
+ * @param borderScheme
+ * Color scheme for coloring the border.
+ * @return Tab background of specified parameters.
+ */
+ private static BufferedImage getFinalTabBackgroundImage(
+ JTabbedPane tabPane, int tabIndex, int x, int y, int width,
+ int height, boolean isSelected, int tabPlacement,
+ SubstanceConstants.Side side, SubstanceColorScheme colorScheme,
+ SubstanceColorScheme borderScheme) {
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(tabPane);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(tabPane);
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(tabPane);
+ Component compForBackground = tabPane.getTabComponentAt(tabIndex);
+ if (compForBackground == null)
+ compForBackground = tabPane.getComponentAt(tabIndex);
+ if (compForBackground == null)
+ compForBackground = tabPane;
+ Color tabColor = compForBackground.getBackground();
+ if (isSelected && (tabColor instanceof UIResource)) {
+ // special handling of tabs placed in decoration areas
+ tabColor = SubstanceColorUtilities
+ .getBackgroundFillColor(compForBackground);
+ }
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
+ isSelected, tabPlacement, fillPainter.getDisplayName(),
+ borderPainter.getDisplayName(), shaper.getDisplayName(),
+ tabPlacement == SwingConstants.BOTTOM, side.name(), colorScheme
+ .getDisplayName(), borderScheme.getDisplayName(),
+ tabColor);
+
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(tabPane);
+ BufferedImage result = SubstanceTabbedPaneUI.backgroundMap.get(key);
+ if (result == null) {
+ BufferedImage backgroundImage = null;
+
+ switch (tabPlacement) {
+ case BOTTOM:
+ return SubstanceImageCreator.getRotated(
+ getFinalTabBackgroundImage(tabPane, tabIndex, x, y,
+ width, height, isSelected, SwingConstants.TOP,
+ side, colorScheme, borderScheme), 2);
+ case TOP:
+ case LEFT:
+ case RIGHT:
+ backgroundImage = SubstanceTabbedPaneUI.getTabBackground(
+ tabPane, width, height, SwingConstants.TOP,
+ colorScheme, borderScheme, false);
+ if (isSelected) {
+ int fw = backgroundImage.getWidth();
+ int fh = backgroundImage.getHeight();
+ BufferedImage fade = SubstanceCoreUtilities.getBlankImage(
+ fw, fh);
+ Graphics2D fadeGraphics = fade.createGraphics();
+ fadeGraphics.setColor(tabColor);
+ fadeGraphics.fillRect(0, 0, fw, fh);
+ if (skin.getWatermark() != null) {
+ fadeGraphics.translate(-x, -y);
+ skin.getWatermark().drawWatermarkImage(fadeGraphics,
+ tabPane, x, y, fw, fh);
+ fadeGraphics.translate(x, y);
+ }
+ fadeGraphics.drawImage(SubstanceTabbedPaneUI
+ .getTabBackground(tabPane, width, height,
+ tabPlacement, colorScheme, borderScheme,
+ true), 0, 0, null);
+
+ backgroundImage = SubstanceCoreUtilities
+ .blendImagesVertical(backgroundImage, fade, skin
+ .getSelectedTabFadeStart(), skin
+ .getSelectedTabFadeEnd());
+ }
+ }
+ SubstanceTabbedPaneUI.backgroundMap.put(key, backgroundImage);
+ }
+ return backgroundMap.get(key);
+ }
+
+ /**
+ * Retrieves the image of the close button.
+ *
+ * @param tabPane
+ * Tabbed pane.
+ * @param width
+ * Close button width.
+ * @param height
+ * Close button height.
+ * @param toPaintBorder
+ * Indication whether the button background (including contour)
+ * needs to be painted.
+ * @param fillScheme
+ * Color scheme for coloring the background.
+ * @param markScheme
+ * Color scheme for painting the close mark.
+ * @return Image of the close button of specified parameters.
+ */
+ private static BufferedImage getCloseButtonImage(JTabbedPane tabPane,
+ int width, int height, boolean toPaintBorder,
+ SubstanceColorScheme fillScheme, SubstanceColorScheme markScheme) {
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(tabPane);
+ if (fillPainter == null)
+ return null;
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height,
+ toPaintBorder, fillPainter.getDisplayName(), fillScheme
+ .getDisplayName(), markScheme.getDisplayName());
+ BufferedImage result = SubstanceTabbedPaneUI.closeButtonMap.get(key);
+ if (result == null) {
+ result = SubstanceCoreUtilities.getBlankImage(width, height);
+ Graphics2D finalGraphics = (Graphics2D) result.getGraphics();
+
+ if (toPaintBorder) {
+ GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(
+ width, height, 1, null);
+ fillPainter.paintContourBackground(finalGraphics, tabPane,
+ width, height, contour, false, fillScheme, true);
+ // finalGraphics.drawImage(background, 0, 0, null);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(tabPane);
+ finalGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ borderPainter.paintBorder(finalGraphics, tabPane, width,
+ height, contour, null, markScheme);
+ }
+
+ finalGraphics.setStroke(new BasicStroke(SubstanceSizeUtils
+ .getTabCloseButtonStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane))));
+
+ int delta = (int) (Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane))));
+ if (delta % 2 != 0)
+ delta--;
+ int iconSize = width - delta;
+
+ Icon closeIcon = SubstanceImageCreator.getCloseIcon(iconSize,
+ markScheme, markScheme);
+ closeIcon.paintIcon(tabPane, finalGraphics, delta / 2, delta / 2);
+
+ SubstanceTabbedPaneUI.closeButtonMap.put(key, result);
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTabbedPaneUI#paintTabBackground(java.awt.
+ * Graphics, int, int, int, int, int, int, boolean)
+ */
+ @Override
+ protected void paintTabBackground(Graphics g, int tabPlacement,
+ final int tabIndex, final int x, final int y, int w, int h,
+ boolean isSelected) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.tabPane, g));
+
+ boolean isEnabled = this.tabPane.isEnabledAt(tabIndex);
+ ComponentState currState = this.getTabState(tabIndex);
+ StateTransitionTracker.ModelStateInfo modelStateInfo = this
+ .getModelStateInfo(tabIndex);
+
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tabPane, tabIndex,
+ ColorSchemeAssociationKind.TAB_BORDER, currState);
+ SubstanceColorScheme baseColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tabPane, tabIndex,
+ ColorSchemeAssociationKind.TAB, currState);
+ BufferedImage fullOpacity = null;
+
+ // check if have windowModified property
+ Component comp = this.tabPane.getComponentAt(tabIndex);
+ boolean isWindowModified = SubstanceCoreUtilities.isTabModified(comp);
+ boolean toMarkModifiedCloseButton = SubstanceCoreUtilities
+ .toAnimateCloseIconOfModifiedTab(this.tabPane, tabIndex);
+ if (isWindowModified && isEnabled && !toMarkModifiedCloseButton) {
+ SubstanceColorScheme colorScheme2 = SubstanceColorSchemeUtilities.YELLOW;
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities.ORANGE;
+
+ float cyclePos = this.modifiedTimelines.get(comp)
+ .getTimelinePosition();
+
+ BufferedImage layer1 = SubstanceTabbedPaneUI
+ .getFinalTabBackgroundImage(this.tabPane, tabIndex, x, y,
+ w, h, isSelected, tabPlacement,
+ SubstanceConstants.Side.BOTTOM, colorScheme,
+ baseBorderScheme);
+ BufferedImage layer2 = SubstanceTabbedPaneUI
+ .getFinalTabBackgroundImage(this.tabPane, tabIndex, x, y,
+ w, h, isSelected, tabPlacement,
+ SubstanceConstants.Side.BOTTOM, colorScheme2,
+ baseBorderScheme);
+
+ fullOpacity = SubstanceCoreUtilities.getBlankImage(w, h);
+ Graphics2D g2d = fullOpacity.createGraphics();
+ if (cyclePos < 1.0f)
+ g2d.drawImage(layer1, 0, 0, null);
+ if (cyclePos > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver.derive(cyclePos));
+ g2d.drawImage(layer2, 0, 0, null);
+ }
+ g2d.dispose();
+ } else {
+ BufferedImage layerBase = SubstanceTabbedPaneUI
+ .getFinalTabBackgroundImage(this.tabPane, tabIndex, x, y,
+ w, h, isSelected, tabPlacement,
+ SubstanceConstants.Side.BOTTOM, baseColorScheme,
+ baseBorderScheme);
+
+ if ((modelStateInfo == null) || currState.isDisabled()
+ || (modelStateInfo.getStateContributionMap().size() == 1)) {
+ fullOpacity = layerBase;
+ } else {
+ fullOpacity = SubstanceCoreUtilities.getBlankImage(w, h);
+ Graphics2D g2d = fullOpacity.createGraphics();
+ // draw the base layer
+ g2d.drawImage(layerBase, 0, 0, null);
+
+ // draw the other active layers
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : modelStateInfo
+ .getStateContributionMap().entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float stateContribution = activeEntry.getValue()
+ .getContribution();
+ if (stateContribution > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(stateContribution));
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tabPane, tabIndex,
+ ColorSchemeAssociationKind.TAB,
+ activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tabPane, tabIndex,
+ ColorSchemeAssociationKind.TAB_BORDER,
+ activeState);
+ BufferedImage layer = SubstanceTabbedPaneUI
+ .getFinalTabBackgroundImage(this.tabPane,
+ tabIndex, x, y, w, h, isSelected,
+ tabPlacement,
+ SubstanceConstants.Side.BOTTOM,
+ fillScheme, borderScheme);
+ g2d.drawImage(layer, 0, 0, null);
+ }
+ }
+ }
+ }
+
+ // at this point the 'fillOpacity' has all the relevant layers for the
+ // fill + border
+
+ SubstanceColorScheme baseMarkScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tabPane, tabIndex,
+ ColorSchemeAssociationKind.MARK, currState);
+
+ // fix for defect 138
+ graphics.clip(new Rectangle(x, y, w, h));
+
+ boolean isRollover = (this.getRolloverTab() == tabIndex);
+
+ float finalAlpha = 0.5f;
+ StateTransitionTracker tabTracker = this.stateTransitionMultiTracker
+ .getTracker(tabIndex);
+ if (modelStateInfo != null) {
+ finalAlpha += 0.5f * tabTracker
+ .getFacetStrength(ComponentStateFacet.ROLLOVER);
+ if (tabTracker.getFacetStrength(ComponentStateFacet.SELECTION) == 1.0f) {
+ finalAlpha = 1.0f;
+ }
+ } else {
+ ComponentState tabState = getTabState(tabIndex);
+ if (tabState.isFacetActive(ComponentStateFacet.ROLLOVER)
+ || tabState.isFacetActive(ComponentStateFacet.SELECTION)) {
+ finalAlpha = 1.0f;
+ }
+ }
+
+ finalAlpha *= SubstanceColorSchemeUtilities.getAlpha(this.tabPane
+ .getComponentAt(tabIndex), currState);
+
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.tabPane, finalAlpha, g));
+ graphics.drawImage(fullOpacity, x, y, null);
+
+ // Check if requested to paint close buttons.
+ if (SubstanceCoreUtilities.hasCloseButton(this.tabPane, tabIndex)
+ && isEnabled) {
+
+ float alpha = (isSelected || isRollover) ? 1.0f : 0.0f;
+ if (!isSelected) {
+ if (tabTracker != null) {
+ alpha = tabTracker
+ .getFacetStrength(ComponentStateFacet.ROLLOVER);
+ }
+ }
+ if (alpha > 0.0) {
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.tabPane, finalAlpha * alpha, g));
+
+ // paint close button
+ Rectangle orig = this.getCloseButtonRectangleForDraw(tabIndex,
+ x, y, w, h);
+
+ boolean toPaintCloseBorder = false;
+ if (isRollover) {
+ if (this.substanceMouseLocation != null) {
+ Rectangle bounds = new Rectangle();
+ bounds = this.getTabBounds(tabIndex, bounds);
+ if (toRotateTabsOnPlacement(tabPlacement)) {
+ bounds = new Rectangle(bounds.x, bounds.y,
+ bounds.height, bounds.width);
+ }
+ Rectangle rect = this.getCloseButtonRectangleForEvents(
+ tabIndex, bounds.x, bounds.y, bounds.width,
+ bounds.height);
+ // System.out.println("paint " + bounds + " " + rect +"
+ // "
+ // + mouseLocation);
+ if (rect.contains(this.substanceMouseLocation)) {
+ toPaintCloseBorder = true;
+ }
+ }
+ }
+
+ if (isWindowModified && isEnabled && toMarkModifiedCloseButton) {
+ SubstanceColorScheme colorScheme2 = SubstanceColorSchemeUtilities.YELLOW;
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities.ORANGE;
+
+ float cyclePos = this.modifiedTimelines.get(comp)
+ .getTimelinePosition();
+
+ BufferedImage layer1 = SubstanceTabbedPaneUI
+ .getCloseButtonImage(this.tabPane, orig.width,
+ orig.height, toPaintCloseBorder,
+ colorScheme, baseMarkScheme);
+ BufferedImage layer2 = SubstanceTabbedPaneUI
+ .getCloseButtonImage(this.tabPane, orig.width,
+ orig.height, toPaintCloseBorder,
+ colorScheme2, baseMarkScheme);
+
+ if (cyclePos < 1.0f) {
+ graphics.drawImage(layer1, orig.x, orig.y, null);
+ }
+ if (cyclePos > 0.0f) {
+ graphics.setComposite(AlphaComposite.SrcOver
+ .derive(cyclePos));
+ graphics.drawImage(layer2, orig.x, orig.y, null);
+ }
+ } else {
+ BufferedImage layerBase = SubstanceTabbedPaneUI
+ .getCloseButtonImage(this.tabPane, orig.width,
+ orig.height, toPaintCloseBorder,
+ baseColorScheme, baseMarkScheme);
+
+ if ((modelStateInfo == null)
+ || currState.isDisabled()
+ || (modelStateInfo.getStateContributionMap().size() == 1)) {
+ graphics.drawImage(layerBase, orig.x, orig.y, null);
+ } else {
+ BufferedImage complete = SubstanceCoreUtilities
+ .getBlankImage(orig.width, orig.height);
+ Graphics2D g2d = complete.createGraphics();
+ // draw the base layer
+ g2d.drawImage(layerBase, 0, 0, null);
+
+ // draw the other active layers
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : modelStateInfo
+ .getStateContributionMap().entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float stateContribution = activeEntry.getValue()
+ .getContribution();
+ if (stateContribution > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(stateContribution));
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tabPane, tabIndex,
+ ColorSchemeAssociationKind.TAB,
+ activeState);
+ SubstanceColorScheme markScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(
+ this.tabPane,
+ tabIndex,
+ ColorSchemeAssociationKind.MARK,
+ activeState);
+ BufferedImage layer = SubstanceTabbedPaneUI
+ .getCloseButtonImage(this.tabPane,
+ orig.width, orig.height,
+ toPaintCloseBorder, fillScheme,
+ markScheme);
+ g2d.drawImage(layer, 0, 0, null);
+ }
+ }
+ g2d.dispose();
+ graphics.drawImage(complete, orig.x, orig.y, null);
+ }
+ }
+ }
+ }
+
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTabbedPaneUI#paintFocusIndicator(java.awt
+ * .Graphics, int, java.awt.Rectangle[], int, java.awt.Rectangle,
+ * java.awt.Rectangle, boolean)
+ */
+ @Override
+ protected void paintFocusIndicator(Graphics g, int tabPlacement,
+ Rectangle[] rects, int tabIndex, Rectangle iconRect,
+ Rectangle textRect, boolean isSelected) {
+ // empty to remove Basic functionality
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTabbedPaneUI#paintTabBorder(java.awt.Graphics
+ * , int, int, int, int, int, int, boolean)
+ */
+ @Override
+ protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex,
+ int x, int y, int w, int h, boolean isSelected) {
+ // empty to remove Basic functionality
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#createScrollButton(int)
+ */
+ @Override
+ protected JButton createScrollButton(final int direction) {
+ SubstanceScrollButton ssb = new SubstanceScrollButton(direction);
+ Icon icon = new TransitionAwareIcon(ssb,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ // fix for defect 279 - tab pane might not yet have the
+ // font installed.
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(tabPane);
+ return SubstanceImageCreator.getArrowIcon(fontSize,
+ direction, scheme);
+ }
+ }, "substance.tabbedpane.scroll." + direction);
+ ssb.setIcon(icon);
+ return ssb;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateTabHeight(int,
+ * int, int)
+ */
+ @Override
+ protected int calculateTabHeight(int tabPlacement, int tabIndex,
+ int fontHeight) {
+ boolean toSwap = toRotateTabsOnPlacement(tabPlacement);
+ if (toSwap)
+ return this.getTabExtraWidth(tabPlacement, tabIndex)
+ + super.calculateTabWidth(tabPlacement, tabIndex, this
+ .getFontMetrics());
+ return super.calculateTabHeight(tabPlacement, tabIndex, fontHeight);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateTabWidth(int, int,
+ * java.awt.FontMetrics)
+ */
+ @Override
+ protected int calculateTabWidth(int tabPlacement, int tabIndex,
+ FontMetrics metrics) {
+ boolean toSwap = toRotateTabsOnPlacement(tabPlacement);
+ if (toSwap)
+ return super.calculateTabHeight(tabPlacement, tabIndex, metrics
+ .getHeight());
+ int result = this.getTabExtraWidth(tabPlacement, tabIndex)
+ + super.calculateTabWidth(tabPlacement, tabIndex, metrics);
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateMaxTabHeight(int)
+ */
+ @Override
+ protected int calculateMaxTabHeight(int tabPlacement) {
+ if (toRotateTabsOnPlacement(tabPlacement))
+ return super.calculateMaxTabHeight(tabPlacement);
+ int result = 0;
+ for (int i = 0; i < this.tabPane.getTabCount(); i++)
+ result = Math.max(result, this.calculateTabHeight(tabPlacement, i,
+ this.getFontMetrics().getHeight()));
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabRunOverlay(int)
+ */
+ @Override
+ protected int getTabRunOverlay(int tabPlacement) {
+ boolean toSwap = this.toRotateTabsOnPlacement(tabPlacement);
+ if (toSwap)
+ return super.getTabRunOverlay(tabPlacement);
+
+ return 0;
+ }
+
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ int selectedIndex = tabPane.getSelectedIndex();
+ int tabPlacement = tabPane.getTabPlacement();
+
+ ensureCurrentLayout();
+
+ // If scrollable tabs are enabled, the tab area will be
+ // painted by the scrollable tab panel instead.
+ if (tabPane.getLayout().getClass() == TabbedPaneLayout.class) {
+ paintTabArea(g, tabPlacement, selectedIndex);
+ }
+
+ int width = tabPane.getWidth();
+ int height = tabPane.getHeight();
+ Insets insets = tabPane.getInsets();
+
+ int x = insets.left;
+ int y = insets.top;
+ int w = width - insets.right - insets.left;
+ int h = height - insets.top - insets.bottom;
+
+ switch (tabPlacement) {
+ case LEFT:
+ x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
+ w -= (x - insets.left);
+ break;
+ case RIGHT:
+ w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
+ break;
+ case BOTTOM:
+ h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
+ break;
+ case TOP:
+ default:
+ y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
+ h -= (y - insets.top);
+ }
+
+ Graphics2D g2d = (Graphics2D) g.create(x, y, w, h);
+ BackgroundPaintingUtils.update(g2d, c, false);
+
+ paintContentBorder(g, tabPlacement, selectedIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#paintTab(java.awt.Graphics,
+ * int, java.awt.Rectangle[], int, java.awt.Rectangle, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintTab(Graphics g, int tabPlacement, Rectangle[] rects,
+ int tabIndex, Rectangle iconRect, Rectangle textRect) {
+ boolean toSwap = toRotateTabsOnPlacement(tabPlacement);
+ if (toSwap) {
+ Graphics2D tempG = (Graphics2D) g.create();
+ Rectangle tabRect = rects[tabIndex];
+ Rectangle correctRect = new Rectangle(tabRect.x, tabRect.y,
+ tabRect.height, tabRect.width);
+ if (tabPlacement == SwingConstants.LEFT) {
+ // rotate 90 degrees counterclockwise for LEFT orientation
+ tempG.rotate(-Math.PI / 2, tabRect.x, tabRect.y);
+ tempG.translate(-tabRect.height, 0);
+ } else {
+ // rotate 90 degrees clockwise for RIGHT orientation
+ tempG.rotate(Math.PI / 2, tabRect.x, tabRect.y);
+ tempG.translate(0, -tabRect.getWidth());
+ }
+ tempG.setColor(Color.red);
+ rects[tabIndex] = correctRect;
+ super.paintTab(tempG, tabPlacement, rects, tabIndex, iconRect,
+ textRect);
+ rects[tabIndex] = tabRect;
+ tempG.dispose();
+ } else {
+ if (tabPane.getLayout().getClass() == TabbedPaneLayout.class) {
+ super.paintTab(g, tabPlacement, rects, tabIndex, iconRect,
+ textRect);
+ } else {
+ // scrolled tabs are painted by
+ // BasicTabbedPaneUI.ScrollableTabPanel
+ // which does not have the right rendering hints
+ Graphics2D g2d = (Graphics2D) g.create();
+ RenderingUtils.installDesktopHints(g2d, tabPane);
+ super.paintTab(g2d, tabPlacement, rects, tabIndex, iconRect,
+ textRect);
+ g2d.dispose();
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTabbedPaneUI#paintTabArea(java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
+ if (this.substanceContentOpaque) {
+ int width = calculateTabAreaWidth(tabPlacement, runCount,
+ maxTabWidth);
+ if ((tabPlacement == SwingConstants.TOP)
+ || (tabPlacement == SwingConstants.BOTTOM))
+ width = Math.max(width, tabPane.getWidth());
+ int height = calculateTabAreaHeight(tabPlacement, runCount,
+ maxTabHeight);
+ if (toRotateTabsOnPlacement(tabPlacement))
+ height = Math.max(height, tabPane.getHeight());
+
+ // restrict the painting to the tab area only
+ Graphics2D g2d = (Graphics2D) g.create(0, 0, width, height);
+ BackgroundPaintingUtils.update(g2d, this.tabPane, true);
+ g2d.dispose();
+ }
+ super.paintTabArea(g, tabPlacement, selectedIndex);
+ }
+
+ /**
+ * Retrieves the close button rectangle for drawing purposes.
+ *
+ * @param tabIndex
+ * Tab index.
+ * @param x
+ * X coordinate of the tab.
+ * @param y
+ * Y coordinate of the tab.
+ * @param width
+ * The tab width.
+ * @param height
+ * The tab height.
+ * @return The close button rectangle.
+ */
+ protected Rectangle getCloseButtonRectangleForDraw(int tabIndex, int x,
+ int y, int width, int height) {
+ int dimension = SubstanceCoreUtilities.getCloseButtonSize(this.tabPane,
+ tabIndex);
+
+ int borderDelta = (int) Math.ceil(3.0 * SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.tabPane)));
+
+ int xs = this.tabPane.getComponentOrientation().isLeftToRight() ? (x
+ + width - dimension - borderDelta) : (x + borderDelta);
+ int ys = y + (height - dimension) / 2 + 1;
+ return new Rectangle(xs, ys, dimension, dimension);
+ }
+
+ /**
+ * Retrieves the close button rectangle for event handling.
+ *
+ * @param tabIndex
+ * Tab index.
+ * @param x
+ * X coordinate of the tab.
+ * @param y
+ * Y coordinate of the tab.
+ * @param w
+ * The tab width.
+ * @param h
+ * The tab height.
+ * @return The close button rectangle.
+ */
+ protected Rectangle getCloseButtonRectangleForEvents(int tabIndex, int x,
+ int y, int w, int h) {
+ int tabPlacement = this.tabPane.getTabPlacement();
+ boolean toSwap = toRotateTabsOnPlacement(tabPlacement);
+ if (!toSwap) {
+ return this.getCloseButtonRectangleForDraw(tabIndex, x, y, w, h);
+ }
+ int dimension = SubstanceCoreUtilities.getCloseButtonSize(this.tabPane,
+ tabIndex);
+
+ Point2D transCorner = null;
+ Rectangle rectForDraw = this.getCloseButtonRectangleForDraw(tabIndex,
+ x, y, h, w);
+ if (tabPlacement == SwingConstants.LEFT) {
+ AffineTransform trans = new AffineTransform();
+ trans.rotate(-Math.PI / 2, x, y);
+ trans.translate(-h, 0);
+ Point2D.Double origCorner = new Point2D.Double(rectForDraw
+ .getMaxX(), rectForDraw.getMinY());
+ transCorner = trans.transform(origCorner, null);
+ } else {
+ // rotate 90 degrees clockwise for RIGHT orientation
+ AffineTransform trans = new AffineTransform();
+ trans.rotate(Math.PI / 2, x, y);
+ trans.translate(0, -w);
+ Point2D.Double origCorner = new Point2D.Double(rectForDraw
+ .getMinX(), rectForDraw.getMaxY());
+ transCorner = trans.transform(origCorner, null);
+ }
+ return new Rectangle((int) transCorner.getX(),
+ (int) transCorner.getY(), dimension, dimension);
+ }
+
+ /**
+ * Implementation of the fade tracker callback that repaints a single tab.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class TabRepaintCallback extends UIThreadTimelineCallbackAdapter {
+ /**
+ * The associated tabbed pane.
+ */
+ protected JTabbedPane tabbedPane;
+
+ /**
+ * The associated tab index.
+ */
+ protected int tabIndex;
+
+ /**
+ * Creates new tab repaint callback.
+ *
+ * @param tabPane
+ * The associated tabbed pane.
+ * @param tabIndex
+ * The associated tab index.
+ */
+ public TabRepaintCallback(JTabbedPane tabPane, int tabIndex) {
+ this.tabbedPane = tabPane;
+ this.tabIndex = tabIndex;
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ this.repaintTab();
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ this.repaintTab();
+ }
+
+ /**
+ * Repaints the relevant tab.
+ */
+ protected void repaintTab() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (SubstanceTabbedPaneUI.this.tabPane == null) {
+ // may happen if the LAF was switched in the meantime
+ return;
+ }
+ SubstanceTabbedPaneUI.this.ensureCurrentLayout();
+ int tabCount = SubstanceTabbedPaneUI.this.tabPane
+ .getTabCount();
+ if ((tabCount > 0)
+ && (TabRepaintCallback.this.tabIndex < tabCount)
+ && (TabRepaintCallback.this.tabIndex < SubstanceTabbedPaneUI.this.rects.length)) {
+ // need to retrieve the tab rectangle since the tabs
+ // can be moved while animating (especially when the
+ // current layout is SCROLL_LAYOUT)
+ Rectangle rect = SubstanceTabbedPaneUI.this
+ .getTabBounds(
+ SubstanceTabbedPaneUI.this.tabPane,
+ TabRepaintCallback.this.tabIndex);
+ // System.out.println("Repainting " + tabIndex);
+ SubstanceTabbedPaneUI.this.tabPane.repaint(rect);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Ensures the current layout.
+ */
+ protected void ensureCurrentLayout() {
+ if (!this.tabPane.isValid()) {
+ this.tabPane.validate();
+ }
+ /*
+ * If tabPane doesn't have a peer yet, the validate() call will silently
+ * fail. We handle that by forcing a layout if tabPane is still invalid.
+ * See bug 4237677.
+ */
+ if (!this.tabPane.isValid()) {
+ LayoutManager lm = this.tabPane.getLayout();
+ if (lm instanceof BasicTabbedPaneUI.TabbedPaneLayout) {
+ BasicTabbedPaneUI.TabbedPaneLayout layout = (BasicTabbedPaneUI.TabbedPaneLayout) lm;
+ layout.calculateLayoutInfo();
+ }
+ }
+ }
+
+ /**
+ * Tries closing tabs based on the specified tab index and tab close kind.
+ *
+ * @param tabIndex
+ * Tab index.
+ * @param tabCloseKind
+ * Tab close kind.
+ */
+ protected void tryCloseTabs(int tabIndex, TabCloseKind tabCloseKind) {
+ if (tabCloseKind == null)
+ return;
+ if (tabCloseKind == TabCloseKind.NONE)
+ return;
+
+ if (tabCloseKind == TabCloseKind.ALL_BUT_THIS) {
+ // close all but this
+ Set<Integer> indexes = new HashSet<Integer>();
+ for (int i = 0; i < this.tabPane.getTabCount(); i++)
+ if (i != tabIndex)
+ indexes.add(i);
+ this.tryCloseTabs(indexes);
+ return;
+ }
+ if (tabCloseKind == TabCloseKind.ALL) {
+ // close all
+ Set<Integer> indexes = new HashSet<Integer>();
+ for (int i = 0; i < this.tabPane.getTabCount(); i++)
+ indexes.add(i);
+ this.tryCloseTabs(indexes);
+ return;
+ }
+ this.tryCloseTab(tabIndex);
+ }
+
+ /**
+ * Tries closing a single tab.
+ *
+ * @param tabIndex
+ * Tab index.
+ */
+ protected void tryCloseTab(int tabIndex) {
+ Component component = this.tabPane.getComponentAt(tabIndex);
+ Set<Component> componentSet = new HashSet<Component>();
+ componentSet.add(component);
+
+ // check if there's at least one listener
+ // that vetoes the closing
+ boolean isVetoed = false;
+ for (BaseTabCloseListener listener : SubstanceLookAndFeel
+ .getAllTabCloseListeners(this.tabPane)) {
+ if (listener instanceof VetoableTabCloseListener) {
+ VetoableTabCloseListener vetoableListener = (VetoableTabCloseListener) listener;
+ isVetoed = isVetoed
+ || vetoableListener.vetoTabClosing(this.tabPane,
+ component);
+ }
+ if (listener instanceof VetoableMultipleTabCloseListener) {
+ VetoableMultipleTabCloseListener vetoableListener = (VetoableMultipleTabCloseListener) listener;
+ isVetoed = isVetoed
+ || vetoableListener.vetoTabsClosing(this.tabPane,
+ componentSet);
+ }
+ }
+ if (isVetoed)
+ return;
+
+ for (BaseTabCloseListener listener : SubstanceLookAndFeel
+ .getAllTabCloseListeners(this.tabPane)) {
+ if (listener instanceof TabCloseListener)
+ ((TabCloseListener) listener).tabClosing(this.tabPane,
+ component);
+ if (listener instanceof MultipleTabCloseListener)
+ ((MultipleTabCloseListener) listener).tabsClosing(this.tabPane,
+ componentSet);
+ }
+
+ this.tabPane.remove(tabIndex);
+ if (this.tabPane.getTabCount() > 0) {
+ this.selectPreviousTab(0);
+ this.selectNextTab(this.tabPane.getSelectedIndex());
+ }
+ this.tabPane.repaint();
+
+ for (BaseTabCloseListener listener : SubstanceLookAndFeel
+ .getAllTabCloseListeners(this.tabPane)) {
+ if (listener instanceof TabCloseListener)
+ ((TabCloseListener) listener)
+ .tabClosed(this.tabPane, component);
+ if (listener instanceof MultipleTabCloseListener)
+ ((MultipleTabCloseListener) listener).tabsClosed(this.tabPane,
+ componentSet);
+ }
+ }
+
+ /**
+ * Tries closing the specified tabs.
+ *
+ * @param tabIndexes
+ * Tab indexes.
+ */
+ protected void tryCloseTabs(Set<Integer> tabIndexes) {
+ Set<Component> componentSet = new HashSet<Component>();
+ for (int tabIndex : tabIndexes) {
+ componentSet.add(this.tabPane.getComponentAt(tabIndex));
+ }
+
+ // check if there's at least one listener
+ // that vetoes the closing
+ boolean isVetoed = false;
+ for (BaseTabCloseListener listener : SubstanceLookAndFeel
+ .getAllTabCloseListeners(this.tabPane)) {
+ if (listener instanceof VetoableMultipleTabCloseListener) {
+ VetoableMultipleTabCloseListener vetoableListener = (VetoableMultipleTabCloseListener) listener;
+ isVetoed = isVetoed
+ || vetoableListener.vetoTabsClosing(this.tabPane,
+ componentSet);
+ }
+ }
+ if (isVetoed)
+ return;
+
+ for (BaseTabCloseListener listener : SubstanceLookAndFeel
+ .getAllTabCloseListeners(this.tabPane)) {
+ if (listener instanceof MultipleTabCloseListener)
+ ((MultipleTabCloseListener) listener).tabsClosing(this.tabPane,
+ componentSet);
+ }
+
+ for (Component toRemove : componentSet) {
+ this.tabPane.remove(toRemove);
+ }
+
+ if (this.tabPane.getTabCount() > 0) {
+ this.selectPreviousTab(0);
+ this.selectNextTab(this.tabPane.getSelectedIndex());
+ }
+ this.tabPane.repaint();
+
+ for (BaseTabCloseListener listener : SubstanceLookAndFeel
+ .getAllTabCloseListeners(this.tabPane)) {
+ if (listener instanceof MultipleTabCloseListener)
+ ((MultipleTabCloseListener) listener).tabsClosed(this.tabPane,
+ componentSet);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabLabelShiftX(int, int,
+ * boolean)
+ */
+ @Override
+ protected int getTabLabelShiftX(int tabPlacement, int tabIndex,
+ boolean isSelected) {
+ int delta = 0;
+ if (SubstanceCoreUtilities.hasCloseButton(this.tabPane, tabIndex)) {
+ if (this.tabPane.getComponentOrientation().isLeftToRight()) {
+ delta = 5 - SubstanceCoreUtilities.getCloseButtonSize(
+ this.tabPane, tabIndex);
+ } else {
+ delta = SubstanceCoreUtilities.getCloseButtonSize(this.tabPane,
+ tabIndex) - 5;
+ }
+ }
+ return delta
+ + super.getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabLabelShiftY(int, int,
+ * boolean)
+ */
+ @Override
+ protected int getTabLabelShiftY(int tabPlacement, int tabIndex,
+ boolean isSelected) {
+ int result = 0;
+ if (tabPlacement == SwingConstants.BOTTOM)
+ result = -1;
+ else
+ result = 1;
+ return result;
+ }
+
+ /**
+ * Returns extra width for the specified tab.
+ *
+ * @param tabPlacement
+ * Tab placement.
+ * @param tabIndex
+ * Tab index.
+ * @return Extra width for the specified tab.
+ */
+ protected int getTabExtraWidth(int tabPlacement, int tabIndex) {
+ int extraWidth = 0;
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(this.tabPane);
+ if (shaper instanceof ClassicButtonShaper)
+ extraWidth = (int) (2.0 * SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(this.tabPane)));
+ else
+ extraWidth = super.calculateTabHeight(tabPlacement, tabIndex, this
+ .getFontMetrics().getHeight()) / 3;
+
+ if (SubstanceCoreUtilities.hasCloseButton(this.tabPane, tabIndex)
+ && this.tabPane.isEnabledAt(tabIndex)) {
+ extraWidth += (4 + SubstanceCoreUtilities.getCloseButtonSize(
+ this.tabPane, tabIndex));
+ }
+
+ // System.out.println(tabPane.getTitleAt(tabIndex) + ":" + extraWidth);
+ return extraWidth;
+ }
+
+ /**
+ * Returns the index of the tab currently being rolled-over.
+ *
+ * @return Index of the tab currently being rolled-over.
+ */
+ public int getRolloverTabIndex() {
+ return this.getRolloverTab();
+ }
+
+ /**
+ * Sets new value for tab area insets.
+ *
+ * @param insets
+ * Tab area insets.
+ */
+ public void setTabAreaInsets(Insets insets) {
+ Insets old = this.tabAreaInsets;
+ this.tabAreaInsets = insets;
+ // Fire a property change event so that the tabbed
+ // pane can revalidate itself
+ LafWidgetUtilities.firePropertyChangeEvent(this.tabPane,
+ "tabAreaInsets", old, tabAreaInsets);
+ }
+
+ /**
+ * Returns tab area insets.
+ *
+ * @return Tab area insets.
+ */
+ public Insets getTabAreaInsets() {
+ return this.tabAreaInsets;
+ }
+
+ /**
+ * Returns the tab rectangle for the specified tab.
+ *
+ * @param tabIndex
+ * Index of a tab.
+ * @return The tab rectangle for the specified parameters.
+ */
+ public Rectangle getTabRectangle(int tabIndex) {
+ return this.rects[tabIndex];
+ }
+
+ /**
+ * Returns the memory usage string.
+ *
+ * @return The memory usage string.
+ */
+ public static String getMemoryUsage() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("SubstanceTabbedPaneUI: \n");
+ sb.append("\t" + SubstanceTabbedPaneUI.backgroundMap.size()
+ + " backgrounds");
+ return sb.toString();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#shouldPadTabRun(int, int)
+ */
+ @Override
+ protected boolean shouldPadTabRun(int tabPlacement, int run) {
+ // Don't pad last run
+ return this.runCount > 1 && run < this.runCount - 1;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#createLayoutManager()
+ */
+ @Override
+ protected LayoutManager createLayoutManager() {
+ if (this.tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
+ return super.createLayoutManager();
+ }
+ return new TabbedPaneLayout();
+ }
+
+ /**
+ * Layout for the tabbed pane.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public class TabbedPaneLayout extends BasicTabbedPaneUI.TabbedPaneLayout {
+ /**
+ * Creates a new layout.
+ */
+ public TabbedPaneLayout() {
+ SubstanceTabbedPaneUI.this.super();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout#
+ * normalizeTabRuns(int, int, int, int)
+ */
+ @Override
+ protected void normalizeTabRuns(int tabPlacement, int tabCount,
+ int start, int max) {
+ // Only normalize the runs for top & bottom; normalizing
+ // doesn't look right for Metal's vertical tabs
+ // because the last run isn't padded and it looks odd to have
+ // fat tabs in the first vertical runs, but slimmer ones in the
+ // last (this effect isn't noticeable for horizontal tabs).
+ if (tabPlacement == TOP || tabPlacement == BOTTOM) {
+ super.normalizeTabRuns(tabPlacement, tabCount, start, max);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout#rotateTabRuns
+ * (int, int)
+ */
+ @Override
+ protected void rotateTabRuns(int tabPlacement, int selectedRun) {
+ // Don't rotate runs!
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout#padSelectedTab
+ * (int, int)
+ */
+ @Override
+ protected void padSelectedTab(int tabPlacement, int selectedIndex) {
+ // Don't pad selected tab
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTabbedPaneUI#getContentBorderInsets(int)
+ */
+ @Override
+ protected Insets getContentBorderInsets(int tabPlacement) {
+ Insets insets = SubstanceSizeUtils
+ .getTabbedPaneContentInsets(SubstanceSizeUtils
+ .getComponentFontSize(this.tabPane));
+
+ TabContentPaneBorderKind kind = SubstanceCoreUtilities
+ .getContentBorderKind(this.tabPane);
+ boolean isDouble = (kind == TabContentPaneBorderKind.DOUBLE_FULL)
+ || (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
+ boolean isPlacement = (kind == TabContentPaneBorderKind.SINGLE_PLACEMENT)
+ || (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
+ int delta = isDouble ? (int) (3.0 * SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane))) : 0;
+
+ if (isPlacement) {
+ switch (tabPlacement) {
+ case TOP:
+ return new Insets(insets.top + delta, 0, 0, 0);
+ case LEFT:
+ return new Insets(0, insets.left + delta, 0, 0);
+ case RIGHT:
+ return new Insets(0, 0, 0, insets.right + delta);
+ case BOTTOM:
+ return new Insets(0, 0, insets.bottom + delta, 0);
+ }
+ } else {
+ switch (tabPlacement) {
+ case TOP:
+ return new Insets(insets.top + delta, insets.left,
+ insets.bottom, insets.right);
+ case LEFT:
+ return new Insets(insets.top, insets.left + delta,
+ insets.bottom, insets.right);
+ case RIGHT:
+ return new Insets(insets.top, insets.left, insets.bottom,
+ insets.right + delta);
+ case BOTTOM:
+ return new Insets(insets.top, insets.left, insets.bottom
+ + delta, insets.right);
+ }
+ }
+ return insets;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTabbedPaneUI#paintContentBorder(java.awt.
+ * Graphics, int, int)
+ */
+ @Override
+ protected void paintContentBorder(Graphics g, int tabPlacement,
+ int selectedIndex) {
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tabPane, selectedIndex,
+ ColorSchemeAssociationKind.TAB, ComponentState.ENABLED);
+ this.highlight = scheme.isDark() ? SubstanceColorUtilities
+ .getAlphaColor(scheme.getUltraDarkColor(), 100) : scheme
+ .getLightColor();
+ super.paintContentBorder(g, tabPlacement, selectedIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTabbedPaneUI#paintContentBorderBottomEdge
+ * (java.awt.Graphics, int, int, int, int, int, int)
+ */
+ @Override
+ protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
+ int selectedIndex, int x, int y, int w, int h) {
+ TabContentPaneBorderKind kind = SubstanceCoreUtilities
+ .getContentBorderKind(this.tabPane);
+ boolean isDouble = (kind == TabContentPaneBorderKind.DOUBLE_FULL)
+ || (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
+ boolean isPlacement = (kind == TabContentPaneBorderKind.SINGLE_PLACEMENT)
+ || (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
+ if (isPlacement) {
+ if (tabPlacement != SwingConstants.BOTTOM)
+ return;
+ }
+ int ribbonDelta = (int) (2.0 * SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane)));
+
+ Rectangle selRect = selectedIndex < 0 ? null : this.getTabBounds(
+ selectedIndex, this.calcRect);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_NORMALIZE);
+ float strokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane));
+ int joinKind = BasicStroke.JOIN_ROUND;
+ int capKind = BasicStroke.CAP_BUTT;
+ g2d.setStroke(new BasicStroke(strokeWidth, capKind, joinKind));
+ int offset = (int) (strokeWidth / 2.0);
+
+ boolean isUnbroken = (tabPlacement != BOTTOM || selectedIndex < 0
+ || (selRect.y - 1 > h) || (selRect.x < x || selRect.x > x + w));
+
+ x += offset;
+ y += offset;
+ w -= 2 * offset;
+ h -= 2 * offset;
+
+ // Draw unbroken line if tabs are not on BOTTOM, OR
+ // selected tab is not in run adjacent to content, OR
+ // selected tab is not visible (SCROLL_TAB_LAYOUT)
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tabPane, selectedIndex,
+ ColorSchemeAssociationKind.TAB_BORDER,
+ ComponentState.SELECTED);
+ Color darkShadowColor = SubstanceColorUtilities
+ .getMidBorderColor(borderScheme);
+ if (isUnbroken) {
+ g2d.setColor(this.highlight);
+ g2d.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
+ } else {
+ // Break line to show visual connection to selected tab
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(this.tabPane);
+ int delta = (shaper instanceof ClassicButtonShaper) ? 1 : 0;
+ int borderInsets = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane)) / 2.0);
+ GeneralPath bottomOutline = new GeneralPath();
+ bottomOutline.moveTo(x, y + h - 1);
+ bottomOutline.lineTo(selRect.x + borderInsets, y + h - 1);
+ int bumpHeight = super.calculateTabHeight(tabPlacement, 0,
+ SubstanceSizeUtils.getComponentFontSize(this.tabPane)) / 2;
+ bottomOutline.lineTo(selRect.x + borderInsets, y + h + bumpHeight);
+ if (selRect.x + selRect.width < x + w - 1) {
+ int selectionEndX = selRect.x + selRect.width - delta - 1
+ - borderInsets;
+ bottomOutline.lineTo(selectionEndX, y + h - 1 + bumpHeight);
+ bottomOutline.lineTo(selectionEndX, y + h - 1);
+ bottomOutline.lineTo(x + w - 1, y + h - 1);
+ }
+ g2d.setPaint(new GradientPaint(x, y + h - 1, darkShadowColor, x, y
+ + h - 1 + bumpHeight, SubstanceColorUtilities
+ .getAlphaColor(darkShadowColor, 0)));
+ g2d.draw(bottomOutline);
+ }
+
+ if (isDouble) {
+ if (tabPlacement == BOTTOM) {
+ g2d.setColor(this.highlight);
+ // g2d.drawLine(x+1, y + h - 2 - ribbonDelta, x + w - 2,
+ // y + h - 2 - ribbonDelta);
+ g2d.setColor(darkShadowColor);
+ g2d.drawLine(x, y + h - 1 - ribbonDelta, x + w - 1, y + h - 1
+ - ribbonDelta);
+ }
+ if (tabPlacement == LEFT) {
+ g2d.setPaint(new GradientPaint(x, y + h - 1, darkShadowColor, x
+ + 4 * ribbonDelta, y + h - 1, this.highlight));
+ g2d.drawLine(x, y + h - 1, x + 4 * ribbonDelta, y + h - 1);
+ }
+ if (tabPlacement == RIGHT) {
+ g2d.setPaint(new GradientPaint(x + w - 1 - 4 * ribbonDelta, y
+ + h - 1, this.highlight, x + w - 1, y + h - 1,
+ darkShadowColor));
+ g2d.drawLine(x + w - 1 - 4 * ribbonDelta, y + h - 1, x + w - 1,
+ y + h - 1);
+ }
+ }
+
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTabbedPaneUI#paintContentBorderLeftEdge(java
+ * .awt.Graphics, int, int, int, int, int, int)
+ */
+ @Override
+ protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
+ int selectedIndex, int x, int y, int w, int h) {
+ TabContentPaneBorderKind kind = SubstanceCoreUtilities
+ .getContentBorderKind(this.tabPane);
+ boolean isDouble = (kind == TabContentPaneBorderKind.DOUBLE_FULL)
+ || (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
+ boolean isPlacement = (kind == TabContentPaneBorderKind.SINGLE_PLACEMENT)
+ || (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
+ if (isPlacement) {
+ if (tabPlacement != SwingConstants.LEFT)
+ return;
+ }
+ int ribbonDelta = (int) (3.0 * SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane)));
+
+ Rectangle selRect = selectedIndex < 0 ? null : this.getTabBounds(
+ selectedIndex, this.calcRect);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_NORMALIZE);
+ float strokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane));
+ int joinKind = BasicStroke.JOIN_ROUND;
+ int capKind = BasicStroke.CAP_BUTT;
+ g2d.setStroke(new BasicStroke(strokeWidth, capKind, joinKind));
+ int offset = (int) (strokeWidth / 2.0);
+
+ boolean isUnbroken = (tabPlacement != LEFT || selectedIndex < 0
+ || (selRect.x + selRect.width + 1 < x) || (selRect.y < y || selRect.y > y
+ + h));
+
+ x += offset;
+ y += offset;
+ // w -= 2 * offset;
+ h -= 2 * offset;
+
+ // Draw unbroken line if tabs are not on LEFT, OR
+ // selected tab is not in run adjacent to content, OR
+ // selected tab is not visible (SCROLL_TAB_LAYOUT)
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tabPane, selectedIndex,
+ ColorSchemeAssociationKind.TAB_BORDER,
+ ComponentState.SELECTED);
+ Color darkShadowColor = SubstanceColorUtilities
+ .getMidBorderColor(borderScheme);
+ if (isUnbroken) {
+ g2d.setColor(this.highlight);
+ g2d.drawLine(x, y, x, y + h);
+ } else {
+ // Break line to show visual connection to selected tab
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(this.tabPane);
+ int delta = (shaper instanceof ClassicButtonShaper) ? 1 : 0;
+
+ int borderInsets = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane)) / 2.0);
+ GeneralPath leftOutline = new GeneralPath();
+ leftOutline.moveTo(x, y);
+ leftOutline.lineTo(x, selRect.y + borderInsets);
+ int bumpWidth = super.calculateTabHeight(tabPlacement, 0,
+ SubstanceSizeUtils.getComponentFontSize(this.tabPane)) / 2;
+ leftOutline.lineTo(x - bumpWidth, selRect.y + borderInsets);
+ if (selRect.y + selRect.height < y + h) {
+ int selectionEndY = selRect.y + selRect.height - delta - 1
+ - borderInsets;
+ leftOutline.lineTo(x - bumpWidth, selectionEndY);
+ leftOutline.lineTo(x, selectionEndY);
+ leftOutline.lineTo(x, y + h);
+ }
+ g2d.setPaint(new GradientPaint(x, y, darkShadowColor,
+ x - bumpWidth, y, SubstanceColorUtilities.getAlphaColor(
+ darkShadowColor, 0)));
+ g2d.draw(leftOutline);
+
+ }
+
+ if (isDouble) {
+ if (tabPlacement == LEFT) {
+ g2d.setColor(darkShadowColor);
+ g2d.drawLine(x + ribbonDelta, y, x + ribbonDelta, y + h);
+ // g2d.setColor(this.highlight);
+ // g2d.drawLine(x + 1 + ribbonDelta, y + 1, x + 1 + ribbonDelta,
+ // y +
+ // h - 1);
+ }
+ if (tabPlacement == TOP) {
+ g2d.setPaint(new GradientPaint(x, y, darkShadowColor, x, y + 4
+ * ribbonDelta, this.highlight));
+ g2d.drawLine(x, y, x, y + 4 * ribbonDelta);
+ }
+ if (tabPlacement == BOTTOM) {
+ g2d.setPaint(new GradientPaint(x, y + h - 1 - 4 * ribbonDelta,
+ this.highlight, x, y + h - 1, darkShadowColor));
+ g2d.drawLine(x, y + h - 1 - 4 * ribbonDelta, x, y + h - 1);
+ }
+ }
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTabbedPaneUI#paintContentBorderRightEdge(
+ * java.awt.Graphics, int, int, int, int, int, int)
+ */
+ @Override
+ protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
+ int selectedIndex, int x, int y, int w, int h) {
+ TabContentPaneBorderKind kind = SubstanceCoreUtilities
+ .getContentBorderKind(this.tabPane);
+ boolean isDouble = (kind == TabContentPaneBorderKind.DOUBLE_FULL)
+ || (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
+ boolean isPlacement = (kind == TabContentPaneBorderKind.SINGLE_PLACEMENT)
+ || (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
+ if (isPlacement) {
+ if (tabPlacement != SwingConstants.RIGHT)
+ return;
+ }
+ int ribbonDelta = (int) (3.0 * SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane)));
+
+ Rectangle selRect = selectedIndex < 0 ? null : this.getTabBounds(
+ selectedIndex, this.calcRect);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_NORMALIZE);
+ float strokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane));
+ int joinKind = BasicStroke.JOIN_ROUND;
+ int capKind = BasicStroke.CAP_BUTT;
+ g2d.setStroke(new BasicStroke(strokeWidth, capKind, joinKind));
+ int offset = (int) (strokeWidth / 2.0);
+
+ boolean isUnbroken = (tabPlacement != RIGHT || selectedIndex < 0
+ || (selRect.x - 1 > w) || (selRect.y < y || selRect.y > y + h));
+
+ x += offset;
+ y += offset;
+ w -= 2 * offset;
+ h -= 2 * offset;
+
+ // Draw unbroken line if tabs are not on RIGHT, OR
+ // selected tab is not in run adjacent to content, OR
+ // selected tab is not visible (SCROLL_TAB_LAYOUT)
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tabPane, selectedIndex,
+ ColorSchemeAssociationKind.TAB_BORDER,
+ ComponentState.SELECTED);
+ Color darkShadowColor = SubstanceColorUtilities
+ .getMidBorderColor(borderScheme);
+ if (isUnbroken) {
+ g2d.setColor(this.highlight);
+ g2d.drawLine(x + w - 1, y, x + w - 1, y + h);
+ } else {
+ // Break line to show visual connection to selected tab
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(this.tabPane);
+ int delta = (shaper instanceof ClassicButtonShaper) ? 1 : 0;
+
+ int borderInsets = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane)) / 2.0);
+ GeneralPath rightOutline = new GeneralPath();
+ rightOutline.moveTo(x + w - 1, y);
+ rightOutline.lineTo(x + w - 1, selRect.y + borderInsets);
+ int bumpWidth = super.calculateTabHeight(tabPlacement, 0,
+ SubstanceSizeUtils.getComponentFontSize(this.tabPane)) / 2;
+ rightOutline
+ .lineTo(x + w - 1 + bumpWidth, selRect.y + borderInsets);
+ if (selRect.y + selRect.height < y + h) {
+ int selectionEndY = selRect.y + selRect.height - delta - 1
+ - borderInsets;
+ rightOutline.lineTo(x + w - 1 + bumpWidth, selectionEndY);
+ rightOutline.lineTo(x + w - 1, selectionEndY);
+ rightOutline.lineTo(x + w - 1, y + h);
+ }
+ g2d.setPaint(new GradientPaint(x + w - 1, y, darkShadowColor, x + w
+ - 1 + bumpWidth, y, SubstanceColorUtilities.getAlphaColor(
+ darkShadowColor, 0)));
+ g2d.draw(rightOutline);
+ }
+
+ if (isDouble) {
+ if (tabPlacement == RIGHT) {
+ g2d.setColor(this.highlight);
+ // g2d.drawLine(x + w - 2 - ribbonDelta, y + 1, x + w - 2 -
+ // ribbonDelta, y + h - 1);
+ g2d.setColor(darkShadowColor);
+ g2d.drawLine(x + w - 1 - ribbonDelta, y, x + w - 1
+ - ribbonDelta, y + h);
+ }
+ if (tabPlacement == TOP) {
+ g2d.setPaint(new GradientPaint(x + w - 1, y, darkShadowColor, x
+ + w - 1, y + 4 * ribbonDelta, this.highlight));
+ g2d.drawLine(x + w - 1, y, x + w - 1, y + 4 * ribbonDelta);
+ }
+ if (tabPlacement == BOTTOM) {
+ g2d.setPaint(new GradientPaint(x + w - 1, y + h - 1 - 4
+ * ribbonDelta, this.highlight, x + w - 1, y + h - 1,
+ darkShadowColor));
+ g2d.drawLine(x + w - 1, y + h - 1 - 4 * ribbonDelta, x + w - 1,
+ y + h - 1);
+ }
+ }
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTabbedPaneUI#paintContentBorderTopEdge(java
+ * .awt.Graphics, int, int, int, int, int, int)
+ */
+ @Override
+ protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
+ int selectedIndex, int x, int y, int w, int h) {
+ TabContentPaneBorderKind kind = SubstanceCoreUtilities
+ .getContentBorderKind(this.tabPane);
+ boolean isDouble = (kind == TabContentPaneBorderKind.DOUBLE_FULL)
+ || (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
+ boolean isPlacement = (kind == TabContentPaneBorderKind.SINGLE_PLACEMENT)
+ || (kind == TabContentPaneBorderKind.DOUBLE_PLACEMENT);
+ if (isPlacement) {
+ if (tabPlacement != SwingConstants.TOP)
+ return;
+ }
+ int ribbonDelta = (int) (3.0 * SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane)));
+
+ Rectangle selRect = selectedIndex < 0 ? null : this.getTabBounds(
+ selectedIndex, this.calcRect);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_NORMALIZE);
+ float strokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane));
+ int joinKind = BasicStroke.JOIN_ROUND;
+ int capKind = BasicStroke.CAP_BUTT;
+ g2d.setStroke(new BasicStroke(strokeWidth, capKind, joinKind));
+ int offset = (int) (strokeWidth / 2.0);
+
+ boolean isUnbroken = (tabPlacement != TOP || selectedIndex < 0
+ || (selRect.y + selRect.height + 1 < y) || (selRect.x < x || selRect.x > x
+ + w));
+
+ x += offset;
+ y += offset;
+ w -= 2 * offset;
+ // h -= 2 * offset;
+
+ // Draw unbroken line if tabs are not on TOP, OR
+ // selected tab is not in run adjacent to content, OR
+ // selected tab is not visible (SCROLL_TAB_LAYOUT)
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tabPane, selectedIndex,
+ ColorSchemeAssociationKind.TAB_BORDER,
+ ComponentState.SELECTED);
+ Color darkShadowColor = SubstanceColorUtilities
+ .getMidBorderColor(borderScheme);
+ if (isUnbroken) {
+ g2d.setColor(this.highlight);
+ g2d.drawLine(x, y, x + w - 1, y);
+ } else {
+ // Break line to show visual connection to selected tab
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(this.tabPane);
+ int delta = (shaper instanceof ClassicButtonShaper) ? 1 : 0;
+ int borderInsets = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(tabPane)) / 2.0);
+ GeneralPath topOutline = new GeneralPath();
+ topOutline.moveTo(x, y);
+ topOutline.lineTo(selRect.x + borderInsets, y);
+ int bumpHeight = super.calculateTabHeight(tabPlacement, 0,
+ SubstanceSizeUtils.getComponentFontSize(this.tabPane)) / 2;
+ topOutline.lineTo(selRect.x + borderInsets, y - bumpHeight);
+ if (selRect.x + selRect.width < x + w - 1) {
+ int selectionEndX = selRect.x + selRect.width - delta - 1
+ - borderInsets;
+ topOutline.lineTo(selectionEndX, y - bumpHeight);
+ topOutline.lineTo(selectionEndX, y);
+ topOutline.lineTo(x + w - 1, y);
+ }
+ g2d.setPaint(new GradientPaint(x, y, darkShadowColor, x, y
+ - bumpHeight, SubstanceColorUtilities.getAlphaColor(
+ darkShadowColor, 0)));
+ g2d.draw(topOutline);
+ }
+
+ if (isDouble) {
+ if (tabPlacement == TOP) {
+ g2d.setColor(darkShadowColor);
+ g2d.drawLine(x, y + ribbonDelta, x + w - 1, y + ribbonDelta);
+ g2d.setColor(this.highlight);
+ // g2d.drawLine(x, y + 1 + ribbonDelta, x + w - 1, y + 1 +
+ // ribbonDelta);
+ }
+ if (tabPlacement == LEFT) {
+ g2d.setPaint(new GradientPaint(x, y, darkShadowColor, x + 4
+ * ribbonDelta, y, this.highlight));
+ g2d.drawLine(x, y, x + 4 * ribbonDelta, y);
+ }
+ if (tabPlacement == RIGHT) {
+ g2d.setPaint(new GradientPaint(x + w - 1 - 4 * ribbonDelta, y,
+ this.highlight, x + w - 1, y, darkShadowColor));
+ g2d.drawLine(x + w - 1 - 4 * ribbonDelta, y, x + w - 1, y);
+ }
+ }
+
+ g2d.dispose();
+ }
+
+ @Override
+ public Rectangle getTabBounds(JTabbedPane pane, int i) {
+ this.ensureCurrentLayout();
+ Rectangle tabRect = new Rectangle();
+ return this.getTabBounds(i, tabRect);
+ }
+
+ // /**
+ // * Returns the previous state for the specified tab.
+ // *
+ // * @param tabIndex
+ // * Tab index.
+ // * @return The previous state for the specified tab.
+ // */
+ // protected ComponentState getPrevTabState(int tabIndex) {
+ // StateTransitionTracker tracker = this.stateTransitionMultiTracker
+ // .getTracker(tabIndex);
+ // if (tracker == null) {
+ // return getTabState(tabIndex);
+ // } else {
+ // ComponentState fromTracker = tracker.getPrevModelState();
+ // boolean isEnabled = this.tabPane.isEnabledAt(tabIndex);
+ // return ComponentState.getState(isEnabled, fromTracker
+ // .isFacetActive(AnimationFacet.ROLLOVER), fromTracker
+ // .isFacetActive(AnimationFacet.SELECTION));
+ // }
+ // }
+
+ protected StateTransitionTracker.ModelStateInfo getModelStateInfo(
+ int tabIndex) {
+ if (this.stateTransitionMultiTracker.size() == 0)
+ return null;
+ StateTransitionTracker tracker = this.stateTransitionMultiTracker
+ .getTracker(tabIndex);
+ if (tracker == null) {
+ return null;
+ } else {
+ return tracker.getModelStateInfo();
+ }
+ }
+
+ /**
+ * Returns the current state for the specified tab.
+ *
+ * @param tabIndex
+ * Tab index.
+ * @return The current state for the specified tab.
+ */
+ protected ComponentState getTabState(int tabIndex) {
+ boolean isEnabled = this.tabPane.isEnabledAt(tabIndex);
+ StateTransitionTracker tracker = this.stateTransitionMultiTracker
+ .getTracker(tabIndex);
+ if (tracker == null) {
+ boolean isRollover = this.getRolloverTabIndex() == tabIndex;
+ boolean isSelected = this.tabPane.getSelectedIndex() == tabIndex;
+ return ComponentState.getState(isEnabled, isRollover, isSelected);
+ } else {
+ ComponentState fromTracker = tracker.getModelStateInfo()
+ .getCurrModelState();
+ return ComponentState.getState(isEnabled, fromTracker
+ .isFacetActive(ComponentStateFacet.ROLLOVER), fromTracker
+ .isFacetActive(ComponentStateFacet.SELECTION));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTabbedPaneUI#paintText(java.awt.Graphics,
+ * int, java.awt.Font, java.awt.FontMetrics, int, java.lang.String,
+ * java.awt.Rectangle, boolean)
+ */
+ @Override
+ protected void paintText(Graphics g, int tabPlacement, Font font,
+ FontMetrics metrics, int tabIndex, String title,
+ Rectangle textRect, boolean isSelected) {
+ g.setFont(font);
+
+ View v = this.getTextViewForTab(tabIndex);
+ if (v != null) {
+ // html
+ v.paint(g, textRect);
+ } else {
+ // plain text
+ int mnemIndex = this.tabPane.getDisplayedMnemonicIndexAt(tabIndex);
+ StateTransitionTracker.ModelStateInfo modelStateInfo = this
+ .getModelStateInfo(tabIndex);
+ ComponentState currState = this.getTabState(tabIndex);
+
+ // System.out.println("Tab " + title + ":" + currState);
+ Color fg = null;
+ if (modelStateInfo != null) {
+ Map<ComponentState, StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(tabPane, tabIndex,
+ ColorSchemeAssociationKind.TAB, currState);
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ fg = colorScheme.getForegroundColor();
+ } else {
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(tabPane, tabIndex,
+ ColorSchemeAssociationKind.TAB,
+ activeState);
+ Color schemeFg = scheme.getForegroundColor();
+ float contribution = activeEntry.getValue()
+ .getContribution();
+ // // System.out.println("\t" + activeState + ":"
+ // + contribution + ":" + scheme.getDisplayName()
+ // + ":" + schemeFg);
+ aggrRed += schemeFg.getRed() * contribution;
+ aggrGreen += schemeFg.getGreen() * contribution;
+ aggrBlue += schemeFg.getBlue() * contribution;
+ }
+ fg = new Color((int) aggrRed, (int) aggrGreen,
+ (int) aggrBlue);
+ }
+ } else {
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(tabPane, tabIndex,
+ ColorSchemeAssociationKind.TAB, currState);
+ fg = scheme.getForegroundColor();
+ }
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ if (currState.isDisabled()) {
+ Color bgFillColor = SubstanceColorUtilities
+ .getBackgroundFillColor(this.tabPane);
+ fg = SubstanceColorUtilities.getInterpolatedColor(fg,
+ bgFillColor, SubstanceColorSchemeUtilities.getAlpha(
+ this.tabPane.getComponentAt(tabIndex),
+ currState));
+ }
+ graphics.clip(getTabRectangle(tabIndex));
+ SubstanceTextUtilities.paintText(graphics, this.tabPane, textRect,
+ title, mnemIndex, graphics.getFont(), fg, null);
+ graphics.dispose();
+ }
+ }
+
+ @Override
+ protected void paintIcon(Graphics g, int tabPlacement, int tabIndex,
+ Icon icon, Rectangle iconRect, boolean isSelected) {
+ if (icon == null)
+ return;
+
+ if (SubstanceCoreUtilities.useThemedDefaultIcon(this.tabPane)) {
+ ComponentState currState = this.getTabState(tabIndex);
+ StateTransitionTracker tabTracker = stateTransitionMultiTracker
+ .getTracker(tabIndex);
+
+ if (tabTracker == null) {
+ if (currState.isFacetActive(ComponentStateFacet.ROLLOVER)
+ || currState
+ .isFacetActive(ComponentStateFacet.SELECTION)
+ || currState.isDisabled()) {
+ // use the original (full color or disabled) icon
+ super.paintIcon(g, tabPlacement, tabIndex, icon, iconRect,
+ isSelected);
+ return;
+ }
+ }
+
+ Icon themed = SubstanceCoreUtilities.getThemedIcon(this.tabPane,
+ tabIndex, icon);
+ if (tabTracker == null) {
+ super.paintIcon(g, tabPlacement, tabIndex, themed, iconRect,
+ isSelected);
+ } else {
+ Graphics2D g2d = (Graphics2D) g.create();
+ super.paintIcon(g2d, tabPlacement, tabIndex, icon, iconRect,
+ isSelected);
+ g2d
+ .setComposite(LafWidgetUtilities
+ .getAlphaComposite(
+ this.tabPane,
+ 1.0f - tabTracker
+ .getFacetStrength(ComponentStateFacet.ROLLOVER),
+ g2d));
+ super.paintIcon(g2d, tabPlacement, tabIndex, themed, iconRect,
+ isSelected);
+ g2d.dispose();
+ }
+ } else {
+ super.paintIcon(g, tabPlacement, tabIndex, icon, iconRect,
+ isSelected);
+ }
+ }
+
+ @Override
+ protected MouseListener createMouseListener() {
+ return null;
+ }
+
+ /**
+ * Extension point to allow horizontal orientation of left / right placed
+ * tabs.
+ *
+ * @param tabPlacement
+ * Tab placement.
+ * @return Indication whether the tabs in the specified placement should be
+ * rotated.
+ */
+ protected boolean toRotateTabsOnPlacement(int tabPlacement) {
+ Object rotateProperty = tabPane.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_ROTATE_SIDE_TABS);
+ if (!(rotateProperty instanceof Boolean)) {
+ rotateProperty = UIManager.get(SubstanceLookAndFeel.TABBED_PANE_ROTATE_SIDE_TABS);
+
+ }
+ boolean rotate = (rotateProperty instanceof Boolean) ? (Boolean)rotateProperty : true;
+
+ return rotate && ( (tabPlacement == SwingConstants.LEFT) || (tabPlacement == SwingConstants.RIGHT) );
+ }
+
+ private StateTransitionTracker getTracker(final int tabIndex,
+ boolean initialRollover, boolean initialSelected) {
+ StateTransitionTracker tracker = stateTransitionMultiTracker
+ .getTracker(tabIndex);
+ if (tracker == null) {
+ ButtonModel model = new DefaultButtonModel();
+ model.setSelected(initialSelected);
+ model.setRollover(initialRollover);
+ tracker = new StateTransitionTracker(tabPane, model);
+ tracker.registerModelListeners();
+ tracker.setRepaintCallback(new RepaintCallback() {
+ @Override
+ public TimelineCallback getRepaintCallback() {
+ return new TabRepaintCallback(tabPane, tabIndex);
+ }
+ });
+ stateTransitionMultiTracker.addTracker(tabIndex, tracker);
+ }
+ return tracker;
+ }
+
+ private void trackTabModification(int tabIndex, Component tabComponent) {
+ Timeline modifiedTimeline = new Timeline(tabPane);
+ AnimationConfigurationManager.getInstance().configureModifiedTimeline(
+ modifiedTimeline);
+ modifiedTimeline.addCallback(new TabRepaintCallback(tabPane, tabIndex));
+ modifiedTimeline.playLoop(RepeatBehavior.REVERSE);
+ modifiedTimelines.put(tabComponent, modifiedTimeline);
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTableHeaderUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTableHeaderUI.java
new file mode 100644
index 0000000..1356256
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTableHeaderUI.java
@@ -0,0 +1,1059 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicTableHeaderUI;
+import javax.swing.table.*;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.renderers.SubstanceDefaultTableHeaderCellRenderer;
+import org.pushingpixels.substance.internal.animation.StateTransitionMultiTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.RepaintCallback;
+import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.TimelineCallback;
+import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
+
+/**
+ * UI for table headers in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTableHeaderUI extends BasicTableHeaderUI {
+ /**
+ * Repaints the header on column selection.
+ */
+ protected TableHeaderListener substanceHeaderListener;
+
+ /**
+ * The default renderer.
+ */
+ protected TableCellRenderer defaultHeaderRenderer;
+
+ /**
+ * Holds the list of currently selected indices.
+ */
+ protected Map<Integer, Object> selectedIndices;
+
+ /**
+ * Listener for transition animations on list selections.
+ */
+ protected ListSelectionListener substanceFadeSelectionListener;
+
+ private StateTransitionMultiTracker<Integer> stateTransitionMultiTracker;
+
+ /**
+ * Property change listener.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Listener for table header.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class TableHeaderListener implements ListSelectionListener {
+ /**
+ * The associated table header UI.
+ */
+ private SubstanceTableHeaderUI ui;
+
+ /**
+ * Simple constructor.
+ *
+ * @param ui
+ * The associated table header UI
+ */
+ public TableHeaderListener(SubstanceTableHeaderUI ui) {
+ this.ui = ui;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.event.ListSelectionListener#valueChanged(javax.swing.
+ * event.ListSelectionEvent)
+ */
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (ui.header == null)
+ return;
+ if (ui.header.isValid())
+ ui.header.repaint();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceTableHeaderUI();
+ }
+
+ /**
+ * Creates a new UI delegate.
+ */
+ public SubstanceTableHeaderUI() {
+ this.stateTransitionMultiTracker = new StateTransitionMultiTracker<Integer>();
+ selectedIndices = new HashMap<Integer, Object>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTableHeaderUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ TableColumnModel columnModel = header.getColumnModel();
+ if (columnModel != null) {
+ ListSelectionModel lsm = columnModel.getSelectionModel();
+ if (lsm != null) {
+ // fix for defect 109 - memory leak on skin switch
+ substanceHeaderListener = new TableHeaderListener(this);
+ lsm.addListSelectionListener(substanceHeaderListener);
+ }
+ }
+
+ // Add listener for the selection animation
+ this.substanceFadeSelectionListener = new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (header == null)
+ return;
+
+ if (!header.getColumnModel().getColumnSelectionAllowed())
+ return;
+
+ // fix for issue 367 - check that there is an associated
+ // table and that it has a substance UI delegate
+ JTable table = header.getTable();
+ if (table == null)
+ return;
+
+ TableUI ui = table.getUI();
+ if (!(ui instanceof SubstanceTableUI)) {
+ stateTransitionMultiTracker.clear();
+ return;
+ }
+
+ // optimization on large tables and large selections
+ // and syncing the fade presence with the table
+ // (issue 309)
+ SubstanceTableUI tableUI = (SubstanceTableUI) ui;
+ // System.out.println("Sel anim: "
+ // + tableUI.hasSelectionAnimations());
+ if (!tableUI._hasSelectionAnimations()) {
+ stateTransitionMultiTracker.clear();
+ return;
+ }
+
+ // Set<Long> initiatedFadeSequences = new HashSet<Long>();
+ Set<StateTransitionTracker> initiatedTrackers = new HashSet<StateTransitionTracker>();
+ boolean fadeCanceled = false;
+
+ // if (SubstanceCoreUtilities.toBleedWatermark(list))
+ // return;
+
+ // FadeTracker fadeTrackerInstance = FadeTracker.getInstance();
+ TableColumnModel columnModel = header.getColumnModel();
+ int size = columnModel.getColumnCount();
+ ListSelectionModel lsm = columnModel.getSelectionModel();
+ for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
+ if (i >= size)
+ continue;
+ if (lsm.isSelectedIndex(i)) {
+ // check if was selected before
+ if (!selectedIndices.containsKey(i)) {
+ // start fading in
+ // System.out.println("Fade in on index " + i);
+
+ if (!fadeCanceled) {
+ StateTransitionTracker tracker = getTracker(i,
+ getColumnState(i).isFacetActive(
+ ComponentStateFacet.ROLLOVER),
+ false);
+ tracker.getModel().setSelected(true);
+ initiatedTrackers.add(tracker);
+ if (initiatedTrackers.size() > 15) {
+ stateTransitionMultiTracker.clear();
+ initiatedTrackers.clear();
+ fadeCanceled = true;
+ }
+ }
+
+ selectedIndices.put(i, columnModel.getColumn(i));
+ }
+ } else {
+ // check if was selected before and still points to the
+ // same element
+ if (selectedIndices.containsKey(i)) {
+ if (selectedIndices.get(i) == columnModel
+ .getColumn(i)) {
+ // start fading out
+ // System.out.println("Fade out on index " + i);
+
+ if (!fadeCanceled) {
+ StateTransitionTracker tracker = getTracker(
+ i,
+ getColumnState(i)
+ .isFacetActive(
+ ComponentStateFacet.ROLLOVER),
+ true);
+ tracker.getModel().setSelected(false);
+ initiatedTrackers.add(tracker);
+ if (initiatedTrackers.size() > 15) {
+ stateTransitionMultiTracker.clear();
+ initiatedTrackers.clear();
+ fadeCanceled = true;
+ }
+ }
+ }
+ selectedIndices.remove(i);
+ }
+ }
+ }
+ }
+ };
+
+ if (columnModel != null) {
+ ListSelectionModel lsm = columnModel.getSelectionModel();
+ if (lsm != null) {
+ lsm.addListSelectionListener(substanceFadeSelectionListener);
+ }
+ }
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("table".equals(evt.getPropertyName())) {
+ // track changes to the table and re-register the
+ // column model listener to the new table.
+ TableColumnModel oldModel = (evt.getOldValue() instanceof JTable) ? ((JTable) evt
+ .getOldValue()).getColumnModel()
+ : null;
+ TableColumnModel newModel = (evt.getNewValue() instanceof JTable) ? ((JTable) evt
+ .getNewValue()).getColumnModel()
+ : null;
+ processColumnModelChangeEvent(oldModel, newModel);
+ }
+ }
+ };
+ this.header
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTableHeaderUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+
+ defaultHeaderRenderer = header.getDefaultRenderer();
+ if (defaultHeaderRenderer instanceof UIResource) {
+ header
+ .setDefaultRenderer(new SubstanceDefaultTableHeaderCellRenderer());
+ }
+
+ for (int i = 0; i < header.getColumnModel().getColumnCount(); i++) {
+ if (header.getColumnModel().getSelectionModel().isSelectedIndex(i)) {
+ selectedIndices.put(i, header.getColumnModel().getColumn(i));
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTableHeaderUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ // fix for defect 109 - memory leak on skin switch
+ TableColumnModel columnModel = header.getColumnModel();
+ if (columnModel != null) {
+ ListSelectionModel lsm = columnModel.getSelectionModel();
+ if (lsm != null) {
+ lsm.removeListSelectionListener(substanceHeaderListener);
+ substanceHeaderListener = null;
+ }
+ }
+
+ this.header
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTableHeaderUI#uninstallDefaults()
+ */
+ @Override
+ protected void uninstallDefaults() {
+ super.uninstallDefaults();
+
+ selectedIndices.clear();
+
+ if (header.getDefaultRenderer() instanceof SubstanceDefaultTableHeaderCellRenderer) {
+ header.setDefaultRenderer(defaultHeaderRenderer);
+ if (defaultHeaderRenderer instanceof Component)
+ SwingUtilities
+ .updateComponentTreeUI((Component) defaultHeaderRenderer);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ if (header.getColumnModel().getColumnCount() <= 0) {
+ return;
+ }
+ boolean ltr = header.getComponentOrientation().isLeftToRight();
+
+ Rectangle clip = g.getClipBounds();
+ Point left = clip.getLocation();
+ Point right = new Point(clip.x + clip.width - 1, clip.y);
+
+ TableColumnModel cm = header.getColumnModel();
+ int[] selectedColumns = cm.getSelectedColumns();
+ Set<Integer> selected = new HashSet<Integer>();
+ for (int sel : selectedColumns)
+ selected.add(sel);
+
+ int cMin = header.columnAtPoint(ltr ? left : right);
+ int cMax = header.columnAtPoint(ltr ? right : left);
+ // This should never happen.
+ if (cMin == -1) {
+ cMin = 0;
+ }
+ // If the table does not have enough columns to fill the view we'll get
+ // -1.
+ // Replace this with the index of the last column.
+ if (cMax == -1) {
+ cMax = cm.getColumnCount() - 1;
+ }
+
+ TableColumn draggedColumn = header.getDraggedColumn();
+ int columnWidth;
+ Rectangle cellRect = header.getHeaderRect(ltr ? cMin : cMax);
+ TableColumn aColumn;
+ if (ltr) {
+ for (int column = cMin; column <= cMax; column++) {
+ aColumn = cm.getColumn(column);
+ columnWidth = aColumn.getWidth();
+ cellRect.width = columnWidth;
+ if (aColumn != draggedColumn) {
+ this.paintCell(g, cellRect, column, selected
+ .contains(column));
+ }
+ cellRect.x += columnWidth;
+ }
+ } else {
+ for (int column = cMax; column >= cMin; column--) {
+ aColumn = cm.getColumn(column);
+ columnWidth = aColumn.getWidth();
+ cellRect.width = columnWidth;
+ if (aColumn != draggedColumn) {
+ this.paintCell(g, cellRect, column, selected
+ .contains(column));
+ }
+ cellRect.x += columnWidth;
+ }
+ }
+
+ this.paintGrid(g, c);
+
+ // Paint the dragged column if we are dragging.
+ if (draggedColumn != null) {
+ int draggedColumnIndex = viewIndexForColumn(draggedColumn);
+ Rectangle draggedCellRect = header
+ .getHeaderRect(draggedColumnIndex);
+
+ // Draw a gray well in place of the moving column.
+ g.setColor(header.getParent().getBackground());
+ g.fillRect(draggedCellRect.x, draggedCellRect.y,
+ draggedCellRect.width, draggedCellRect.height);
+
+ draggedCellRect.x += header.getDraggedDistance();
+
+ // Fill the background.
+ g.setColor(header.getBackground());
+ g.fillRect(draggedCellRect.x, draggedCellRect.y,
+ draggedCellRect.width, draggedCellRect.height);
+
+ this.paintCell(g, draggedCellRect, draggedColumnIndex, selected
+ .contains(draggedColumnIndex));
+ }
+
+ // Remove all components in the rendererPane.
+ rendererPane.removeAll();
+ }
+
+ /**
+ * Retrieves renderer for the specified column header.
+ *
+ * @param columnIndex
+ * Column index.
+ * @return Renderer for the specified column header.
+ */
+ private Component getHeaderRenderer(int columnIndex) {
+ TableColumn aColumn = header.getColumnModel().getColumn(columnIndex);
+ TableCellRenderer renderer = aColumn.getHeaderRenderer();
+ if (renderer == null) {
+ renderer = header.getDefaultRenderer();
+ }
+ return renderer.getTableCellRendererComponent(header.getTable(),
+ aColumn.getHeaderValue(), false, false, -1, columnIndex);
+ }
+
+ /**
+ *
+ *
+ * @param g
+ * @param c
+ */
+ protected void paintGrid(Graphics g, JComponent c) {
+ boolean ltr = header.getComponentOrientation().isLeftToRight();
+
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ Rectangle clip = g.getClipBounds();
+ Point left = clip.getLocation();
+ // tweak the points for issue 378 - making sure that the
+ // grid lines are repainted correctly on scroll.
+ int lineWeight = (int) Math.ceil(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(c)));
+ left = new Point(left.x - 2 * lineWeight, left.y);
+ Point right = new Point(clip.x + clip.width + 2 * lineWeight, clip.y);
+
+ TableColumnModel cm = header.getColumnModel();
+
+ int cMin = header.columnAtPoint(ltr ? left : right);
+ int cMax = header.columnAtPoint(ltr ? right : left);
+ // This should never happen.
+ if (cMin == -1) {
+ cMin = 0;
+ }
+
+ Rectangle cellRect0 = header.getHeaderRect(cMin);
+ // int top = cellRect0.y;
+ int bottom = cellRect0.y + cellRect0.height;
+
+ Color gridColor = getGridColor(this.header);
+
+ float strokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.header));
+ g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_BEVEL));
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setColor(gridColor);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.header,
+ 0.7f, g));
+
+ g2d.drawLine((int) left.getX(), (int) (bottom - strokeWidth / 2),
+ (int) right.getX(), (int) (bottom - strokeWidth / 2));
+ // If the table does not have enough columns to fill the view we'll
+ // get -1. Replace this with the index of the last column.
+ if (cMax == -1) {
+ cMax = cm.getColumnCount() - 1;
+ }
+
+ TableColumn draggedColumn = this.header.getDraggedColumn();
+ int columnWidth;
+ Rectangle cellRect = this.header.getHeaderRect(ltr ? cMin : cMax);
+ TableColumn aColumn;
+ if (ltr) {
+ for (int column = cMin; column <= cMax; column++) {
+ aColumn = cm.getColumn(column);
+ columnWidth = aColumn.getWidth();
+ cellRect.width = columnWidth;
+
+ if (aColumn != draggedColumn) {
+ if (hasLeadingVerticalGridLine(header, cellRect, column)) {
+ g2d
+ .drawLine(cellRect.x, cellRect.y, cellRect.x,
+ bottom);
+ }
+ if (hasTrailingVerticalGridLine(header, cellRect, column)) {
+ g2d.drawLine(cellRect.x + cellRect.width - 1,
+ cellRect.y, cellRect.x + cellRect.width - 1,
+ bottom);
+ }
+ }
+
+ cellRect.x += columnWidth;
+ }
+ } else {
+ for (int column = cMax; column >= cMin; column--) {
+ aColumn = cm.getColumn(column);
+ columnWidth = aColumn.getWidth();
+ cellRect.width = columnWidth;
+
+ if (aColumn != draggedColumn) {
+ if (hasLeadingVerticalGridLine(header, cellRect, column)) {
+ g2d.drawLine(cellRect.x + cellRect.width - 1,
+ cellRect.y, cellRect.x + cellRect.width - 1,
+ bottom);
+ }
+ if (hasTrailingVerticalGridLine(header, cellRect, column)) {
+ g2d
+ .drawLine(cellRect.x, cellRect.y, cellRect.x,
+ bottom);
+ }
+ }
+ cellRect.x += columnWidth;
+ }
+ }
+
+ g2d.dispose();
+ }
+
+ private boolean hasTrailingVerticalGridLine(JTableHeader tableHeader,
+ Rectangle cellRect, int column) {
+ boolean toDrawLine = (column != (tableHeader.getColumnModel()
+ .getColumnCount() - 1));
+ if (!toDrawLine) {
+ Container parent = this.header.getParent();
+ if (tableHeader.getComponentOrientation().isLeftToRight()) {
+ toDrawLine = (parent != null)
+ && (parent.getWidth() > (cellRect.x + cellRect.width));
+ } else {
+ toDrawLine = (parent != null) && (cellRect.x > 0);
+ }
+ }
+ return toDrawLine;
+ }
+
+ private boolean hasLeadingVerticalGridLine(JTableHeader tableHeader,
+ Rectangle cellRect, int column) {
+ if (column != 0) {
+ return false;
+ }
+ Container parent = tableHeader.getParent();
+ if (parent instanceof JViewport) {
+ Container grand = parent.getParent();
+ if (grand instanceof JScrollPane) {
+ return (((JScrollPane) grand).getRowHeader() != null);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the grid color for the table header.
+ *
+ * @param header
+ * Table header.
+ * @return Grid color.
+ */
+ protected static Color getGridColor(JTableHeader header) {
+ boolean isEnabled = header.isEnabled();
+ if (header.getTable() != null) {
+ // fix for issue 472 - handle standalone table headers
+ isEnabled = isEnabled && header.getTable().isEnabled();
+ }
+ ComponentState currState = isEnabled ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED;
+ Color gridColor = SubstanceColorSchemeUtilities.getColorScheme(header,
+ ColorSchemeAssociationKind.BORDER, currState).getLineColor();
+ return gridColor;
+ }
+
+ /**
+ * Paints cell.
+ *
+ * @param g
+ * Graphic context.
+ * @param cellRect
+ * Cell rectangle.
+ * @param columnIndex
+ * Column index.
+ * @param isSelected
+ * Selection indication.
+ */
+ private void paintCell(Graphics g, Rectangle cellRect, int columnIndex,
+ boolean isSelected) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(header, g));
+
+ // paint default background
+ Component component = getHeaderRenderer(columnIndex);
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = getModelStateInfo(columnIndex);
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ((modelStateInfo == null) ? null
+ : modelStateInfo.getStateContributionMap());
+ ComponentState currState = ((modelStateInfo == null) ? getColumnState(columnIndex)
+ : modelStateInfo.getCurrModelState());
+
+ boolean hasHighlights = false;
+ if (activeStates != null) {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
+ .entrySet()) {
+ hasHighlights = (SubstanceColorSchemeUtilities
+ .getHighlightAlpha(this.header, stateEntry.getKey())
+ * stateEntry.getValue().getContribution() > 0.0f);
+ if (hasHighlights)
+ break;
+ }
+ } else {
+ hasHighlights = (SubstanceColorSchemeUtilities.getHighlightAlpha(
+ this.header, currState) > 0.0f);
+ }
+
+ // System.out.println(row + ":" + prevTheme.getDisplayName() + "["
+ // + alphaForPrevBackground + "]:" + currTheme.getDisplayName()
+ // + "[" + alphaForCurrBackground + "]");
+
+ if (hasHighlights) {
+ if (activeStates == null) {
+ float alpha = SubstanceColorSchemeUtilities.getHighlightAlpha(
+ this.header, currState);
+ if (alpha > 0.0f) {
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.header,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ currState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.header,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ currState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.header, alpha, g));
+ HighlightPainterUtils.paintHighlight(g2d,
+ this.rendererPane, rendererPane, cellRect, 0.8f,
+ null, fillScheme, borderScheme);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.header, g));
+ }
+ } else {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = stateEntry.getKey();
+ float alpha = SubstanceColorSchemeUtilities
+ .getHighlightAlpha(this.header, activeState)
+ * stateEntry.getValue().getContribution();
+ if (alpha == 0.0f)
+ continue;
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.header,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.header,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ activeState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.header, alpha, g));
+ HighlightPainterUtils.paintHighlight(g2d,
+ this.rendererPane, rendererPane, cellRect, 0.8f,
+ null, fillScheme, borderScheme);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.header, g));
+ }
+ }
+ }
+
+ rendererPane.paintComponent(g2d, component, header, cellRect.x,
+ cellRect.y, cellRect.width, cellRect.height, true);
+
+ g2d.dispose();
+ }
+
+ /**
+ * Retrieves view index for the specified column.
+ *
+ * @param aColumn
+ * Table column.
+ * @return View index for the specified column.
+ */
+ private int viewIndexForColumn(TableColumn aColumn) {
+ TableColumnModel cm = header.getColumnModel();
+ for (int column = 0; column < cm.getColumnCount(); column++) {
+ if (cm.getColumn(column) == aColumn) {
+ return column;
+ }
+ }
+ return -1;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ // fix for issue 175 - table header under resize mode off
+ // was painted in color scheme-agnostic (gray) color.
+ boolean isEnabled = this.header.isEnabled();
+ if (this.header.getTable() != null) {
+ // fix for issue 472 - handle standalone table headers
+ isEnabled = isEnabled && this.header.getTable().isEnabled();
+ }
+ ComponentState backgroundState = isEnabled ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED;
+
+ // fix for issue 360 - respect the clip bounds of the
+ // table header
+ Rectangle clip = g.getClipBounds();
+ if (clip == null)
+ clip = c.getBounds();
+
+ // do not use the highlight scheme for painting the
+ // table header background
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c, backgroundState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c, ColorSchemeAssociationKind.BORDER,
+ backgroundState);
+
+ HighlightPainterUtils.paintHighlight(g, null, c, clip, 0.0f, null,
+ fillScheme, borderScheme);
+ paint(g, c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTableHeaderUI#uninstallUI(javax.swing.JComponent
+ * )
+ */
+ @Override
+ public void uninstallUI(JComponent c) {
+ for (int i = 0; i < header.getColumnModel().getColumnCount(); i++) {
+ TableColumn aColumn = header.getColumnModel().getColumn(i);
+ TableCellRenderer renderer = aColumn.getHeaderRenderer();
+ if (renderer == null) {
+ renderer = header.getDefaultRenderer();
+ }
+ Component rendComp = renderer.getTableCellRendererComponent(header
+ .getTable(), aColumn.getHeaderValue(), false, false, -1, i);
+ SwingUtilities.updateComponentTreeUI(rendComp);
+ }
+ super.uninstallUI(c);
+ }
+
+ /**
+ * Returns the current state for the specified cell.
+ *
+ * @param columnIndex
+ * Column index.
+ * @return The current state for the specified column.
+ */
+ public ComponentState getColumnState(int columnIndex) {
+ // ButtonModel synthModel = new DefaultButtonModel();
+ boolean toEnable = header.isEnabled();
+
+ // get the rollover column index from the table UI delegate
+ JTable table = this.header.getTable();
+ if (table != null) {
+ toEnable = toEnable && table.isEnabled();
+ }
+
+ StateTransitionTracker tracker = this.stateTransitionMultiTracker
+ .getTracker(columnIndex);
+ if (tracker == null) {
+ boolean isRollover = false;
+ TableColumnModel columnModel = header.getColumnModel();
+ boolean isSelected = false;
+ if (columnModel.getColumnSelectionAllowed()) {
+ isSelected = columnModel.getSelectionModel().isSelectedIndex(
+ columnIndex);
+ if ((table != null)
+ && (table.getUI() instanceof SubstanceTableUI)) {
+ SubstanceTableUI tableUI = (SubstanceTableUI) table.getUI();
+ int rolledOverIndex = tableUI.getRolloverColumnIndex();
+ isRollover = (rolledOverIndex >= 0)
+ && (rolledOverIndex == columnIndex);
+ boolean hasSelectionAnimations = tableUI
+ .hasSelectionAnimations();
+ if (hasSelectionAnimations
+ && AnimationConfigurationManager.getInstance()
+ .isAnimationAllowed(
+ AnimationFacet.SELECTION, table))
+ isSelected = this.selectedIndices
+ .containsKey(columnIndex);
+ }
+ }
+ return ComponentState.getState(toEnable, isRollover, isSelected);
+ } else {
+ ComponentState fromTracker = tracker.getModelStateInfo()
+ .getCurrModelState();
+ return ComponentState.getState(toEnable, fromTracker
+ .isFacetActive(ComponentStateFacet.ROLLOVER), fromTracker
+ .isFacetActive(ComponentStateFacet.SELECTION));
+ }
+ }
+
+ public StateTransitionTracker.ModelStateInfo getModelStateInfo(
+ int columnIndex) {
+ if (this.stateTransitionMultiTracker.size() == 0)
+ return null;
+ StateTransitionTracker tracker = this.stateTransitionMultiTracker
+ .getTracker(columnIndex);
+ if (tracker == null) {
+ return null;
+ } else {
+ return tracker.getModelStateInfo();
+ }
+ }
+
+ public StateTransitionTracker getStateTransitionTracker(int columnIndex) {
+ return this.stateTransitionMultiTracker.getTracker(columnIndex);
+ }
+
+ /**
+ * Returns the scroll pane corner filler component. This method is used in
+ * {@link SubstanceScrollPaneUI} to put a consistent filler for tables.
+ *
+ * @return Scroll pane corner filler.
+ */
+ public JComponent getScrollPaneCornerFiller() {
+ return new ScrollPaneCornerFiller(this.header);
+ }
+
+ /**
+ * Corner filler for tables wrapped in scroll panes.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected static class ScrollPaneCornerFiller extends JComponent implements
+ UIResource {
+ /**
+ * Associated table header.
+ */
+ protected JTableHeader header;
+
+ /**
+ * Creates a new corner filler.
+ *
+ * @param header
+ * Table header.
+ */
+ public ScrollPaneCornerFiller(JTableHeader header) {
+ this.header = header;
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ // System.err.println("Painting " + this.hashCode() + " from "
+ // + ((header == null) ? "null" : header.hashCode()));
+
+ boolean ltr = header.getComponentOrientation().isLeftToRight();
+ final ComponentState backgroundState = (header.isEnabled() && header
+ .getTable().isEnabled()) ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED;
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.header,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ backgroundState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.header,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ backgroundState);
+
+ HighlightPainterUtils.paintHighlight(g2d, null, this.header,
+ new Rectangle(0, 0, this.getWidth(), this.getHeight()),
+ 0.0f, null, fillScheme, borderScheme);
+
+ g2d.setColor(getGridColor(this.header));
+ float strokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(header));
+ g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_BEVEL));
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.header,
+ 0.7f, g));
+
+ int x = ltr ? (int) strokeWidth / 2 : getWidth() - 1
+ - (int) strokeWidth / 2;
+ g2d.drawLine(x, 0, x, getHeight());
+
+ g2d.dispose();
+ }
+ }
+
+ /**
+ * Processes the events on model changes on the table column model.
+ *
+ * @param oldModel
+ * Old column model.
+ * @param newModel
+ * New column model.
+ */
+ public void processColumnModelChangeEvent(TableColumnModel oldModel,
+ TableColumnModel newModel) {
+ if (oldModel != null) {
+ oldModel.getSelectionModel().removeListSelectionListener(
+ substanceFadeSelectionListener);
+ }
+ if (newModel != null) {
+ newModel.getSelectionModel().addListSelectionListener(
+ substanceFadeSelectionListener);
+ }
+ selectedIndices.clear();
+ stateTransitionMultiTracker.clear();
+ }
+
+ /**
+ * Repaints a single column header during the fade animation cycle.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class ColumnHeaderRepaintCallback extends
+ UIThreadTimelineCallbackAdapter {
+ /**
+ * Associated table header.
+ */
+ protected JTableHeader header;
+
+ /**
+ * Associated (animated) column index.
+ */
+ protected int columnIndex;
+
+ /**
+ * Creates a new animation repaint callback.
+ *
+ * @param header
+ * Associated table header.
+ * @param columnIndex
+ * Associated (animated) column index.
+ */
+ public ColumnHeaderRepaintCallback(JTableHeader header, int columnIndex) {
+ this.header = header;
+ this.columnIndex = columnIndex;
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ repaintColumnHeader();
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ repaintColumnHeader();
+ }
+
+ /**
+ * Repaints the associated cell.
+ */
+ private void repaintColumnHeader() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (header == null) {
+ // may happen if the LAF was switched in the meantime
+ return;
+ }
+ try {
+ // maybeUpdateLayoutState();
+ int cellCount = header.getColumnModel()
+ .getColumnCount();
+ if ((cellCount > 0) && (columnIndex < cellCount)) {
+ // need to retrieve the cell rectangle since the
+ // cells can be moved while animating
+ Rectangle rect = header.getHeaderRect(columnIndex);
+ Rectangle damaged = new Rectangle(rect.x - 5,
+ rect.y, rect.width + 10, rect.height);
+ header.repaint(damaged);
+ }
+ } catch (RuntimeException re) {
+ return;
+ }
+ }
+ });
+ }
+ }
+
+ public StateTransitionTracker getTracker(final int columnIndex,
+ boolean initialRollover, boolean initialSelected) {
+ StateTransitionTracker tracker = stateTransitionMultiTracker
+ .getTracker(columnIndex);
+ if (tracker == null) {
+ ButtonModel model = new DefaultButtonModel();
+ model.setSelected(initialSelected);
+ model.setRollover(initialRollover);
+ tracker = new StateTransitionTracker(header, model);
+ tracker.registerModelListeners();
+ tracker.setRepaintCallback(new RepaintCallback() {
+ @Override
+ public TimelineCallback getRepaintCallback() {
+ return new ColumnHeaderRepaintCallback(header, columnIndex);
+ }
+ });
+ stateTransitionMultiTracker.addTracker(columnIndex, tracker);
+ }
+ return tracker;
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTableUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTableUI.java
new file mode 100755
index 0000000..f581c2a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTableUI.java
@@ -0,0 +1,2652 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.RowSorter.SortKey;
+import javax.swing.border.Border;
+import javax.swing.event.*;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicTableUI;
+import javax.swing.table.*;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.SubstanceConstants.Side;
+import org.pushingpixels.substance.api.renderers.SubstanceDefaultTableCellRenderer;
+import org.pushingpixels.substance.api.renderers.SubstanceDefaultTableHeaderCellRenderer;
+import org.pushingpixels.substance.internal.animation.StateTransitionMultiTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.RepaintCallback;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.TimelineCallback;
+import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
+
+/**
+ * UI for tables in <b>Substance</b> look and feel. Unfortunately, the entire
+ * painting stack has been copied from {@link BasicTableUI} since the methods
+ * are private. The animation effects are implemented in the
+ * {@link #paintCell(Graphics, Rectangle, int, int)}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTableUI extends BasicTableUI implements
+ UpdateOptimizationAware {
+ /**
+ * Holds the list of currently selected row-column indexes.
+ */
+ protected Map<TableCellId, Object> selectedIndices;
+
+ /**
+ * Holds the currently rolled-over row-column index, or <code>null</code> if
+ * none such.
+ */
+ protected Set<TableCellId> rolledOverIndices;
+
+ protected TableCellId focusedCellId;
+
+ /**
+ * Holds the currently rolled-over column index, or <code>-1</code> if none
+ * such. This is used for the table header animations.
+ */
+ protected int rolledOverColumn;
+
+ /**
+ * Map of default renderers.
+ */
+ protected Map<Class<?>, TableCellRenderer> defaultRenderers;
+
+ /**
+ * Map of default editors.
+ */
+ protected Map<Class<?>, TableCellEditor> defaultEditors;
+
+ /**
+ * Listener that listens to changes on table properties.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Listener for transition animations on list selections.
+ */
+ protected TableStateListener substanceTableStateListener;
+
+ /**
+ * Listener for transition animations on table rollovers.
+ */
+ protected RolloverFadeListener substanceFadeRolloverListener;
+
+ protected FocusListener substanceFocusListener;
+
+ private StateTransitionMultiTracker<TableCellId> stateTransitionMultiTracker;
+
+ protected Boolean drawLeadingVerticalLine;
+ protected Boolean drawTrailingVerticalLine;
+
+ /**
+ * Cell renderer insets. Is computed in {@link #installDefaults()} and
+ * reused in
+ * {@link SubstanceDefaultTableCellRenderer#getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)}
+ * and
+ * {@link SubstanceDefaultTableHeaderCellRenderer#getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)}
+ * for performance optimizations.
+ */
+ private Insets cellRendererInsets;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceTableUI();
+ }
+
+ /**
+ * Creates a UI delegate for table.
+ */
+ public SubstanceTableUI() {
+ super();
+ this.selectedIndices = new HashMap<TableCellId, Object>();
+ this.rolledOverIndices = new HashSet<TableCellId>();
+ this.stateTransitionMultiTracker = new StateTransitionMultiTracker<TableCellId>();
+ this.rolledOverColumn = -1;
+
+ this.cellId = new TableCellId(-1, -1);
+ }
+
+ static class BooleanEditor extends DefaultCellEditor {
+ private static class SubstanceEditorCheckBox extends JCheckBox {
+ @Override
+ public void setOpaque(boolean isOpaque) {
+ if (!isOpaque) {
+ super.setOpaque(isOpaque);
+ }
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return false;
+ }
+
+ @Override
+ public void setBorder(Border border) {
+ }
+ }
+
+ public BooleanEditor() {
+ super(new SubstanceEditorCheckBox());
+ JCheckBox checkBox = (JCheckBox) getComponent();
+ checkBox.setOpaque(false);
+ checkBox.setHorizontalAlignment(JCheckBox.CENTER);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTableUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ if (SubstanceCoreUtilities.toDrawWatermark(this.table))
+ this.table.setOpaque(false);
+
+ // fix for defect 117 - need to restore default table cell
+ // renderers when Substance is unset
+ this.defaultRenderers = new HashMap<Class<?>, TableCellRenderer>();
+
+ Class<?>[] defClasses = new Class[] { Object.class, Icon.class,
+ ImageIcon.class, Number.class, Float.class, Double.class,
+ Date.class, Boolean.class };
+ for (Class<?> clazz : defClasses) {
+ this.defaultRenderers.put(clazz,
+ this.table.getDefaultRenderer(clazz));
+ }
+
+ // Override default renderers - note fix for issue 194
+ // that doesn't override user-specific renderers (those that don't come
+ // from JTable class).
+ this.installRendererIfNecessary(Object.class,
+ new SubstanceDefaultTableCellRenderer());
+ this.installRendererIfNecessary(Icon.class,
+ new SubstanceDefaultTableCellRenderer.IconRenderer());
+ this.installRendererIfNecessary(ImageIcon.class,
+ new SubstanceDefaultTableCellRenderer.IconRenderer());
+ this.installRendererIfNecessary(Number.class,
+ new SubstanceDefaultTableCellRenderer.NumberRenderer());
+ this.installRendererIfNecessary(Float.class,
+ new SubstanceDefaultTableCellRenderer.DoubleRenderer());
+ this.installRendererIfNecessary(Double.class,
+ new SubstanceDefaultTableCellRenderer.DoubleRenderer());
+ this.installRendererIfNecessary(Date.class,
+ new SubstanceDefaultTableCellRenderer.DateRenderer());
+
+ // fix for bug 56 - making default renderer for Boolean a check box.
+ this.installRendererIfNecessary(Boolean.class,
+ new SubstanceDefaultTableCellRenderer.BooleanRenderer());
+
+ this.defaultEditors = new HashMap<Class<?>, TableCellEditor>();
+
+ Class<?>[] defEditorClasses = new Class[] { Boolean.class };
+ for (Class<?> clazz : defEditorClasses) {
+ this.defaultEditors.put(clazz, this.table.getDefaultEditor(clazz));
+ }
+ this.installEditorIfNecessary(Boolean.class, new BooleanEditor());
+
+ int rows = this.table.getRowCount();
+ int cols = this.table.getColumnCount();
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ if (this.table.isCellSelected(i, j)) {
+ TableCellId cellId = new TableCellId(i, j);
+ this.selectedIndices.put(cellId,
+ this.table.getValueAt(i, j));
+ }
+ }
+ }
+
+ // This is a little tricky, and hopefully will not
+ // interfere with existing applications. The row height in tables
+ // is computed differently from trees and lists. While lists
+ // trees respect the current renderers and their insets, the
+ // JTable uses hard-code value of 16 pixels as the default
+ // row height. This, obviously, doesn't sit well with the support
+ // for custom fonts and high-DPI monitors.
+ //
+ // The current solution first checks whether all the renderers
+ // come from Substance. If not, it does nothing. If they do, it
+ // creates a dummy label, computes its preferred height and apply
+ // insets. There's no need to go over each cell and compute its
+ // preferred height - since at this moment the cell renderers
+ // *are* Substance labels.
+ boolean areAllRenderersFromSubstance = true;
+ TableColumnModel columnModel = table.getColumnModel();
+ for (int i = 0; i < columnModel.getColumnCount(); i++) {
+ TableColumn column = columnModel.getColumn(i);
+ TableCellRenderer renderer = column.getCellRenderer();
+ if (renderer == null) {
+ renderer = table.getDefaultRenderer(table.getColumnClass(i));
+ }
+ if ((renderer instanceof SubstanceDefaultTableCellRenderer)
+ || (renderer instanceof SubstanceDefaultTableCellRenderer.BooleanRenderer))
+ continue;
+ areAllRenderersFromSubstance = false;
+ break;
+ }
+ if (areAllRenderersFromSubstance) {
+ Insets rendererInsets = SubstanceSizeUtils
+ .getTableCellRendererInsets(SubstanceSizeUtils
+ .getComponentFontSize(table));
+ JLabel dummy = new JLabel("dummy");
+ dummy.setFont(table.getFont());
+ int rowHeight = dummy.getPreferredSize().height
+ + rendererInsets.bottom + rendererInsets.top;
+ table.setRowHeight(rowHeight);
+ }
+
+ // instead of computing the cell renderer insets on
+ // every cell rendering, compute it once and expose to the
+ // SubstanceDefaultTableCellRenderer
+ this.cellRendererInsets = SubstanceSizeUtils
+ .getTableCellRendererInsets(SubstanceSizeUtils
+ .getComponentFontSize(table));
+
+ drawLeadingVerticalLine = (Boolean) table.getClientProperty(SubstanceLookAndFeel.TABLE_LEADING_VERTICAL_LINE);
+ drawTrailingVerticalLine = (Boolean) table.getClientProperty(SubstanceLookAndFeel.TABLE_TRAILING_VERTICAL_LINE);
+ }
+
+ /**
+ * Installs Substance-specific renderers for column classes that don't have
+ * application-specific renderers installed by the user code.
+ *
+ * @param clazz
+ * Column class.
+ * @param renderer
+ * Default renderer for the specified column class.
+ */
+ protected void installRendererIfNecessary(Class<?> clazz,
+ TableCellRenderer renderer) {
+ TableCellRenderer currRenderer = this.table.getDefaultRenderer(clazz);
+ if (currRenderer != null) {
+ boolean isCore = (currRenderer instanceof DefaultTableCellRenderer.UIResource)
+ || (currRenderer.getClass().getName()
+ .startsWith("javax.swing.JTable"));
+ if (!isCore)
+ return;
+ }
+ this.table.setDefaultRenderer(clazz, renderer);
+ }
+
+ /**
+ * Installs Substance-specific renderers for column classes that don't have
+ * application-specific renderers installed by the user code.
+ *
+ * @param clazz
+ * Column class.
+ * @param editor
+ * Default renderer for the specified column class.
+ */
+ protected void installEditorIfNecessary(Class<?> clazz,
+ TableCellEditor editor) {
+ TableCellEditor currEditor = this.table.getDefaultEditor(clazz);
+ if (currEditor != null) {
+ boolean isCore = currEditor.getClass().getName()
+ .startsWith("javax.swing.JTable");
+ if (!isCore)
+ return;
+ }
+ this.table.setDefaultEditor(clazz, editor);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTableUI#uninstallDefaults()
+ */
+ @Override
+ protected void uninstallDefaults() {
+ // fix for defect 117 - need to restore default table cell
+ // renderers when Substance is unset
+ for (Map.Entry<Class<?>, TableCellRenderer> entry : this.defaultRenderers
+ .entrySet()) {
+ // fix for issue 194 - restore only those renderers that were
+ // overriden by Substance.
+ this.uninstallRendererIfNecessary(entry.getKey(), entry.getValue());
+ }
+
+ for (Map.Entry<Class<?>, TableCellEditor> entry : this.defaultEditors
+ .entrySet()) {
+ this.uninstallEditorIfNecessary(entry.getKey(), entry.getValue());
+ }
+
+ this.selectedIndices.clear();
+ // this.table.putClientProperty(SubstanceTableUI.SELECTED_INDICES,
+ // null);
+
+ super.uninstallDefaults();
+ }
+
+ /**
+ * Uninstalls default Substance renderers that were installed in
+ * {@link #installRendererIfNecessary(Class, TableCellRenderer)}.
+ *
+ * @param clazz
+ * Column class.
+ * @param renderer
+ * Renderer to restore.
+ */
+ protected void uninstallRendererIfNecessary(Class<?> clazz,
+ TableCellRenderer renderer) {
+ TableCellRenderer currRenderer = this.table.getDefaultRenderer(clazz);
+ if (currRenderer != null) {
+ boolean isSubstanceRenderer = isSubstanceDefaultRenderer(currRenderer);
+ if (!isSubstanceRenderer)
+ return;
+ }
+ if (renderer instanceof Component)
+ SwingUtilities.updateComponentTreeUI((Component) renderer);
+ this.table.setDefaultRenderer(clazz, renderer);
+ }
+
+ /**
+ * Uninstalls default Substance editors that were installed in
+ * {@link #installEditorIfNecessary(Class, TableCellEditor)}.
+ *
+ * @param clazz
+ * Column class.
+ * @param editor
+ * Editor to restore.
+ */
+ protected void uninstallEditorIfNecessary(Class<?> clazz,
+ TableCellEditor editor) {
+ TableCellEditor currEditor = this.table.getDefaultEditor(clazz);
+ if (currEditor != null) {
+ boolean isSubstanceEditor = isSubstanceDefaultEditor(currEditor);
+ if (!isSubstanceEditor)
+ return;
+ }
+ if (editor instanceof Component)
+ SwingUtilities.updateComponentTreeUI((Component) editor);
+ this.table.setDefaultEditor(clazz, editor);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTableUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (SubstanceLookAndFeel.WATERMARK_VISIBLE.equals(evt
+ .getPropertyName())) {
+ SubstanceTableUI.this.table
+ .setOpaque(!SubstanceCoreUtilities
+ .toDrawWatermark(SubstanceTableUI.this.table));
+ }
+
+ if ("columnSelectionAllowed".equals(evt.getPropertyName())
+ || "rowSelectionAllowed".equals(evt.getPropertyName())) {
+ SubstanceTableUI.this.syncSelection(true);
+ }
+
+ if ("model".equals(evt.getPropertyName())) {
+ TableModel old = (TableModel) evt.getOldValue();
+ if (old != null) {
+ old.removeTableModelListener(substanceTableStateListener);
+ }
+ // fix for defect 291 - track changes to the table.
+ table.getModel().addTableModelListener(
+ substanceTableStateListener);
+ selectedIndices.clear();
+ stateTransitionMultiTracker.clear();
+ SubstanceTableUI.this.syncSelection(true);
+ }
+
+ if ("columnModel".equals(evt.getPropertyName())) {
+ TableColumnModel old = (TableColumnModel) evt.getOldValue();
+ if (old != null) {
+ old.getSelectionModel().removeListSelectionListener(
+ substanceTableStateListener);
+ }
+ table.getColumnModel()
+ .getSelectionModel()
+ .addListSelectionListener(
+ substanceTableStateListener);
+ selectedIndices.clear();
+ stateTransitionMultiTracker.clear();
+ SubstanceTableUI.this.syncSelection(true);
+
+ JTableHeader tableHeader = table.getTableHeader();
+ // fix for issue 408 - table header may be null.
+ if (tableHeader != null) {
+ // fix for issue 309 - syncing animations on tables
+ // and table headers.
+ SubstanceTableHeaderUI headerUI = (SubstanceTableHeaderUI) tableHeader
+ .getUI();
+ headerUI.processColumnModelChangeEvent(
+ (TableColumnModel) evt.getOldValue(),
+ (TableColumnModel) evt.getNewValue());
+ }
+ }
+
+ // fix for defect 243 - not tracking changes to selection
+ // model results in incorrect selection painting on JXTreeTable
+ // component from SwingX.
+ if ("selectionModel".equals(evt.getPropertyName())) {
+ ListSelectionModel old = (ListSelectionModel) evt
+ .getOldValue();
+ if (old != null) {
+ old.removeListSelectionListener(substanceTableStateListener);
+ }
+ table.getSelectionModel().addListSelectionListener(
+ substanceTableStateListener);
+ selectedIndices.clear();
+ stateTransitionMultiTracker.clear();
+ SubstanceTableUI.this.syncSelection(true);
+ }
+
+ // fix for issue 479 - tracking sort / filter changes and
+ // canceling selection animations
+ if ("rowSorter".equals(evt.getPropertyName())) {
+ RowSorter old = (RowSorter) evt.getOldValue();
+ if (old != null) {
+ old.removeRowSorterListener(substanceTableStateListener);
+ }
+ RowSorter newSorter = (RowSorter) evt.getNewValue();
+ if (newSorter != null) {
+ newSorter
+ .addRowSorterListener(substanceTableStateListener);
+ }
+ selectedIndices.clear();
+ stateTransitionMultiTracker.clear();
+ SubstanceTableUI.this.syncSelection(true);
+ }
+
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // fix for bug 341
+ if (table == null)
+ return;
+ table.updateUI();
+ }
+ });
+ }
+
+ if ("background".equals(evt.getPropertyName())) {
+ // propagate application-specific background color to the
+ // header.
+ Color newBackgr = (Color) evt.getNewValue();
+ JTableHeader header = table.getTableHeader();
+ if (header != null) {
+ Color headerBackground = header.getBackground();
+ if (SubstanceCoreUtilities
+ .canReplaceChildBackgroundColor(headerBackground)) {
+ if (!(newBackgr instanceof UIResource)) {
+ if (newBackgr == null) {
+ header.setBackground(null);
+ } else {
+ // Issue 450 - wrap the color in
+ // SubstanceColorResource to
+ // mark that it can be replaced.
+ header.setBackground(new SubstanceColorResource(
+ newBackgr));
+ }
+ } else {
+ header.setBackground(newBackgr);
+ }
+ }
+ }
+ }
+
+ // fix for issue 361 - track enabled status of the table
+ // and propagate to the table header
+ if ("enabled".equals(evt.getPropertyName())) {
+ JTableHeader header = table.getTableHeader();
+ if (header != null) {
+ header.setEnabled(table.isEnabled());
+ }
+ }
+
+ if ("dropLocation".equals(evt.getPropertyName())) {
+ JTable.DropLocation oldValue = (JTable.DropLocation) evt
+ .getOldValue();
+ if (oldValue != null) {
+ Rectangle oldRect = getCellRectangleForRepaint(
+ oldValue.getRow(), oldValue.getColumn());
+ table.repaint(oldRect);
+ }
+ JTable.DropLocation newValue = table.getDropLocation();
+ if (newValue != null) {
+ Rectangle newRect = getCellRectangleForRepaint(table
+ .getDropLocation().getRow(), table
+ .getDropLocation().getColumn());
+ table.repaint(newRect);
+ }
+ }
+
+ if ("tableCellEditor".equals(evt.getPropertyName())) {
+ // fix for issue 481 - rollovers on cell editing
+ TableCellEditor newEditor = (TableCellEditor) evt
+ .getNewValue();
+ TableCellEditor oldEditor = (TableCellEditor) evt
+ .getOldValue();
+ if (oldEditor != null) {
+ if (table.getEditorComponent() != null) {
+ table.getEditorComponent().removeMouseListener(
+ substanceFadeRolloverListener);
+ }
+ }
+ if (newEditor != null) {
+ if (table.getEditorComponent() != null) {
+ table.getEditorComponent().addMouseListener(
+ substanceFadeRolloverListener);
+ }
+ }
+ }
+
+ if (SubstanceLookAndFeel.TABLE_LEADING_VERTICAL_LINE.equals(evt.getPropertyName())) {
+ drawLeadingVerticalLine = (Boolean) evt.getNewValue();
+ }
+ if (SubstanceLookAndFeel.TABLE_TRAILING_VERTICAL_LINE.equals(evt.getPropertyName())) {
+ drawTrailingVerticalLine = (Boolean) evt.getNewValue();
+ }
+ }
+ };
+ this.table
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+
+ // Add listener for the selection animation
+ this.substanceTableStateListener = new TableStateListener();
+ this.table.getSelectionModel().addListSelectionListener(
+ this.substanceTableStateListener);
+ TableColumnModel columnModel = this.table.getColumnModel();
+ columnModel.getSelectionModel().addListSelectionListener(
+ this.substanceTableStateListener);
+ this.table.getModel().addTableModelListener(
+ this.substanceTableStateListener);
+ if (this.table.getRowSorter() != null) {
+ this.table.getRowSorter().addRowSorterListener(
+ this.substanceTableStateListener);
+ }
+
+ // Add listener for the transition animation
+ this.substanceFadeRolloverListener = new RolloverFadeListener();
+ this.table.addMouseMotionListener(this.substanceFadeRolloverListener);
+ this.table.addMouseListener(this.substanceFadeRolloverListener);
+
+ // fix for issue 481 - tracking focus events on the table
+ this.substanceFocusListener = new FocusListener() {
+ @Override
+ public void focusLost(FocusEvent e) {
+ if (focusedCellId == null)
+ return;
+
+ ComponentState cellState = getCellState(focusedCellId);
+ StateTransitionTracker tracker = getTracker(focusedCellId,
+ cellState.isFacetActive(ComponentStateFacet.ROLLOVER),
+ cellState.isFacetActive(ComponentStateFacet.SELECTION));
+ tracker.setFocusState(false);
+
+ focusedCellId = null;
+ }
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ int rowLead = table.getSelectionModel().getLeadSelectionIndex();
+ int colLead = table.getColumnModel().getSelectionModel()
+ .getLeadSelectionIndex();
+ if ((rowLead >= 0) && (colLead >= 0)) {
+ TableCellId toFocus = new TableCellId(rowLead, colLead);
+ if (toFocus.equals(focusedCellId))
+ return;
+ ComponentState cellState = getCellState(toFocus);
+ StateTransitionTracker tracker = getTracker(
+ toFocus,
+ cellState
+ .isFacetActive(ComponentStateFacet.ROLLOVER),
+ cellState
+ .isFacetActive(ComponentStateFacet.SELECTION));
+ tracker.setFocusState(true);
+
+ focusedCellId = toFocus;
+ }
+ }
+ };
+ this.table.addFocusListener(this.substanceFocusListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTableUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.table
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ this.table.getSelectionModel().removeListSelectionListener(
+ this.substanceTableStateListener);
+ this.table.getColumnModel().getSelectionModel()
+ .removeListSelectionListener(this.substanceTableStateListener);
+ this.table.getModel().removeTableModelListener(
+ this.substanceTableStateListener);
+ if (this.table.getRowSorter() != null) {
+ this.table.getRowSorter().removeRowSorterListener(
+ this.substanceTableStateListener);
+ }
+ this.substanceTableStateListener = null;
+
+ // Remove listener for the fade animation
+ this.table
+ .removeMouseMotionListener(this.substanceFadeRolloverListener);
+ this.table.removeMouseListener(this.substanceFadeRolloverListener);
+ this.substanceFadeRolloverListener = null;
+
+ this.table.removeFocusListener(this.substanceFocusListener);
+ this.substanceFocusListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /**
+ * Paint a representation of the <code>table</code> instance that was set in
+ * installUI().
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Rectangle clip = g.getClipBounds();
+
+ Rectangle bounds = this.table.getBounds();
+ // account for the fact that the graphics has already been translated
+ // into the table's bounds
+ bounds.x = bounds.y = 0;
+
+ if (this.table.getRowCount() <= 0 || this.table.getColumnCount() <= 0 ||
+ // this check prevents us from painting the entire table
+ // when the clip doesn't intersect our bounds at all
+ !bounds.intersects(clip)) {
+
+ return;
+ }
+
+ Point upperLeft = clip.getLocation();
+ Point lowerRight = new Point(clip.x + clip.width - 1, clip.y
+ + clip.height - 1);
+ int rMin = this.table.rowAtPoint(upperLeft);
+ int rMax = this.table.rowAtPoint(lowerRight);
+ // This should never happen (as long as our bounds intersect the clip,
+ // which is why we bail above if that is the case).
+ if (rMin == -1) {
+ rMin = 0;
+ }
+ // If the table does not have enough rows to fill the view we'll get -1.
+ // (We could also get -1 if our bounds don't intersect the clip,
+ // which is why we bail above if that is the case).
+ // Replace this with the index of the last row.
+ if (rMax == -1) {
+ rMax = this.table.getRowCount() - 1;
+ }
+
+ boolean ltr = this.table.getComponentOrientation().isLeftToRight();
+ int cMin = this.table.columnAtPoint(ltr ? upperLeft : lowerRight);
+ int cMax = this.table.columnAtPoint(ltr ? lowerRight : upperLeft);
+ // This should never happen.
+ if (cMin == -1) {
+ cMin = 0;
+ }
+ // If the table does not have enough columns to fill the view we'll get
+ // -1.
+ // Replace this with the index of the last column.
+ if (cMax == -1) {
+ cMax = this.table.getColumnCount() - 1;
+ }
+
+ // Paint the cells.
+ this.paintCells(g, rMin, rMax, cMin, cMax);
+
+ // Paint the grid.
+ this.paintGrid(g, rMin, rMax, cMin, cMax);
+
+ // Paint the drop lines
+ this.paintDropLines(g);
+ }
+
+ /**
+ * Paints the grid lines within <I>aRect</I>, using the grid color set with
+ * <I>setGridColor</I>. Paints vertical lines if
+ * <code>getShowVerticalLines()</code> returns true and paints horizontal
+ * lines if <code>getShowHorizontalLines()</code> returns true.
+ */
+ protected void paintGrid(Graphics g, int rMin, int rMax, int cMin, int cMax) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ ComponentState currState = this.table.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED;
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(this.table,
+ currState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.table,
+ alpha, g));
+
+ Color gridColor = this.table.getGridColor();
+ if (gridColor instanceof UIResource) {
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.table,
+ ColorSchemeAssociationKind.BORDER, this.table
+ .isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED);
+ gridColor = scheme.getLineColor();
+ }
+ g2d.setColor(gridColor);
+
+ Rectangle minCell = this.table.getCellRect(rMin, cMin, true);
+ Rectangle maxCell = this.table.getCellRect(rMax, cMax, true);
+ Rectangle damagedArea = minCell.union(maxCell);
+
+ float strokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.table));
+ g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_BEVEL));
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ if (this.table.getShowHorizontalLines()) {
+ int tableWidth = damagedArea.x + damagedArea.width;
+ int y = damagedArea.y;
+ for (int row = rMin; row <= rMax; row++) {
+ y += this.table.getRowHeight(row);
+ g2d.drawLine(damagedArea.x, y - 1, tableWidth - 1, y - 1);
+ }
+ }
+ if (this.table.getShowVerticalLines()) {
+ TableColumnModel cm = this.table.getColumnModel();
+ int tableHeight = damagedArea.y + damagedArea.height;
+ int x;
+ if (this.table.getComponentOrientation().isLeftToRight()) {
+ x = damagedArea.x;
+ for (int column = cMin; column <= cMax; column++) {
+ int w = cm.getColumn(column).getWidth();
+ if (hasLeadingVerticalGridLine(cm, column)) {
+ g2d.drawLine(x, 0, x, tableHeight - 1);
+ }
+ x += w;
+ if (hasTrailingVerticalGridLine(cm, column)) {
+ g2d.drawLine(x - 1, 0, x - 1, tableHeight - 1);
+ }
+ }
+ } else {
+ x = damagedArea.x + damagedArea.width;
+ // fix for defect 196 - proper grid painting on RTL tables
+ for (int column = cMin; column <= cMax; column++) {
+ int w = cm.getColumn(column).getWidth();
+ if (hasLeadingVerticalGridLine(cm, column)) {
+ g2d.drawLine(x - 1, 0, x - 1, tableHeight - 1);
+ }
+ x -= w;
+ if (hasTrailingVerticalGridLine(cm, column)) {
+ g2d.drawLine(x, 0, x, tableHeight - 1);
+ }
+ }
+ }
+ }
+ g2d.dispose();
+ }
+
+ private boolean hasTrailingVerticalGridLine(TableColumnModel cm, int column) {
+ boolean toDrawLine = (column != (cm.getColumnCount() - 1));
+ if (!toDrawLine) {
+ if (drawTrailingVerticalLine != null) {
+ toDrawLine = drawTrailingVerticalLine;
+ } else {
+ Container parent = this.table.getParent();
+ toDrawLine = (parent != null)
+ && (parent.getWidth() >= this.table.getWidth());
+ }
+ }
+ return toDrawLine;
+ }
+
+ private boolean hasLeadingVerticalGridLine(TableColumnModel cm, int column) {
+ if (column != 0) {
+ return false;
+ }
+
+ if (drawLeadingVerticalLine != null) {
+ return drawLeadingVerticalLine;
+ }
+
+ Container parent = this.table.getParent();
+ if (parent instanceof JViewport) {
+ Container grand = parent.getParent();
+ if (grand instanceof JScrollPane) {
+ return (((JScrollPane) grand).getRowHeader() != null);
+ }
+ }
+ return false;
+ }
+
+ private int viewIndexForColumn(TableColumn aColumn) {
+ TableColumnModel cm = this.table.getColumnModel();
+ for (int column = 0; column < cm.getColumnCount(); column++) {
+ if (cm.getColumn(column) == aColumn) {
+ return column;
+ }
+ }
+ return -1;
+ }
+
+ protected void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) {
+ JTableHeader header = this.table.getTableHeader();
+ TableColumn draggedColumn = (header == null) ? null : header
+ .getDraggedColumn();
+
+ TableColumnModel cm = this.table.getColumnModel();
+ int columnMargin = cm.getColumnMargin();
+ int rowMargin = this.table.getRowMargin();
+
+ Rectangle cellRect;
+ Rectangle highlightCellRect;
+ TableColumn aColumn;
+ int columnWidth;
+ if (this.table.getComponentOrientation().isLeftToRight()) {
+ for (int row = rMin; row <= rMax; row++) {
+ cellRect = this.table.getCellRect(row, cMin, false);
+
+ highlightCellRect = new Rectangle(cellRect);
+ highlightCellRect.y -= rowMargin / 2;
+ highlightCellRect.height += rowMargin;
+
+ for (int column = cMin; column <= cMax; column++) {
+ aColumn = cm.getColumn(column);
+ columnWidth = aColumn.getWidth();
+
+ cellRect.width = columnWidth - columnMargin;
+ highlightCellRect.x = cellRect.x - columnMargin / 2;
+ highlightCellRect.width = columnWidth;
+ //if (!hasTrailingVerticalGridLine(cm, column)) {
+ // cellRect.width++;
+ // highlightCellRect.width++;
+ //}
+
+ if (aColumn != draggedColumn) {
+ this.paintCell(g, cellRect, highlightCellRect, row,
+ column);
+ }
+ cellRect.x += columnWidth;
+ }
+ }
+ } else {
+ for (int row = rMin; row <= rMax; row++) {
+ cellRect = this.table.getCellRect(row, cMin, false);
+ highlightCellRect = new Rectangle(cellRect);
+ highlightCellRect.y -= rowMargin / 2;
+ highlightCellRect.height += rowMargin;
+
+ for (int column = cMin; column <= cMax; column++) {
+ aColumn = cm.getColumn(column);
+ columnWidth = aColumn.getWidth();
+ cellRect.width = columnWidth - columnMargin;
+
+ highlightCellRect.x = cellRect.x - columnMargin / 2;
+ highlightCellRect.width = columnWidth;
+ if (aColumn != draggedColumn) {
+ this.paintCell(g, cellRect, highlightCellRect, row,
+ column);
+ }
+ cellRect.x -= columnWidth;
+ }
+ }
+ }
+
+ // Paint the dragged column if we are dragging.
+ if (draggedColumn != null) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ // enhancement 331 - translucent dragged column
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.table,
+ 0.65f, g));
+ this.paintDraggedArea(g2d, rMin, rMax, draggedColumn,
+ header.getDraggedDistance());
+ g2d.dispose();
+ }
+
+ // Remove any renderers that may be left in the rendererPane.
+ this.rendererPane.removeAll();
+ }
+
+ protected void paintDraggedArea(Graphics g, int rMin, int rMax,
+ TableColumn draggedColumn, int distance) {
+ int draggedColumnIndex = this.viewIndexForColumn(draggedColumn);
+
+ Rectangle minCell = this.table.getCellRect(rMin, draggedColumnIndex,
+ true);
+ Rectangle maxCell = this.table.getCellRect(rMax, draggedColumnIndex,
+ true);
+
+ Rectangle vacatedColumnRect = minCell.union(maxCell);
+
+ // Paint a gray well in place of the moving column.
+ g.setColor(this.table.getParent().getBackground());
+ g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
+ vacatedColumnRect.width, vacatedColumnRect.height);
+
+ // Move to the where the cell has been dragged.
+ vacatedColumnRect.x += distance;
+
+ // Fill the background.
+ g.setColor(this.table.getBackground());
+ g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
+ vacatedColumnRect.width, vacatedColumnRect.height);
+
+ // Paint the vertical grid lines if necessary.
+ if (this.table.getShowVerticalLines()) {
+ g.setColor(this.table.getGridColor());
+ int x1 = vacatedColumnRect.x;
+ int y1 = vacatedColumnRect.y;
+ int x2 = x1 + vacatedColumnRect.width - 1;
+ int y2 = y1 + vacatedColumnRect.height - 1;
+ // Left
+ g.drawLine(x1 - 1, y1, x1 - 1, y2);
+ // Right
+ g.drawLine(x2, y1, x2, y2);
+ }
+
+ for (int row = rMin; row <= rMax; row++) {
+ // Render the cell value
+ Rectangle r = this.table
+ .getCellRect(row, draggedColumnIndex, false);
+ r.x += distance;
+ this.paintCell(g, r, r, row, draggedColumnIndex);
+
+ // Paint the (lower) horizontal grid line if necessary.
+ if (this.table.getShowHorizontalLines()) {
+ g.setColor(this.table.getGridColor());
+ Rectangle rcr = this.table.getCellRect(row, draggedColumnIndex,
+ true);
+ rcr.x += distance;
+ int x1 = rcr.x;
+ int y1 = rcr.y;
+ int x2 = x1 + rcr.width - 1;
+ int y2 = y1 + rcr.height - 1;
+ g.drawLine(x1, y2, x2, y2);
+ }
+ }
+ }
+
+ protected void paintCell(Graphics g, Rectangle cellRect,
+ Rectangle highlightCellRect, int row, int column) {
+ // System.out.println("Painting " + row + ":" + column);
+ Component rendererComponent = null;
+ if (!this.table.isEditing() || this.table.getEditingRow() != row
+ || this.table.getEditingColumn() != column) {
+ TableCellRenderer renderer = this.table
+ .getCellRenderer(row, column);
+ boolean isSubstanceRenderer = isSubstanceDefaultRenderer(renderer);
+ rendererComponent = this.table.prepareRenderer(renderer, row,
+ column);
+ boolean isSubstanceRendererComponent = isSubstanceDefaultRenderer(rendererComponent);
+ if (isSubstanceRenderer && !isSubstanceRendererComponent) {
+ if (!Boolean.getBoolean("insubstantial.looseTableCellRenderers")) {
+ throw new IllegalArgumentException(
+ "Renderer extends the SubstanceDefaultTableCellRenderer but does not return one in its getTableCellRendererComponent() method");
+ }
+ }
+
+ if (!isSubstanceRenderer) {
+ rendererPane.paintComponent(g, rendererComponent, table,
+ cellRect.x, cellRect.y, cellRect.width,
+ cellRect.height, true);
+ return;
+ }
+ }
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ // fix for issue 183 - passing the original Graphics context
+ // to compute the alpha composite. If the table is in a JXPanel
+ // (component from SwingX) and it has custom alpha value set,
+ // then the original graphics context will have a SRC_OVER
+ // alpha composite applied to it.
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.table, g));
+
+ TableCellId cellId = new TableCellId(row, column);
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = this
+ .getModelStateInfo(cellId);
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ((modelStateInfo == null) ? null
+ : modelStateInfo.getStateContributionMap());
+ // optimize for tables that don't initiate rollover
+ // or selection animations
+ if (!updateInfo.hasRolloverAnimations
+ && !updateInfo.hasSelectionAnimations)
+ activeStates = null;
+ ComponentState currState = ((modelStateInfo == null) ? this
+ .getCellState(cellId) : modelStateInfo.getCurrModelState());
+
+ boolean hasHighlights = (currState != ComponentState.ENABLED)
+ || (activeStates != null);
+ if (activeStates != null) {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
+ .entrySet()) {
+ hasHighlights = (this.updateInfo.getHighlightAlpha(stateEntry
+ .getKey()) * stateEntry.getValue().getContribution() > 0.0f);
+ if (hasHighlights)
+ break;
+ }
+ } else {
+ hasHighlights = (this.updateInfo.getHighlightAlpha(currState) > 0.0f);
+ }
+
+ Object highlightProperty = table.getClientProperty("substancelaf.highlightCells");
+ hasHighlights = (highlightProperty instanceof Boolean) ? (Boolean)highlightProperty && hasHighlights : hasHighlights;
+
+ Set<SubstanceConstants.Side> highlightOpenSides = null;
+ float highlightBorderAlpha = 0.0f;
+
+ if (hasHighlights) {
+ // compute the highlight visuals, but only if there are
+ // highlights on this cell (optimization)
+ highlightOpenSides = EnumSet.noneOf(Side.class);
+ // show highlight border only when the table grid is not shown
+ highlightBorderAlpha = (table.getShowHorizontalLines() || table
+ .getShowVerticalLines()) ? 0.0f : 0.8f;
+ if (!table.getColumnSelectionAllowed()
+ && table.getRowSelectionAllowed()) {
+ // if row selection is on and column selection is off, we
+ // will show the highlight for the entire row
+
+ // all cells have open left side
+ highlightOpenSides.add(SubstanceConstants.Side.LEFT);
+ // all cells have open right side
+ highlightOpenSides.add(SubstanceConstants.Side.RIGHT);
+ }
+ if (table.getColumnSelectionAllowed()
+ && !table.getRowSelectionAllowed()) {
+ // if row selection is off and column selection is on, we
+ // will show the highlight for the entire column
+
+ // the top side is open for all rows except the
+ // first, or when the table header is visible
+ highlightOpenSides.add(SubstanceConstants.Side.TOP);
+ // all cells but the last have open bottom side
+ highlightOpenSides.add(SubstanceConstants.Side.BOTTOM);
+ }
+ if (row > 1) {
+ ComponentState upperNeighbourState = this
+ .getCellState(new TableCellId(row - 1, column));
+ if (currState == upperNeighbourState) {
+ // the cell above it is in the same state
+ highlightOpenSides.add(SubstanceConstants.Side.TOP);
+ }
+ }
+ if (column > 1) {
+ ComponentState leftNeighbourState = this
+ .getCellState(new TableCellId(row, column - 1));
+ if (currState == leftNeighbourState) {
+ // the cell to the left is in the same state
+ highlightOpenSides.add(SubstanceConstants.Side.LEFT);
+ }
+ }
+ if (row == 0) {
+ highlightOpenSides.add(SubstanceConstants.Side.TOP);
+ }
+ if (row == (table.getRowCount() - 1)) {
+ highlightOpenSides.add(SubstanceConstants.Side.BOTTOM);
+ }
+ if (column == 0) {
+ highlightOpenSides.add(SubstanceConstants.Side.LEFT);
+ }
+ if (column == (table.getColumnCount() - 1)) {
+ highlightOpenSides.add(SubstanceConstants.Side.RIGHT);
+ }
+ }
+
+ boolean isRollover = this.rolledOverIndices.contains(cellId);
+ if (this.table.isEditing() && this.table.getEditingRow() == row
+ && this.table.getEditingColumn() == column) {
+ Component component = this.table.getEditorComponent();
+ component.applyComponentOrientation(this.table
+ .getComponentOrientation());
+
+ if (hasHighlights) {
+ float extra = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.table
+ .getTableHeader()));
+ float extraWidth = highlightOpenSides
+ .contains(SubstanceConstants.Side.LEFT) ? 0.0f : extra;
+ float extraHeight = highlightOpenSides
+ .contains(SubstanceConstants.Side.TOP) ? 0.0f : extra;
+ Rectangle highlightRect = new Rectangle(highlightCellRect.x
+ - (int) extraWidth, highlightCellRect.y
+ - (int) extraHeight, highlightCellRect.width
+ + (int) extraWidth, highlightCellRect.height
+ + (int) extraHeight);
+ if (activeStates == null) {
+ float alpha = this.updateInfo.getHighlightAlpha(currState);
+ if (alpha > 0.0f) {
+ SubstanceColorScheme fillScheme = this.updateInfo
+ .getHighlightColorScheme(currState);
+ SubstanceColorScheme borderScheme = this.updateInfo
+ .getHighlightBorderColorScheme(currState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.table, alpha, g));
+ HighlightPainterUtils.paintHighlight(g2d,
+ this.rendererPane, component, highlightRect,
+ highlightBorderAlpha, highlightOpenSides,
+ fillScheme, borderScheme);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.table, g));
+ }
+ } else {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = stateEntry.getKey();
+ float alpha = this.updateInfo
+ .getHighlightAlpha(activeState)
+ * stateEntry.getValue().getContribution();
+ if (alpha == 0.0f)
+ continue;
+ SubstanceColorScheme fillScheme = this.updateInfo
+ .getHighlightColorScheme(activeState);
+ SubstanceColorScheme borderScheme = this.updateInfo
+ .getHighlightBorderColorScheme(activeState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.table, alpha, g));
+ HighlightPainterUtils.paintHighlight(g2d,
+ this.rendererPane, component, highlightRect,
+ highlightBorderAlpha, highlightOpenSides,
+ fillScheme, borderScheme);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.table, g));
+ }
+ }
+ }
+
+ component.setBounds(cellRect);
+ component.validate();
+ } else {
+ boolean isWatermarkBleed = this.updateInfo.toDrawWatermark;
+ if (rendererComponent != null) {
+ if (!isWatermarkBleed) {
+ Color background = rendererComponent.getBackground();
+ // optimization - only render background if it's different
+ // from the table background
+ if ((background != null)
+ && (!table.getBackground().equals(background) || this.updateInfo.isInDecorationArea)) {
+ // fill with the renderer background color
+ g2d.setColor(background);
+ g2d.fillRect(highlightCellRect.x, highlightCellRect.y,
+ highlightCellRect.width,
+ highlightCellRect.height);
+ }
+ } else {
+ BackgroundPaintingUtils.fillAndWatermark(g2d, this.table,
+ rendererComponent.getBackground(),
+ highlightCellRect);
+ }
+ }
+
+ if (hasHighlights) {
+ JTable.DropLocation dropLocation = table.getDropLocation();
+ if (dropLocation != null && !dropLocation.isInsertRow()
+ && !dropLocation.isInsertColumn()
+ && dropLocation.getRow() == row
+ && dropLocation.getColumn() == column) {
+ // mark drop location
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(table,
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ currState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(table,
+ ColorSchemeAssociationKind.BORDER,
+ currState);
+ float extra = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.table
+ .getTableHeader()));
+ HighlightPainterUtils.paintHighlight(g2d,
+ this.rendererPane, rendererComponent,
+ new Rectangle(highlightCellRect.x - (int) extra,
+ highlightCellRect.y - (int) extra,
+ highlightCellRect.width + (int) extra,
+ highlightCellRect.height + (int) extra),
+ 0.8f, null, scheme, borderScheme);
+ } else {
+ float extra = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(this.table
+ .getTableHeader()));
+ float extraWidth = highlightOpenSides
+ .contains(SubstanceConstants.Side.LEFT) ? 0.0f
+ : extra;
+ float extraHeight = highlightOpenSides
+ .contains(SubstanceConstants.Side.TOP) ? 0.0f
+ : extra;
+ Rectangle highlightRect = new Rectangle(highlightCellRect.x
+ - (int) extraWidth, highlightCellRect.y
+ - (int) extraHeight, highlightCellRect.width
+ + (int) extraWidth, highlightCellRect.height
+ + (int) extraHeight);
+ if (activeStates == null) {
+ SubstanceColorScheme fillScheme = this.updateInfo
+ .getHighlightColorScheme(currState);
+ SubstanceColorScheme borderScheme = this.updateInfo
+ .getHighlightBorderColorScheme(currState);
+ float alpha = this.updateInfo
+ .getHighlightAlpha(currState);
+ if (alpha > 0.0f) {
+ g2d.setComposite(LafWidgetUtilities
+ .getAlphaComposite(this.table, alpha, g));
+ HighlightPainterUtils.paintHighlight(g2d,
+ this.rendererPane, rendererComponent,
+ highlightRect, highlightBorderAlpha,
+ highlightOpenSides, fillScheme,
+ borderScheme);
+ g2d.setComposite(LafWidgetUtilities
+ .getAlphaComposite(this.table, g));
+ }
+ } else {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = stateEntry.getKey();
+ SubstanceColorScheme fillScheme = this.updateInfo
+ .getHighlightColorScheme(activeState);
+ SubstanceColorScheme borderScheme = this.updateInfo
+ .getHighlightBorderColorScheme(activeState);
+ float alpha = this.updateInfo
+ .getHighlightAlpha(activeState)
+ * stateEntry.getValue().getContribution();
+ if (alpha > 0.0f) {
+ g2d.setComposite(LafWidgetUtilities
+ .getAlphaComposite(this.table, alpha, g));
+ HighlightPainterUtils.paintHighlight(g2d,
+ this.rendererPane, rendererComponent,
+ highlightRect, highlightBorderAlpha,
+ highlightOpenSides, fillScheme,
+ borderScheme);
+ g2d.setComposite(LafWidgetUtilities
+ .getAlphaComposite(this.table, g));
+ }
+ }
+ }
+ }
+ }
+
+ rendererComponent.applyComponentOrientation(this.table
+ .getComponentOrientation());
+ if (rendererComponent instanceof JComponent) {
+ // Play with opacity to make our own gradient background
+ // on selected elements to show.
+ JComponent jRenderer = (JComponent) rendererComponent;
+ // Compute the selection status to prevent flicker - JTable
+ // registers a listener on selection changes and repaints
+ // the relevant cell before our listener (in TableUI) gets
+ // the chance to start the fade sequence. The result is that
+ // the first frame uses full opacity, and the next frame
+ // starts the fade sequence. So, we use the UI delegate to
+ // compute the selection status.
+ boolean isSelected = updateInfo.hasSelectionAnimations ? this.selectedIndices
+ .containsKey(cellId) : this.table.isCellSelected(row,
+ column);
+ boolean newOpaque = !(isSelected || isRollover || hasHighlights);
+
+ if (this.updateInfo.toDrawWatermark)
+ newOpaque = false;
+
+ Map<Component, Boolean> opacity = new HashMap<Component, Boolean>();
+ if (!newOpaque)
+ SubstanceCoreUtilities.makeNonOpaque(jRenderer, opacity);
+ this.rendererPane.paintComponent(g2d, rendererComponent,
+ this.table, cellRect.x, cellRect.y, cellRect.width,
+ cellRect.height, true);
+ if (!newOpaque)
+ SubstanceCoreUtilities.restoreOpaque(jRenderer, opacity);
+ } else {
+ this.rendererPane.paintComponent(g2d, rendererComponent,
+ this.table, cellRect.x, cellRect.y, cellRect.width,
+ cellRect.height, true);
+ }
+ }
+ g2d.dispose();
+ }
+
+ protected void paintDropLines(Graphics g) {
+ JTable.DropLocation loc = table.getDropLocation();
+ if (loc == null) {
+ return;
+ }
+
+ Color color = UIManager.getColor("Table.dropLineColor");
+ Color shortColor = UIManager.getColor("Table.dropLineShortColor");
+ if (color == null && shortColor == null) {
+ return;
+ }
+
+ Rectangle rect;
+
+ rect = getHDropLineRect(loc);
+ if (rect != null) {
+ int x = rect.x;
+ int w = rect.width;
+ if (color != null) {
+ extendRect(rect, true);
+ g.setColor(color);
+ g.fillRect(rect.x, rect.y, rect.width, rect.height);
+ }
+ if (!loc.isInsertColumn() && shortColor != null) {
+ g.setColor(shortColor);
+ g.fillRect(x, rect.y, w, rect.height);
+ }
+ }
+
+ rect = getVDropLineRect(loc);
+ if (rect != null) {
+ int y = rect.y;
+ int h = rect.height;
+ if (color != null) {
+ extendRect(rect, false);
+ g.setColor(color);
+ g.fillRect(rect.x, rect.y, rect.width, rect.height);
+ }
+ if (!loc.isInsertRow() && shortColor != null) {
+ g.setColor(shortColor);
+ g.fillRect(rect.x, y, rect.width, h);
+ }
+ }
+ }
+
+ private Rectangle getHDropLineRect(JTable.DropLocation loc) {
+ if (!loc.isInsertRow()) {
+ return null;
+ }
+
+ int row = loc.getRow();
+ int col = loc.getColumn();
+ if (col >= table.getColumnCount()) {
+ col--;
+ }
+
+ Rectangle rect = table.getCellRect(row, col, true);
+
+ if (row >= table.getRowCount()) {
+ row--;
+ Rectangle prevRect = table.getCellRect(row, col, true);
+ rect.y = prevRect.y + prevRect.height;
+ }
+
+ if (rect.y == 0) {
+ rect.y = -1;
+ } else {
+ rect.y -= 2;
+ }
+
+ rect.height = 3;
+
+ return rect;
+ }
+
+ private Rectangle getVDropLineRect(JTable.DropLocation loc) {
+ if (!loc.isInsertColumn()) {
+ return null;
+ }
+
+ boolean ltr = table.getComponentOrientation().isLeftToRight();
+ int col = loc.getColumn();
+ Rectangle rect = table.getCellRect(loc.getRow(), col, true);
+
+ if (col >= table.getColumnCount()) {
+ col--;
+ rect = table.getCellRect(loc.getRow(), col, true);
+ if (ltr) {
+ rect.x = rect.x + rect.width;
+ }
+ } else if (!ltr) {
+ rect.x = rect.x + rect.width;
+ }
+
+ if (rect.x == 0) {
+ rect.x = -1;
+ } else {
+ rect.x -= 2;
+ }
+
+ rect.width = 3;
+
+ return rect;
+ }
+
+ private Rectangle extendRect(Rectangle rect, boolean horizontal) {
+ if (rect == null) {
+ return rect;
+ }
+
+ if (horizontal) {
+ rect.x = 0;
+ rect.width = table.getWidth();
+ } else {
+ rect.y = 0;
+
+ if (table.getRowCount() != 0) {
+ Rectangle lastRect = table.getCellRect(table.getRowCount() - 1,
+ 0, true);
+ rect.height = lastRect.y + lastRect.height;
+ } else {
+ rect.height = table.getHeight();
+ }
+ }
+
+ return rect;
+ }
+
+ /**
+ * Repaints a single cell during the fade animation cycle.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class CellRepaintCallback extends UIThreadTimelineCallbackAdapter {
+ /**
+ * Associated table.
+ */
+ protected JTable table;
+
+ /**
+ * Associated (animated) row index.
+ */
+ protected int rowIndex;
+
+ /**
+ * Associated (animated) column index.
+ */
+ protected int columnIndex;
+
+ /**
+ * Creates a new animation repaint callback.
+ *
+ * @param table
+ * Associated table.
+ * @param rowIndex
+ * Associated (animated) row index.
+ * @param columnIndex
+ * Associated (animated) column index.
+ */
+ public CellRepaintCallback(JTable table, int rowIndex, int columnIndex) {
+ super();
+ this.table = table;
+ this.rowIndex = rowIndex;
+ this.columnIndex = columnIndex;
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ this.repaintCell();
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ this.repaintCell();
+ }
+
+ /**
+ * Repaints the associated cell.
+ */
+ private void repaintCell() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (SubstanceTableUI.this.table == null) {
+ // may happen if the LAF was switched in the meantime
+ return;
+ }
+ int rowCount = table.getRowCount();
+ int colCount = table.getColumnCount();
+ if ((rowCount > 0) && (rowIndex < rowCount)
+ && (colCount > 0) && (columnIndex < colCount)) {
+ // need to retrieve the cell rectangle since the cells
+ // can be moved while animating
+ Rectangle rect = getCellRectangleForRepaint(rowIndex,
+ columnIndex);
+ // System.out.println("Cell Repainting " + rowIndex +
+ // ":"
+ // + columnIndex + ":" + rect);
+ CellRepaintCallback.this.table.repaint(rect);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Repaints a single row during the fade animation cycle.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class RowRepaintCallback extends UIThreadTimelineCallbackAdapter {
+ /**
+ * Associated table.
+ */
+ protected JTable table;
+
+ /**
+ * Associated (animated) row index.
+ */
+ protected int rowIndex;
+
+ /**
+ * Creates a new animation repaint callback.
+ *
+ * @param table
+ * Associated table.
+ * @param rowIndex
+ * Associated (animated) row index.
+ */
+ public RowRepaintCallback(JTable table, int rowIndex) {
+ super();
+ this.table = table;
+ this.rowIndex = rowIndex;
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ this.repaintRow();
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ this.repaintRow();
+ }
+
+ /**
+ * Repaints the associated row.
+ */
+ private void repaintRow() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (SubstanceTableUI.this.table == null) {
+ // may happen if the LAF was switched in the meantime
+ return;
+ }
+ int rowCount = RowRepaintCallback.this.table.getRowCount();
+ if ((rowCount > 0)
+ && (RowRepaintCallback.this.rowIndex < rowCount)) {
+ // need to retrieve the cell rectangle since the cells
+ // can be moved while animating
+ Rectangle rect = RowRepaintCallback.this.table
+ .getCellRect(RowRepaintCallback.this.rowIndex,
+ 0, true);
+ for (int i = 1; i < RowRepaintCallback.this.table
+ .getColumnCount(); i++) {
+ rect = rect.union(RowRepaintCallback.this.table
+ .getCellRect(
+ RowRepaintCallback.this.rowIndex,
+ i, true));
+ }
+ if (!table.getShowHorizontalLines()
+ && !table.getShowVerticalLines()) {
+ float extra = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(table
+ .getTableHeader()));
+ rect.y -= (int) extra;
+ rect.height += 2 * (int) extra;
+ }
+ // System.out.println("Repainting row " + rowIndex
+ // + " at " + rect);
+ RowRepaintCallback.this.table.repaint(rect);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Repaints a single column during the fade animation cycle.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class ColumnRepaintCallback extends
+ UIThreadTimelineCallbackAdapter {
+ /**
+ * Associated table.
+ */
+ protected JTable table;
+
+ /**
+ * Associated (animated) column index.
+ */
+ protected int columnIndex;
+
+ /**
+ * Creates a new animation repaint callback.
+ *
+ * @param table
+ * Associated table.
+ * @param columnIndex
+ * Associated (animated) column index.
+ */
+ public ColumnRepaintCallback(JTable table, int columnIndex) {
+ super();
+ this.table = table;
+ this.columnIndex = columnIndex;
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ this.repaintColumn();
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ this.repaintColumn();
+ }
+
+ /**
+ * Repaints the associated row.
+ */
+ private void repaintColumn() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (SubstanceTableUI.this.table == null) {
+ // may happen if the LAF was switched in the meantime
+ return;
+ }
+ int columnCount = ColumnRepaintCallback.this.table
+ .getColumnCount();
+ if ((columnCount > 0)
+ && (ColumnRepaintCallback.this.columnIndex < columnCount)) {
+ // need to retrieve the cell rectangle since the cells
+ // can be moved while animating
+ Rectangle rect = ColumnRepaintCallback.this.table
+ .getCellRect(0,
+ ColumnRepaintCallback.this.columnIndex,
+ true);
+ for (int i = 1; i < ColumnRepaintCallback.this.table
+ .getRowCount(); i++) {
+ rect = rect.union(ColumnRepaintCallback.this.table
+ .getCellRect(
+ i,
+ ColumnRepaintCallback.this.columnIndex,
+ true));
+ }
+ if (!table.getShowHorizontalLines()
+ && !table.getShowVerticalLines()) {
+ float extra = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(table
+ .getTableHeader()));
+ rect.x -= (int) extra;
+ rect.width += 2 * (int) extra;
+ }
+ ColumnRepaintCallback.this.table.repaint(rect);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * ID of a single table cell.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class TableCellId implements Comparable<TableCellId> {
+ /**
+ * Cell row.
+ */
+ protected int row;
+
+ /**
+ * Cell column.
+ */
+ protected int column;
+
+ /**
+ * Creates a new cell ID.
+ *
+ * @param row
+ * Cell row.
+ * @param column
+ * Cell column.
+ */
+ public TableCellId(int row, int column) {
+ this.row = row;
+ this.column = column;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ @Override
+ public int compareTo(TableCellId o) {
+ if (row == o.row) {
+ return (column < o.column) ? -1 : ((column == o.column) ? 0 : 1);
+ } else {
+ return (row < o.row) ? -1 : ((row == o.row) ? 0 : 1);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof TableCellId) {
+ return this.compareTo((TableCellId) obj) == 0;
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return (this.row ^ (this.row << 16))
+ & (this.column ^ (this.column << 16));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "Row " + this.row + ", Column " + this.column;
+ }
+ }
+
+ /**
+ * State listener for tracking the selection changes.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class TableStateListener implements ListSelectionListener,
+ TableModelListener, RowSorterListener {
+ List<SortKey> oldSortKeys = null;
+
+ private boolean isSameSorter(List<? extends SortKey> sortKeys1,
+ List<? extends SortKey> sortKeys2) {
+ int size1 = (sortKeys1 == null) ? 0 : sortKeys1.size();
+ int size2 = (sortKeys2 == null) ? 0 : sortKeys2.size();
+ if ((size1 == 0) && (size2 == 0)) {
+ return true;
+ }
+ if ((sortKeys1 == null) && (sortKeys2 == null))
+ return true;
+ if ((sortKeys1 == null) || (sortKeys2 == null))
+ return false;
+ if (size1 != size2)
+ return false;
+ for (int i = 0; i < size1; i++) {
+ SortKey sortKey1 = sortKeys1.get(i);
+ SortKey sortKey2 = sortKeys2.get(i);
+ if ((sortKey1.getColumn() != sortKey2.getColumn())
+ || (sortKey1.getSortOrder() != sortKey2.getSortOrder())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.event.ListSelectionListener#valueChanged(javax.swing.
+ * event.ListSelectionEvent)
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public void valueChanged(final ListSelectionEvent e) {
+ // fix for issue 478 - no animations when sorter has changed
+ List<? extends SortKey> sortKeys = (table.getRowSorter() == null) ? null
+ : table.getRowSorter().getSortKeys();
+ boolean isDifferentSorter = !isSameSorter(sortKeys, oldSortKeys);
+ if (e.getValueIsAdjusting() && isDifferentSorter)
+ return;
+ if (sortKeys == null) {
+ oldSortKeys = null;
+ } else {
+ oldSortKeys = new ArrayList<SortKey>();
+ for (SortKey sortKey : sortKeys) {
+ SortKey copy = new SortKey(sortKey.getColumn(),
+ sortKey.getSortOrder());
+ oldSortKeys.add(copy);
+ }
+ }
+ syncSelection(isDifferentSorter);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.event.TableModelListener#tableChanged(javax.swing.event
+ * .TableModelEvent)
+ */
+ @Override
+ public void tableChanged(final TableModelEvent e) {
+ // fix for defect 291 - tracking changes to the table.
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // fix for defect 350 - font might have been
+ // switched in the middle of update
+ if (table == null)
+ return;
+
+ // fix for defect 328 - do not clear the
+ // internal selection and focus tracking
+ // when the event is table update.
+ if (e.getType() != TableModelEvent.UPDATE) {
+ selectedIndices.clear();
+ stateTransitionMultiTracker.clear();
+ focusedCellId = null;
+ }
+ syncSelection(true);
+ table.repaint();
+ }
+ });
+ }
+
+ @Override
+ public void sorterChanged(RowSorterEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // fix for issue 479 - cancel selection animations
+ // that are happening due to changes in sorter
+ stateTransitionMultiTracker.clear();
+ }
+ });
+ }
+ }
+
+ /**
+ * Listener for fade animations on table rollovers.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class RolloverFadeListener implements MouseListener,
+ MouseMotionListener {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ // if (SubstanceCoreUtilities.toBleedWatermark(list))
+ // return;
+
+ if (table == null)
+ return;
+
+ if (!table.isEnabled())
+ return;
+
+ // check the mouse location. If the cell editor has been shown
+ // or hidden, we will get a mouseExited() event, but shouldn't
+ // be changing the rollover indication if the mouse is still
+ // over the table
+ PointerInfo pi = MouseInfo.getPointerInfo();
+ Point mouseLoc = pi == null ? null : pi.getLocation();
+ Window windowAncestor = SwingUtilities.getWindowAncestor(table);
+ if ((mouseLoc != null) && (windowAncestor != null)) {
+ SwingUtilities.convertPointFromScreen(mouseLoc, windowAncestor);
+ Component deepest = SwingUtilities.getDeepestComponentAt(
+ windowAncestor, mouseLoc.x, mouseLoc.y);
+
+ while (deepest != null) {
+ if (deepest == table) {
+ // still in table
+ return;
+ }
+ deepest = deepest.getParent();
+ }
+ }
+
+ fadeOutAllRollovers();
+ this.fadeOutTableHeader();
+ rolledOverIndices.clear();
+ rolledOverColumn = -1;
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ if (!SubstanceTableUI.this.table.isEnabled())
+ return;
+ handleMouseMove(e.getPoint());
+ this.handleMoveForHeader(e);
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ if (!SubstanceTableUI.this.table.isEnabled())
+ return;
+ handleMouseMove(e.getPoint());
+ this.handleMoveForHeader(e);
+ }
+
+ /**
+ * Handles various mouse move events and initiates the fade animation if
+ * necessary.
+ *
+ * @param e
+ * Mouse event.
+ */
+ private void handleMoveForHeader(MouseEvent e) {
+ if (!SubstanceTableUI.this.table.getColumnSelectionAllowed())
+ return;
+ JTableHeader header = SubstanceTableUI.this.table.getTableHeader();
+ if ((header == null) || (!header.isVisible()))
+ return;
+
+ TableHeaderUI ui = header.getUI();
+ if (!(ui instanceof SubstanceTableHeaderUI))
+ return;
+
+ SubstanceTableHeaderUI sthui = (SubstanceTableHeaderUI) ui;
+
+ // synchronized (SubstanceTableUI.this.table) {
+ int row = SubstanceTableUI.this.table.rowAtPoint(e.getPoint());
+ int column = SubstanceTableUI.this.table
+ .columnAtPoint(e.getPoint());
+ if ((row < 0) || (row >= SubstanceTableUI.this.table.getRowCount())
+ || (column < 0)
+ || (column >= SubstanceTableUI.this.table.getColumnCount())) {
+ this.fadeOutTableHeader();
+ // System.out.println("Nulling RO column index");
+ SubstanceTableUI.this.rolledOverColumn = -1;
+ } else {
+ // check if this is the same column
+ if (SubstanceTableUI.this.rolledOverColumn == column)
+ return;
+
+ this.fadeOutTableHeader();
+
+ TableColumnModel columnModel = header.getColumnModel();
+ StateTransitionTracker columnTransitionTracker = sthui
+ .getTracker(column, false,
+ columnModel.getColumnSelectionAllowed()
+ && columnModel.getSelectionModel()
+ .isSelectedIndex(column));
+ columnTransitionTracker.getModel().setRollover(true);
+
+ SubstanceTableUI.this.rolledOverColumn = column;
+ }
+ // }
+ }
+
+ /**
+ * Initiates the fade out effect.
+ */
+ private void fadeOutTableHeader() {
+ if (SubstanceTableUI.this.rolledOverColumn >= 0) {
+ JTableHeader header = SubstanceTableUI.this.table
+ .getTableHeader();
+ if ((header == null) || (!header.isVisible()))
+ return;
+ SubstanceTableHeaderUI ui = (SubstanceTableHeaderUI) header
+ .getUI();
+
+ TableColumnModel columnModel = header.getColumnModel();
+ StateTransitionTracker columnTransitionTracker = ui.getTracker(
+ rolledOverColumn, true,
+ columnModel.getColumnSelectionAllowed()
+ && columnModel.getSelectionModel()
+ .isSelectedIndex(rolledOverColumn));
+ columnTransitionTracker.getModel().setRollover(false);
+ }
+ }
+
+ /**
+ * Handles various mouse move events and initiates the fade animation if
+ * necessary.
+ *
+ * @param mousePoint
+ * Mouse point.
+ */
+ private void handleMouseMove(Point mousePoint) {
+ // synchronized (SubstanceTableUI.this.table) {
+ int row = table.rowAtPoint(mousePoint);
+ int column = table.columnAtPoint(mousePoint);
+ if ((row < 0) || (row >= table.getRowCount()) || (column < 0)
+ || (column >= table.getColumnCount())) {
+ this.fadeOutAllRollovers();
+ // System.out.println("Nulling RO index in handleMove()");
+ // table.putClientProperty(ROLLED_OVER_INDEX, null);
+ rolledOverIndices.clear();
+ } else {
+ // check if this is the same index
+ boolean hasRowSelection = table.getRowSelectionAllowed();
+ boolean hasColumnSelection = table.getColumnSelectionAllowed();
+ int startRolloverRow = row;
+ int endRolloverRow = row;
+ int startRolloverColumn = column;
+ int endRolloverColumn = column;
+ if (hasRowSelection && !hasColumnSelection) {
+ startRolloverColumn = 0;
+ endRolloverColumn = table.getColumnCount() - 1;
+ }
+ if (!hasRowSelection && hasColumnSelection) {
+ startRolloverRow = 0;
+ endRolloverRow = table.getRowCount() - 1;
+ }
+ Set<TableCellId> toRemove = new HashSet<TableCellId>();
+ for (TableCellId currRolloverId : rolledOverIndices) {
+ if ((currRolloverId.row < startRolloverRow)
+ || (currRolloverId.row > endRolloverRow)
+ || (currRolloverId.column < startRolloverColumn)
+ || (currRolloverId.column > endRolloverColumn)) {
+ fadeOutRollover(currRolloverId);
+ toRemove.add(currRolloverId);
+ }
+ }
+ for (TableCellId id : toRemove) {
+ rolledOverIndices.remove(id);
+ }
+
+ int totalRolloverCount = (endRolloverRow - startRolloverRow + 1)
+ * (endRolloverColumn - startRolloverColumn + 1);
+ if (totalRolloverCount > 20) {
+ for (int i = startRolloverRow; i <= endRolloverRow; i++) {
+ for (int j = startRolloverColumn; j <= endRolloverColumn; j++) {
+ rolledOverIndices.add(new TableCellId(i, j));
+ }
+ }
+ table.repaint();
+ } else {
+ for (int i = startRolloverRow; i <= endRolloverRow; i++) {
+ for (int j = startRolloverColumn; j <= endRolloverColumn; j++) {
+ TableCellId currCellId = new TableCellId(i, j);
+ if (rolledOverIndices.contains(currCellId))
+ continue;
+ // System.out
+ // .println("Getting rollover/in tracker for "
+ // + currCellId);
+ StateTransitionTracker tracker = getTracker(
+ currCellId,
+ false,
+ getCellState(currCellId).isFacetActive(
+ ComponentStateFacet.SELECTION));
+ tracker.getModel().setRollover(true);
+
+ rolledOverIndices.add(currCellId);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Initiates the fade out effect.
+ */
+ private void fadeOutRollover(TableCellId tableCellId) {
+ if (rolledOverIndices.contains(tableCellId)) {
+ // System.out
+ // .println("Getting rollover/out tracker for " + cellId);
+ StateTransitionTracker tracker = getTracker(
+ tableCellId,
+ true,
+ getCellState(tableCellId).isFacetActive(
+ ComponentStateFacet.SELECTION));
+ tracker.getModel().setRollover(false);
+ }
+ }
+
+ private void fadeOutAllRollovers() {
+ if (rolledOverIndices.size() < 20) {
+ for (TableCellId tcid : rolledOverIndices) {
+ fadeOutRollover(tcid);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a comparable ID for the specified location.
+ *
+ * @param row
+ * Row index.
+ * @param column
+ * Column index.
+ * @return Comparable ID for the specified location.
+ */
+ public TableCellId getId(int row, int column) {
+ cellId.column = column;
+ cellId.row = row;
+ return cellId;
+ }
+
+ TableCellId cellId;
+
+ /**
+ * Synchronizes the current selection state.
+ *
+ * @param enforceNoAnimations
+ * Whether to force no animations.
+ */
+ // @SuppressWarnings("unchecked")
+ protected void syncSelection(boolean enforceNoAnimations) {
+ if (this.table == null) {
+ // fix for defect 270 - if the UI delegate is updated
+ // by another selection listener, ignore this
+ return;
+ }
+
+ int rows = this.table.getRowCount();
+ int cols = this.table.getColumnCount();
+
+ int rowLeadIndex = this.table.getSelectionModel()
+ .getLeadSelectionIndex();
+ int colLeadIndex = this.table.getColumnModel().getSelectionModel()
+ .getLeadSelectionIndex();
+ boolean isFocusOwner = this.table.isFocusOwner();
+
+ // fix for defect 209 - selection very slow on large tables with
+ // column selection set to true and row selection set to false.
+ // Solution - no selection animations on tables with more than 1000
+ // cells.
+ if (!this._hasSelectionAnimations()) {
+ stateTransitionMultiTracker.clear();
+ // this.prevStateMap.clear();
+ table.repaint();
+
+ // fix for issue 414 - track focus on tables
+ // without selection animations
+ if (isFocusOwner) {
+ this.focusedCellId = new TableCellId(rowLeadIndex, colLeadIndex);
+ }
+ return;
+ }
+
+ Set<StateTransitionTracker> initiatedTrackers = new HashSet<StateTransitionTracker>();
+
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cols; j++) {
+ TableCellId cellId = new TableCellId(i, j);
+ if (this.table.isCellSelected(i, j)) {
+ // check if was selected before
+ if (!this.selectedIndices.containsKey(cellId)) {
+ // start fading in
+ if (!enforceNoAnimations) {
+ // System.out
+ // .println("Getting selection/in tracker for "
+ // + cellId);
+ StateTransitionTracker tracker = getTracker(
+ cellId,
+ getCellState(cellId).isFacetActive(
+ ComponentStateFacet.ROLLOVER),
+ false);
+ tracker.getModel().setSelected(true);
+ // System.out
+ // .println("Selecting previously unselected "
+ // + i + ":" + j);
+ initiatedTrackers.add(tracker);
+ if (initiatedTrackers.size() > 20) {
+ stateTransitionMultiTracker.clear();
+ initiatedTrackers.clear();
+ enforceNoAnimations = true;
+ }
+ }
+
+ this.selectedIndices.put(cellId,
+ this.table.getValueAt(i, j));
+ }
+ } else {
+ // check if was selected before and still points
+ // to the same element
+ if (this.selectedIndices.containsKey(cellId)) {
+ // corner case when the model returns null
+ Object oldValue = this.selectedIndices.get(cellId);
+ if ((i >= this.table.getModel().getRowCount())
+ || (j >= this.table.getModel().getColumnCount())) {
+ // not only the content changed, but the model
+ // dimensions as well
+ continue;
+ }
+ Object currValue = this.table.getValueAt(i, j);
+ boolean isSame;
+ if (oldValue == null) {
+ isSame = (currValue == null);
+ } else {
+ isSame = oldValue.equals(currValue);
+ }
+ if (isSame) {
+ // start fading out
+ if (!enforceNoAnimations) {
+ // System.out
+ // .println("Getting selection/out tracker for "
+ // + cellId);
+ StateTransitionTracker tracker = getTracker(
+ cellId,
+ getCellState(cellId).isFacetActive(
+ ComponentStateFacet.ROLLOVER),
+ true);
+ tracker.getModel().setSelected(false);
+ // System.out
+ // .println("Unselecting previously selected "
+ // + i + ":" + j);
+
+ initiatedTrackers.add(tracker);
+ if (initiatedTrackers.size() > 20) {
+ stateTransitionMultiTracker.clear();
+ initiatedTrackers.clear();
+ enforceNoAnimations = true;
+ }
+ }
+ }
+ this.selectedIndices.remove(cellId);
+ }
+ }
+
+ // handle focus animations
+ boolean cellHasFocus = isFocusOwner && (i == rowLeadIndex)
+ && (j == colLeadIndex);
+ if (cellHasFocus) {
+ // check if it's a different cell
+ if ((this.focusedCellId == null)
+ || !this.focusedCellId.equals(cellId)) {
+ if (!enforceNoAnimations) {
+ if (this.focusedCellId != null) {
+ // fade out the previous focus holder
+
+ ComponentState cellState = getCellState(this.focusedCellId);
+ // System.out.println("Getting focus/out tracker for "
+ // + cellId);
+ StateTransitionTracker tracker = getTracker(
+ this.focusedCellId,
+ cellState
+ .isFacetActive(ComponentStateFacet.ROLLOVER),
+ cellState
+ .isFacetActive(ComponentStateFacet.SELECTION));
+ tracker.setFocusState(false);
+ }
+
+ // fade in the current cell (new focus holder)
+ ComponentState cellState = getCellState(cellId);
+
+ // System.out.println("Getting focus/in tracker for "
+ // + currId);
+ StateTransitionTracker tracker = getTracker(
+ cellId,
+ cellState
+ .isFacetActive(ComponentStateFacet.ROLLOVER),
+ cellState
+ .isFacetActive(ComponentStateFacet.SELECTION));
+ tracker.setFocusState(true);
+ }
+
+ if (AnimationConfigurationManager.getInstance()
+ .isAnimationAllowed(AnimationFacet.FOCUS,
+ this.table)) {
+ // and store it for future checks
+ this.focusedCellId = new TableCellId(i, j);
+ }
+ }
+ } else {
+ // check if previously it held focus
+ if (cellId.equals(this.focusedCellId)) {
+ if (!enforceNoAnimations) {
+ // fade it out
+ ComponentState cellState = getCellState(cellId);
+ // System.out.println("Getting focus/out tracker for "
+ // + cellId);
+ StateTransitionTracker tracker = getTracker(
+ cellId,
+ cellState
+ .isFacetActive(ComponentStateFacet.ROLLOVER),
+ cellState
+ .isFacetActive(ComponentStateFacet.SELECTION));
+ tracker.setFocusState(false);
+ }
+
+ this.focusedCellId = null;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the current state for the specified cell.
+ *
+ * @param cellIndex
+ * Cell index.
+ * @return The current state for the specified cell.
+ */
+ public ComponentState getCellState(TableCellId cellIndex) {
+ boolean isEnabled = this.table.isEnabled();
+
+ StateTransitionTracker tracker = this.stateTransitionMultiTracker
+ .getTracker(cellIndex);
+ if (tracker == null) {
+ int row = cellIndex.row;
+ int column = cellIndex.column;
+ TableCellId cellId = this.getId(row, column);
+ boolean isRollover = rolledOverIndices.contains(cellId);
+ boolean isSelected;
+ boolean hasSelectionAnimations = (this.updateInfo != null) ? this.updateInfo.hasSelectionAnimations
+ : this._hasSelectionAnimations();
+ if (hasSelectionAnimations
+ && AnimationConfigurationManager
+ .getInstance()
+ .isAnimationAllowed(AnimationFacet.SELECTION, table))
+ isSelected = this.selectedIndices.containsKey(cellId);
+ else {
+ isSelected = this.table.isCellSelected(row, column);
+ }
+ return ComponentState.getState(isEnabled, isRollover, isSelected);
+ } else {
+ ComponentState fromTracker = tracker.getModelStateInfo()
+ .getCurrModelState();
+ return ComponentState.getState(isEnabled,
+ fromTracker.isFacetActive(ComponentStateFacet.ROLLOVER),
+ fromTracker.isFacetActive(ComponentStateFacet.SELECTION));
+ }
+ }
+
+ /**
+ * Returns the current state for the specified cell.
+ *
+ * @param cellId
+ * Cell index.
+ * @return The current state for the specified cell.
+ */
+ public StateTransitionTracker.ModelStateInfo getModelStateInfo(
+ TableCellId cellId) {
+ if (this.stateTransitionMultiTracker.size() == 0)
+ return null;
+ StateTransitionTracker tracker = this.stateTransitionMultiTracker
+ .getTracker(cellId);
+ if (tracker == null) {
+ return null;
+ } else {
+ return tracker.getModelStateInfo();
+ }
+ }
+
+ /**
+ * Checks whether the table has animations.
+ *
+ * @return <code>true</code> if the table has animations, <code>false</code>
+ * otherwise.
+ */
+ protected boolean _hasAnimations() {
+ // fix for defects 164 and 209 - selection
+ // and deletion are very slow on large tables.
+ int rowCount = this.table.getRowCount();
+ int colCount = this.table.getColumnCount();
+ if (rowCount * colCount >= 500)
+ return false;
+ if (this.table.getColumnSelectionAllowed()
+ && !this.table.getRowSelectionAllowed()) {
+ if (!this.table.getShowHorizontalLines()
+ && !this.table.getShowVerticalLines())
+ return rowCount <= 10;
+ return rowCount <= 25;
+ }
+ if (!this.table.getColumnSelectionAllowed()
+ && this.table.getRowSelectionAllowed()) {
+ if (!this.table.getShowHorizontalLines()
+ && !this.table.getShowVerticalLines())
+ return colCount <= 10;
+ return colCount <= 25;
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether the table has selection animations.
+ *
+ * @return <code>true</code> if the table has selection animations,
+ * <code>false</code> otherwise.
+ */
+ protected boolean _hasSelectionAnimations() {
+ return this._hasAnimations()
+ && !LafWidgetUtilities.hasNoAnimations(this.table,
+ AnimationFacet.SELECTION);
+ }
+
+ /**
+ * Checks whether the table has rollover animations.
+ *
+ * @return <code>true</code> if the table has rollover animations,
+ * <code>false</code> otherwise.
+ */
+ protected boolean _hasRolloverAnimations() {
+ return this._hasAnimations()
+ && !LafWidgetUtilities.hasNoAnimations(this.table,
+ AnimationFacet.ROLLOVER);
+ }
+
+ /**
+ * Returns the index of the rollover column.
+ *
+ * @return The index of the rollover column.
+ */
+ public int getRolloverColumnIndex() {
+ return this.rolledOverColumn;
+ }
+
+ /**
+ * Returns indication whether the specified cell has focus.
+ *
+ * @param row
+ * Cell row index.
+ * @param column
+ * Cell column index.
+ * @return <code>true</code> If the focus is on the specified cell,
+ * <code>false</code> otherwise.
+ */
+ public boolean isFocusedCell(int row, int column) {
+ return (this.focusedCellId != null) && (this.focusedCellId.row == row)
+ && (this.focusedCellId.column == column);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ BackgroundPaintingUtils.updateIfOpaque(g, c);
+ Graphics2D g2d = (Graphics2D) g.create();
+ SubstanceStripingUtils.setup(c);
+ this.updateInfo = new TableUpdateOptimizationInfo();
+ this.paint(g2d, c);
+ SubstanceStripingUtils.tearDown(c);
+ g2d.dispose();
+ this.updateInfo = null;
+ }
+
+ /**
+ * Returns the cell renderer insets of this table. Is for internal use only.
+ *
+ * @return The cell renderer insets of this table.
+ */
+ public Insets getCellRendererInsets() {
+ return this.cellRendererInsets;
+ }
+
+ // public SubstanceColorScheme getDefaultColorScheme() {
+ // if (this.updateInfo != null)
+ // return this.updateInfo.defaultScheme;
+ // return null;
+ // }
+ //
+ // public SubstanceColorScheme getHighlightColorScheme(ComponentState state)
+ // {
+ // if (this.updateInfo != null)
+ // return updateInfo.getHighlightColorScheme(state);
+ // return null;
+ // }
+
+ public boolean hasSelectionAnimations() {
+ if (this.updateInfo != null)
+ return this.updateInfo.hasSelectionAnimations;
+ return this._hasSelectionAnimations();
+ }
+
+ public boolean hasRolloverAnimations() {
+ if (this.updateInfo != null)
+ return this.updateInfo.hasRolloverAnimations;
+ return this._hasRolloverAnimations();
+ }
+
+ private TableUpdateOptimizationInfo updateInfo;
+
+ private class TableUpdateOptimizationInfo extends UpdateOptimizationInfo {
+ public boolean hasSelectionAnimations;
+
+ public boolean hasRolloverAnimations;
+
+ public TableUpdateOptimizationInfo() {
+ super(table);
+ this.hasSelectionAnimations = _hasSelectionAnimations();
+ this.hasRolloverAnimations = _hasRolloverAnimations();
+ }
+ }
+
+ @Override
+ public UpdateOptimizationInfo getUpdateOptimizationInfo() {
+ return this.updateInfo;
+ }
+
+ private boolean isSubstanceDefaultRenderer(Object instance) {
+ return (instance instanceof SubstanceDefaultTableCellRenderer)
+ || (instance instanceof SubstanceDefaultTableCellRenderer.BooleanRenderer);
+ }
+
+ private boolean isSubstanceDefaultEditor(TableCellEditor editor) {
+ return (editor instanceof BooleanEditor);
+ }
+
+ private Rectangle getCellRectangleForRepaint(int row, int column) {
+ Rectangle rect = this.table.getCellRect(row, column, true);
+
+ if (!table.getShowHorizontalLines() && !table.getShowVerticalLines()) {
+ float extra = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(table.getTableHeader()));
+ rect.x -= (int) extra;
+ rect.width += 2 * (int) extra;
+ rect.y -= (int) extra;
+ rect.height += 2 * (int) extra;
+ }
+ return rect;
+ }
+
+ private StateTransitionTracker getTracker(final TableCellId tableCellId,
+ boolean initialRollover, boolean initialSelected) {
+ StateTransitionTracker tracker = stateTransitionMultiTracker
+ .getTracker(tableCellId);
+ // System.out.println("TableID " + tableCellId + " has tracker "
+ // + ((tracker == null) ? "null" : ("@" + tracker.hashCode())));
+ if (tracker == null) {
+ ButtonModel model = new DefaultButtonModel();
+ model.setSelected(initialSelected);
+ model.setRollover(initialRollover);
+ tracker = new StateTransitionTracker(table, model);
+ tracker.registerModelListeners();
+ tracker.setRepaintCallback(new RepaintCallback() {
+ @Override
+ public TimelineCallback getRepaintCallback() {
+ return new CellRepaintCallback(table, tableCellId.row,
+ tableCellId.column);
+ }
+ });
+ tracker.setName("row " + tableCellId.row + ", col "
+ + tableCellId.column);
+ // System.out.println("TableID " + tableCellId +
+ // " has new tracker @"
+ // + tracker.hashCode());
+ stateTransitionMultiTracker.addTracker(tableCellId, tracker);
+ }
+ return tracker;
+ }
+
+ public StateTransitionTracker getStateTransitionTracker(TableCellId tableId) {
+ return this.stateTransitionMultiTracker.getTracker(tableId);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTextAreaUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTextAreaUI.java
new file mode 100644
index 0000000..6ba3be7
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTextAreaUI.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicTextAreaUI;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * UI for text areas in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTextAreaUI extends BasicTextAreaUI implements
+ TransitionAwareUI {
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * The associated text area.
+ */
+ protected JTextArea textArea;
+
+ /**
+ * Property change listener.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Listener for transition animations.
+ */
+ private RolloverTextControlListener substanceRolloverListener;
+
+ /**
+ * Surrogate button model for tracking the state transitions.
+ */
+ private ButtonModel transitionModel;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceTextAreaUI(comp);
+ }
+
+ /**
+ * Simple constructor.
+ *
+ * @param c
+ * Component (text area).
+ */
+ public SubstanceTextAreaUI(JComponent c) {
+ super();
+ this.textArea = (JTextArea) c;
+
+ this.transitionModel = new DefaultButtonModel();
+ this.transitionModel.setArmed(false);
+ this.transitionModel.setSelected(false);
+ this.transitionModel.setPressed(false);
+ this.transitionModel.setRollover(false);
+ this.transitionModel.setEnabled(this.textArea.isEnabled());
+
+ this.stateTransitionTracker = new StateTransitionTracker(this.textArea,
+ this.transitionModel);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substanceRolloverListener = new RolloverTextControlListener(
+ this.textArea, this, this.transitionModel);
+ this.substanceRolloverListener.registerListeners();
+
+ this.stateTransitionTracker.registerModelListeners();
+ this.stateTransitionTracker.registerFocusListeners();
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // remember the caret location - issue 404
+ int caretPos = textArea.getCaretPosition();
+ textArea.updateUI();
+ textArea.setCaretPosition(caretPos);
+ Container parent = textArea.getParent();
+ if (parent != null) {
+ parent.invalidate();
+ parent.validate();
+ }
+ }
+ });
+ }
+ if ("componentOrientation".equals(evt.getPropertyName())) {
+ // fix by Davide Raccagni (A03)
+ getComponent().setText(getComponent().getText());
+ }
+
+ if ("enabled".equals(evt.getPropertyName())) {
+ transitionModel.setEnabled(textArea.isEnabled());
+ }
+ }
+ };
+ this.textArea
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.stateTransitionTracker.unregisterModelListeners();
+ this.stateTransitionTracker.unregisterFocusListeners();
+
+ this.textArea
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextAreaUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+
+ // support for per-window skins
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return;
+ if (textArea == null)
+ return;
+ Color foregr = textArea.getForeground();
+ if ((foregr == null) || (foregr instanceof UIResource)) {
+ textArea
+ .setForeground(SubstanceColorUtilities
+ .getForegroundColor(SubstanceLookAndFeel
+ .getCurrentSkin(textArea)
+ .getEnabledColorScheme(
+ SubstanceLookAndFeel
+ .getDecorationType(textArea))));
+ }
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTextUI#paintBackground(java.awt.Graphics)
+ */
+ @Override
+ protected void paintBackground(Graphics g) {
+ SubstanceTextUtilities.paintTextCompBackground(g, this.textArea);
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return true;
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTextFieldUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTextFieldUI.java
new file mode 100644
index 0000000..cd5575d
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTextFieldUI.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicBorders;
+import javax.swing.plaf.basic.BasicTextFieldUI;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+import org.pushingpixels.substance.internal.utils.border.SubstanceTextComponentBorder;
+import org.pushingpixels.trident.swing.SwingRepaintCallback;
+
+/**
+ * UI for text fields in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTextFieldUI extends BasicTextFieldUI implements
+ TransitionAwareUI {
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * The associated text field.
+ */
+ protected JTextField textField;
+
+ /**
+ * Property change listener.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Listener for transition animations.
+ */
+ private RolloverTextControlListener substanceRolloverListener;
+
+ /**
+ * Surrogate button model for tracking the state transitions.
+ */
+ private ButtonModel transitionModel;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceTextFieldUI(comp);
+ }
+
+ /**
+ * Simple constructor.
+ *
+ * @param c
+ * Component (text field).
+ */
+ public SubstanceTextFieldUI(JComponent c) {
+ super();
+ this.textField = (JTextField) c;
+
+ this.transitionModel = new DefaultButtonModel();
+ this.transitionModel.setArmed(false);
+ this.transitionModel.setSelected(false);
+ this.transitionModel.setPressed(false);
+ this.transitionModel.setRollover(false);
+ this.transitionModel.setEnabled(this.textField.isEnabled());
+
+ this.stateTransitionTracker = new StateTransitionTracker(
+ this.textField, this.transitionModel);
+ this.stateTransitionTracker
+ .setRepaintCallback(new StateTransitionTracker.RepaintCallback() {
+ @Override
+ public SwingRepaintCallback getRepaintCallback() {
+ return SubstanceCoreUtilities
+ .getTextComponentRepaintCallback(textField);
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTextUI#paintBackground(java.awt.Graphics)
+ */
+ @Override
+ protected void paintBackground(Graphics g) {
+ SubstanceTextUtilities.paintTextCompBackground(g, this.textField);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substanceRolloverListener = new RolloverTextControlListener(
+ this.textField, this, this.transitionModel);
+ this.substanceRolloverListener.registerListeners();
+
+ this.stateTransitionTracker.registerModelListeners();
+ this.stateTransitionTracker.registerFocusListeners();
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // remember the caret location - issue 404
+ int caretPos = textField.getCaretPosition();
+ textField.updateUI();
+ textField.setCaretPosition(caretPos);
+ Container parent = textField.getParent();
+ if (parent != null) {
+ parent.invalidate();
+ parent.validate();
+ }
+ }
+ });
+ }
+
+ if ("enabled".equals(evt.getPropertyName())) {
+ transitionModel.setEnabled(textField.isEnabled());
+ }
+ }
+ };
+ this.textField
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.stateTransitionTracker.unregisterModelListeners();
+ this.stateTransitionTracker.unregisterFocusListeners();
+
+ this.textField
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ this.substanceRolloverListener.unregisterListeners();
+ this.substanceRolloverListener = null;
+
+ // this.textField.removeFocusListener(this.substanceFocusListener);
+ // this.substanceFocusListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ Border b = this.textField.getBorder();
+ if (b == null || b instanceof UIResource) {
+ Border newB = new BorderUIResource.CompoundBorderUIResource(
+ new SubstanceTextComponentBorder(SubstanceSizeUtils
+ .getTextBorderInsets(SubstanceSizeUtils
+ .getComponentFontSize(this.textField))),
+ new BasicBorders.MarginBorder());
+ this.textField.setBorder(newB);
+ }
+
+ // support for per-window skins
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (textField == null)
+ return;
+ Color foregr = textField.getForeground();
+ if ((foregr == null) || (foregr instanceof UIResource)) {
+ textField
+ .setForeground(SubstanceColorUtilities
+ .getForegroundColor(SubstanceLookAndFeel
+ .getCurrentSkin(textField)
+ .getEnabledColorScheme(
+ SubstanceLookAndFeel
+ .getDecorationType(textField))));
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ return false;
+ }
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(
+ this.textField, 2.0f * SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(this.textField)), null);
+ return contour.contains(me.getPoint());
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTextPaneUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTextPaneUI.java
new file mode 100644
index 0000000..b3c0bb2
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTextPaneUI.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicTextPaneUI;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * UI for text panes in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTextPaneUI extends BasicTextPaneUI implements
+ TransitionAwareUI {
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * The associated text pane.
+ */
+ protected JTextPane textPane;
+
+ /**
+ * Property change listener.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Listener for transition animations.
+ */
+ private RolloverTextControlListener substanceRolloverListener;
+
+ /**
+ * Surrogate button model for tracking the state transitions.
+ */
+ private ButtonModel transitionModel;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceTextPaneUI(comp);
+ }
+
+ /**
+ * Simple constructor.
+ *
+ * @param c
+ * Component (text pane).
+ */
+ public SubstanceTextPaneUI(JComponent c) {
+ super();
+ this.textPane = (JTextPane) c;
+
+ this.transitionModel = new DefaultButtonModel();
+ this.transitionModel.setArmed(false);
+ this.transitionModel.setSelected(false);
+ this.transitionModel.setPressed(false);
+ this.transitionModel.setRollover(false);
+ this.transitionModel.setEnabled(this.textPane.isEnabled());
+
+ this.stateTransitionTracker = new StateTransitionTracker(this.textPane,
+ this.transitionModel);
+ }
+
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+
+ this.substanceRolloverListener = new RolloverTextControlListener(
+ this.textPane, this, this.transitionModel);
+ this.substanceRolloverListener.registerListeners();
+
+ this.stateTransitionTracker.registerModelListeners();
+ this.stateTransitionTracker.registerFocusListeners();
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ // remember the caret location - issue 404
+ int caretPos = textPane.getCaretPosition();
+ textPane.updateUI();
+ textPane.setCaretPosition(caretPos);
+ Container parent = textPane.getParent();
+ if (parent != null) {
+ parent.invalidate();
+ parent.validate();
+ }
+ }
+ });
+ }
+
+ if ("enabled".equals(evt.getPropertyName())) {
+ transitionModel.setEnabled(textPane.isEnabled());
+ }
+ }
+ };
+ this.textPane
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.stateTransitionTracker.unregisterModelListeners();
+ this.stateTransitionTracker.unregisterFocusListeners();
+
+ this.textPane
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTextUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+
+ // support for per-window skins
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (textPane == null)
+ return;
+ Color foregr = textPane.getForeground();
+ if ((foregr == null) || (foregr instanceof UIResource)) {
+ textPane
+ .setForeground(SubstanceColorUtilities
+ .getForegroundColor(SubstanceLookAndFeel
+ .getCurrentSkin(textPane)
+ .getEnabledColorScheme(
+ SubstanceLookAndFeel
+ .getDecorationType(textPane))));
+ }
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTextUI#paintBackground(java.awt.Graphics)
+ */
+ @Override
+ protected void paintBackground(Graphics g) {
+ SubstanceTextUtilities.paintTextCompBackground(g, this.textPane);
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return true;
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceToggleButtonUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceToggleButtonUI.java
new file mode 100644
index 0000000..7b28f70
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceToggleButtonUI.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.AbstractButton;
+import javax.swing.Icon;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JRadioButton;
+import javax.swing.JToggleButton;
+import javax.swing.LookAndFeel;
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicButtonListener;
+import javax.swing.plaf.basic.BasicHTML;
+import javax.swing.plaf.basic.BasicToggleButtonUI;
+import javax.swing.text.View;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.ButtonBackgroundDelegate;
+import org.pushingpixels.substance.internal.utils.ButtonVisualStateTracker;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceTextUtilities;
+import org.pushingpixels.substance.internal.utils.border.SubstanceButtonBorder;
+import org.pushingpixels.substance.internal.utils.icon.GlowingIcon;
+
+/**
+ * UI for toggle buttons in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceToggleButtonUI extends BasicToggleButtonUI implements
+ TransitionAwareUI {
+ /**
+ * Painting delegate.
+ */
+ private ButtonBackgroundDelegate delegate;
+
+ /**
+ * The matching glowing icon. Is used only when
+ * {@link AnimationConfigurationManager#isAnimationAllowed(AnimationFacet, Component)}
+ * returns true on {@link AnimationFacet#ICON_GLOW}.
+ */
+ protected GlowingIcon glowingIcon;
+
+ /**
+ * Property change listener. Listens on changes to the
+ * {@link SubstanceLookAndFeel#BUTTON_SHAPER_PROPERTY} property and
+ * {@link AbstractButton#MODEL_CHANGED_PROPERTY} property.
+ */
+ protected PropertyChangeListener substancePropertyListener;
+
+ /**
+ * Tracker for visual state transitions.
+ */
+ protected ButtonVisualStateTracker substanceVisualStateTracker;
+
+ protected JToggleButton toggleButton;
+
+ private Rectangle viewRect = new Rectangle();
+
+ private Rectangle iconRect = new Rectangle();
+
+ private Rectangle textRect = new Rectangle();
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceToggleButtonUI((JToggleButton) comp);
+ }
+
+ /**
+ * Simple constructor.
+ */
+ public SubstanceToggleButtonUI(JToggleButton toggleButton) {
+ this.toggleButton = toggleButton;
+ this.delegate = new ButtonBackgroundDelegate();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicButtonUI#installDefaults(javax.swing.
+ * AbstractButton)
+ */
+ @Override
+ public void installDefaults(AbstractButton b) {
+ super.installDefaults(b);
+ if (b.getClientProperty(SubstanceButtonUI.BORDER_ORIGINAL) == null)
+ b.putClientProperty(SubstanceButtonUI.BORDER_ORIGINAL, b
+ .getBorder());
+
+ if (b.getClientProperty(SubstanceButtonUI.BORDER_ORIGINAL) == null)
+ b.putClientProperty(SubstanceButtonUI.BORDER_ORIGINAL, b
+ .getBorder());
+
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(b);
+
+ if (b.getClientProperty(SubstanceButtonUI.BORDER_COMPUTED) == null) {
+ b.setBorder(shaper.getButtonBorder(b));
+ } else {
+ Border currBorder = b.getBorder();
+ if (!(currBorder instanceof SubstanceButtonBorder)) {
+ b.setBorder(shaper.getButtonBorder(b));
+ } else {
+ SubstanceButtonBorder sbCurrBorder = (SubstanceButtonBorder) currBorder;
+ if (shaper.getClass() != sbCurrBorder.getButtonShaperClass())
+ b.setBorder(shaper.getButtonBorder(b));
+ }
+ }
+ b.putClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL, b.isOpaque());
+ // fix for defect 140
+ b.setOpaque(false);
+
+ b.setRolloverEnabled(true);
+
+ LookAndFeel.installProperty(b, "iconTextGap", SubstanceSizeUtils
+ .getTextIconGap(SubstanceSizeUtils.getComponentFontSize(b)));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicButtonUI#uninstallDefaults(javax.swing.
+ * AbstractButton)
+ */
+ @Override
+ public void uninstallDefaults(AbstractButton b) {
+ super.uninstallDefaults(b);
+
+ b.setBorder((Border) b
+ .getClientProperty(SubstanceButtonUI.BORDER_ORIGINAL));
+ b.setOpaque((Boolean) b
+ .getClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL));
+ b.putClientProperty(SubstanceButtonUI.OPACITY_ORIGINAL, null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicButtonUI#createButtonListener(javax.swing
+ * .AbstractButton)
+ */
+ @Override
+ protected BasicButtonListener createButtonListener(AbstractButton b) {
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicButtonUI#installListeners(javax.swing.
+ * AbstractButton)
+ */
+ @Override
+ protected void installListeners(final AbstractButton b) {
+ super.installListeners(b);
+
+ this.substanceVisualStateTracker = new ButtonVisualStateTracker();
+ this.substanceVisualStateTracker.installListeners(b, true);
+
+ this.trackGlowingIcon();
+
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (AbstractButton.ICON_CHANGED_PROPERTY.equals(evt
+ .getPropertyName())) {
+ trackGlowingIcon();
+ }
+ }
+ };
+ b.addPropertyChangeListener(this.substancePropertyListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicButtonUI#uninstallListeners(javax.swing.
+ * AbstractButton)
+ */
+ @Override
+ protected void uninstallListeners(AbstractButton b) {
+ this.substanceVisualStateTracker.uninstallListeners(b);
+ this.substanceVisualStateTracker = null;
+
+ b.removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+
+ super.uninstallListeners(b);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicToggleButtonUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ final AbstractButton b = (AbstractButton) c;
+
+ FontMetrics fm = g.getFontMetrics();
+
+ Insets i = c.getInsets();
+
+ viewRect.x = i.left;
+ viewRect.y = i.top;
+ viewRect.width = b.getWidth() - (i.right + viewRect.x);
+ viewRect.height = b.getHeight() - (i.bottom + viewRect.y);
+
+ textRect.x = textRect.y = textRect.width = textRect.height = 0;
+ iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
+
+ Font f = c.getFont();
+
+ // layout the text and icon
+ String text = SwingUtilities.layoutCompoundLabel(c, fm, b.getText(), b
+ .getIcon(), b.getVerticalAlignment(), b
+ .getHorizontalAlignment(), b.getVerticalTextPosition(), b
+ .getHorizontalTextPosition(), viewRect, iconRect, textRect, b
+ .getText() == null ? 0 : b.getIconTextGap());
+
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ View v = (View) c.getClientProperty(BasicHTML.propertyKey);
+ g2d.setFont(f);
+
+ this.delegate.updateBackground(g2d, b);
+ if (v != null) {
+ v.paint(g2d, textRect);
+ } else {
+ this.paintButtonText(g2d, b, textRect, text);
+ }
+
+ // Paint the Icon
+ if (b.getIcon() != null) {
+ paintIcon(g2d, b, iconRect);
+ }
+
+ if (b.isFocusPainted()) {
+ SubstanceCoreUtilities.paintFocus(g, b, b, this, null, textRect,
+ 1.0f, SubstanceSizeUtils
+ .getFocusRingPadding(SubstanceSizeUtils
+ .getComponentFontSize(b)));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.ComponentUI#getPreferredSize(javax.swing.JComponent)
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ AbstractButton button = (AbstractButton) c;
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(button);
+
+ // fix for defect 263
+ Dimension superPref = super.getPreferredSize(button);
+ if (superPref == null)
+ return null;
+
+ if (shaper == null)
+ return superPref;
+
+ return shaper.getPreferredSize(button, superPref);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#contains(javax.swing.JComponent, int,
+ * int)
+ */
+ @Override
+ public boolean contains(JComponent c, int x, int y) {
+ return ButtonBackgroundDelegate.contains((JToggleButton) c, x, y);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicToggleButtonUI#paintIcon(java.awt.Graphics,
+ * javax.swing.AbstractButton, java.awt.Rectangle)
+ */
+ @Override
+ protected void paintIcon(Graphics g, AbstractButton b, Rectangle iconRect) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ // We have three types of icons:
+ // 1. The original button icon
+ // 2. The themed version of 1.
+ // 3. The glowing version of 1.
+ Icon originalIcon = SubstanceCoreUtilities.getOriginalIcon(b, b
+ .getIcon());
+ Icon themedIcon = (!(b instanceof JRadioButton)
+ && !(b instanceof JCheckBox) && SubstanceCoreUtilities
+ .useThemedDefaultIcon(b)) ? SubstanceCoreUtilities
+ .getThemedIcon(b, originalIcon) : originalIcon;
+
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(b, g));
+ float activeAmount = this.substanceVisualStateTracker
+ .getStateTransitionTracker().getActiveStrength();
+ if (activeAmount >= 0.0f) {
+ if (AnimationConfigurationManager.getInstance().isAnimationAllowed(
+ AnimationFacet.ICON_GLOW, b)
+ && this.substanceVisualStateTracker
+ .getStateTransitionTracker().getIconGlowTracker()
+ .isPlaying()) {
+ this.glowingIcon.paintIcon(b, graphics, iconRect.x, iconRect.y);
+ } else {
+ themedIcon.paintIcon(b, graphics, iconRect.x, iconRect.y);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(b,
+ activeAmount, g));
+ originalIcon.paintIcon(b, graphics, iconRect.x, iconRect.y);
+ }
+ } else {
+ originalIcon.paintIcon(b, graphics, iconRect.x, iconRect.y);
+ }
+
+ graphics.dispose();
+ }
+
+ /**
+ * Paints the text.
+ *
+ * @param g
+ * Graphic context
+ * @param button
+ * Button
+ * @param textRect
+ * Text rectangle
+ * @param text
+ * Text to paint
+ */
+ protected void paintButtonText(Graphics g, AbstractButton button,
+ Rectangle textRect, String text) {
+ SubstanceTextUtilities.paintText(g, button, textRect, text, (button)
+ .getDisplayedMnemonicIndex());
+ }
+
+ /**
+ * Tracks possible usage of glowing icon.
+ *
+ */
+ protected void trackGlowingIcon() {
+ Icon currIcon = this.toggleButton.getIcon();
+ if (currIcon instanceof GlowingIcon)
+ return;
+ if (currIcon == null)
+ return;
+ this.glowingIcon = new GlowingIcon(currIcon,
+ this.substanceVisualStateTracker.getStateTransitionTracker()
+ .getIconGlowTracker());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ this.paint(g, c);
+ }
+
+ @Override
+ public boolean isInside(MouseEvent me) {
+ return this.contains(this.toggleButton, me.getX(), me.getY());
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.substanceVisualStateTracker.getStateTransitionTracker();
+ }
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceToolBarSeparatorUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceToolBarSeparatorUI.java
new file mode 100644
index 0000000..5a085bc
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceToolBarSeparatorUI.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicToolBarSeparatorUI;
+
+import org.pushingpixels.substance.internal.painter.SeparatorPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI for toolbar separators in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceToolBarSeparatorUI extends BasicToolBarSeparatorUI {
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceToolBarSeparatorUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicSeparatorUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ SeparatorPainterUtils.paintSeparator(c, graphics, c.getWidth(), c
+ .getHeight(), ((JSeparator) c).getOrientation());
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicToolBarSeparatorUI#getPreferredSize(javax
+ * .swing.JComponent)
+ */
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ Dimension size = ((JToolBar.Separator) c).getSeparatorSize();
+
+ if (size != null) {
+ size = size.getSize();
+ } else {
+ size = new Dimension(6, 6);
+
+ if (((JSeparator) c).getOrientation() == SwingConstants.VERTICAL) {
+ size.height = 0;
+ } else {
+ size.width = 0;
+ }
+ }
+ return size;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicSeparatorUI#getMaximumSize(javax.swing.JComponent
+ * )
+ */
+ @Override
+ public Dimension getMaximumSize(JComponent c) {
+ Dimension pref = getPreferredSize(c);
+ if (((JSeparator) c).getOrientation() == SwingConstants.VERTICAL) {
+ return new Dimension(pref.width, Short.MAX_VALUE);
+ } else {
+ return new Dimension(Short.MAX_VALUE, pref.height);
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceToolBarUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceToolBarUI.java
new file mode 100644
index 0000000..6795879
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceToolBarUI.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.Component;
+import java.awt.Graphics;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicToolBarUI;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.DecorationPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI for tool bars in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceToolBarUI extends BasicToolBarUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceToolBarUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicToolBarUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ SubstanceLookAndFeel.setDecorationType(this.toolBar,
+ DecorationAreaType.TOOLBAR);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicToolBarUI#uninstallDefaults()
+ */
+ @Override
+ protected void uninstallDefaults() {
+ DecorationPainterUtils.clearDecorationType(this.toolBar);
+ super.uninstallDefaults();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ boolean isOpaque = SubstanceCoreUtilities.isOpaque(c);
+ if (isOpaque) {
+ BackgroundPaintingUtils.update(g, c, false);
+ } else {
+ super.update(g, c);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicToolBarUI#setBorderToRollover(java.awt.Component
+ * )
+ */
+ @Override
+ protected void setBorderToRollover(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicToolBarUI#setBorderToNonRollover(java.awt
+ * .Component)
+ */
+ @Override
+ protected void setBorderToNonRollover(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicToolBarUI#setBorderToNormal(java.awt.Component
+ * )
+ */
+ @Override
+ protected void setBorderToNormal(Component c) {
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceToolTipUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceToolTipUI.java
new file mode 100644
index 0000000..f3ef9c6
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceToolTipUI.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.*;
+
+import javax.swing.JComponent;
+import javax.swing.JToolTip;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicHTML;
+import javax.swing.plaf.basic.BasicToolTipUI;
+import javax.swing.text.View;
+
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceTextUtilities;
+
+/**
+ * UI for tool tips in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceToolTipUI extends BasicToolTipUI {
+ /**
+ * Creates a UI delegate for the specified component.
+ *
+ * @param comp
+ * Component.
+ * @return UI delegate.
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceToolTipUI();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicToolTipUI#paint(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ Font font = c.getFont();
+ // FontMetrics metrics = c.getFontMetrics(font);
+ Dimension size = c.getSize();
+ if (c.isOpaque()) {
+ g.setColor(c.getBackground());
+ g.fillRect(0, 0, size.width, size.height);
+ }
+ g.setColor(c.getForeground());
+ g.setFont(font);
+ // fix for bug 4153892
+ String tipText = ((JToolTip) c).getTipText();
+ if (tipText == null) {
+ tipText = "";
+ }
+
+ Insets insets = c.getInsets();
+ Rectangle paintTextR = new Rectangle(insets.left + 3, insets.top,
+ size.width - (insets.left + insets.right + 6), size.height
+ - (insets.top + insets.bottom + 2));
+ View v = (View) c.getClientProperty(BasicHTML.propertyKey);
+ if (v != null) {
+ v.paint(g, paintTextR);
+ } else {
+ SubstanceTextUtilities.paintText(g, c, paintTextR, tipText, -1,
+ font, c.getForeground(), null);
+ }
+ }
+
+ @Override
+ public Dimension getPreferredSize(JComponent c) {
+ Font font = c.getFont();
+ Insets insets = c.getInsets();
+
+ Dimension prefSize = new Dimension(insets.left + insets.right,
+ insets.top + insets.bottom);
+ String text = ((JToolTip) c).getTipText();
+
+ if ((text == null) || text.equals("")) {
+ text = "";
+ } else {
+ View v = (c != null) ? (View) c.getClientProperty("html") : null;
+ if (v != null) {
+ // fix for 302 - add extra pixels for the HTML view as well
+ prefSize.width += (int) (v.getPreferredSpan(View.X_AXIS) + 6);
+ prefSize.height += (int) (v.getPreferredSpan(View.Y_AXIS) + 2);
+ } else {
+ FontMetrics fm = c.getFontMetrics(font);
+ prefSize.width += fm.stringWidth(text) + 6;
+ prefSize.height += fm.getHeight() + 2;
+ }
+ }
+ return prefSize;
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTreeUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTreeUI.java
new file mode 100644
index 0000000..7334b85
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceTreeUI.java
@@ -0,0 +1,1199 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.swing.ButtonModel;
+import javax.swing.DefaultButtonModel;
+import javax.swing.JComponent;
+import javax.swing.JTree;
+import javax.swing.SwingUtilities;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.IconUIResource;
+import javax.swing.plaf.basic.BasicTreeUI;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.tree.TreePath;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.utils.LookUtils;
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.ComponentStateFacet;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.renderers.SubstanceDefaultTreeCellRenderer;
+import org.pushingpixels.substance.internal.animation.StateTransitionMultiTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.RepaintCallback;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceStripingUtils;
+import org.pushingpixels.substance.internal.utils.icon.SubstanceIconFactory;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.TimelineCallback;
+import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
+
+/**
+ * UI for lists in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTreeUI extends BasicTreeUI {
+ /**
+ * Holds the list of currently selected paths.
+ */
+ protected Map<TreePathId, Object> selectedPaths;
+
+ /**
+ * Holds the currently rolled-over path or <code>null</code> if none such.
+ */
+ protected TreePathId currRolloverPathId;
+
+ /**
+ * Listener that listens to changes on tree properties.
+ */
+ protected PropertyChangeListener substancePropertyChangeListener;
+
+ /**
+ * Listener for selection animations.
+ */
+ protected TreeSelectionListener substanceSelectionFadeListener;
+
+ /**
+ * Listener for transition animations on tree rollovers.
+ */
+ protected RolloverFadeListener substanceFadeRolloverListener;
+
+ /**
+ * Listener for selection of an entire row.
+ */
+ protected MouseListener substanceRowSelectionListener;
+
+ private StateTransitionMultiTracker<TreePathId> stateTransitionMultiTracker;
+
+ /**
+ * The current default color scheme. Is computed in
+ * {@link #update(Graphics, JComponent)} and reused in
+ * {@link SubstanceDefaultTreeCellRenderer#getTreeCellRendererComponent(JTree, Object, boolean, boolean, boolean, int, boolean)}
+ * for performance optimizations.
+ */
+ private SubstanceColorScheme currDefaultColorScheme;
+
+ /**
+ * Cell renderer insets. Is computed in {@link #installDefaults()} and
+ * reused in
+ * {@link SubstanceDefaultTreeCellRenderer#getTreeCellRendererComponent(JTree, Object, boolean, boolean, boolean, int, boolean)}
+ * for performance optimizations.
+ */
+ private Insets cellRendererInsets;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceTreeUI();
+ }
+
+ /**
+ * Creates a UI delegate for tree.
+ */
+ public SubstanceTreeUI() {
+ super();
+ this.selectedPaths = new HashMap<TreePathId, Object>();
+ this.stateTransitionMultiTracker = new StateTransitionMultiTracker<TreePathId>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTreeUI#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ if (SubstanceCoreUtilities.toDrawWatermark(this.tree))
+ this.tree.setOpaque(false);
+
+ if (this.tree.getSelectionPaths() != null) {
+ for (TreePath selectionPath : this.tree.getSelectionPaths()) {
+ TreePathId pathId = new TreePathId(selectionPath);
+ selectedPaths.put(pathId, selectionPath.getLastPathComponent());
+ }
+ }
+
+ setExpandedIcon(new IconUIResource(SubstanceIconFactory.getTreeIcon(
+ this.tree, false)));
+ setCollapsedIcon(new IconUIResource(SubstanceIconFactory.getTreeIcon(
+ this.tree, true)));
+
+ // instead of computing the cell renderer insets on
+ // every cell rendering, compute it once and expose to the
+ // SubstanceDefaultTreeCellRenderer
+ this.cellRendererInsets = SubstanceSizeUtils
+ .getTreeCellRendererInsets(SubstanceSizeUtils
+ .getComponentFontSize(tree));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTreeUI#uninstallDefaults()
+ */
+ @Override
+ protected void uninstallDefaults() {
+ this.selectedPaths.clear();
+ super.uninstallDefaults();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTreeUI#paintRow(java.awt.Graphics,
+ * java.awt.Rectangle, java.awt.Insets, java.awt.Rectangle,
+ * javax.swing.tree.TreePath, int, boolean, boolean, boolean)
+ */
+ @Override
+ protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets,
+ Rectangle bounds, TreePath path, int row, boolean isExpanded,
+ boolean hasBeenExpanded, boolean isLeaf) {
+ // Don't paint the renderer if editing this row.
+ if ((this.editingComponent != null) && (this.editingRow == row)) {
+ // fix for issue 446 - paint the expand control
+ // on the editing row
+ if (shouldPaintExpandControl(path, row, isExpanded,
+ hasBeenExpanded, isLeaf)) {
+ if (!this.tree.getComponentOrientation().isLeftToRight()
+ && LookUtils.IS_JAVA_5) {
+ bounds.x -= 4;
+ }
+ paintExpandControlEnforce(g, clipBounds, insets, bounds, path,
+ row, isExpanded, hasBeenExpanded, isLeaf);
+ }
+ }
+
+ int leadIndex;
+
+ if (this.tree.hasFocus()) {
+ TreePath leadPath = this.tree.getLeadSelectionPath();
+ leadIndex = this.getRowForPath(this.tree, leadPath);
+ } else {
+ leadIndex = -1;
+ }
+
+ Component renderer = this.currentCellRenderer
+ .getTreeCellRendererComponent(this.tree, path
+ .getLastPathComponent(), this.tree.isRowSelected(row),
+ isExpanded, isLeaf, row, (leadIndex == row));
+
+ if (!(renderer instanceof SubstanceDefaultTreeCellRenderer)) {
+ // if it's not Substance renderer - ask the Basic delegate to paint
+ // it.
+ super.paintRow(g, clipBounds, insets, bounds, path, row,
+ isExpanded, hasBeenExpanded, isLeaf);
+ if (shouldPaintExpandControl(path, row, isExpanded,
+ hasBeenExpanded, isLeaf)) {
+ paintExpandControlEnforce(g, clipBounds, insets, bounds, path,
+ row, isExpanded, hasBeenExpanded, isLeaf);
+ }
+ return;
+ }
+
+ TreePathId pathId = new TreePathId(path);
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(tree, g));
+
+ // Color background = renderer.getBackground();
+ // if (background == null)
+ // background = tree.getBackground();
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = getModelStateInfo(pathId);
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ((modelStateInfo == null) ? null
+ : modelStateInfo.getStateContributionMap());
+ ComponentState currState = ((modelStateInfo == null) ? getPathState(pathId)
+ : modelStateInfo.getCurrModelState());
+
+ // Compute the alpha values for the animation.
+ boolean hasHighlights = false;
+ if (renderer.isEnabled()) {
+ if (activeStates != null) {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
+ .entrySet()) {
+ hasHighlights = (SubstanceColorSchemeUtilities
+ .getHighlightAlpha(this.tree, stateEntry.getKey())
+ * stateEntry.getValue().getContribution() > 0.0f);
+ if (hasHighlights)
+ break;
+ }
+ } else {
+ hasHighlights = (SubstanceColorSchemeUtilities
+ .getHighlightAlpha(this.tree, currState) > 0.0f);
+ }
+ }
+
+ // System.out.println(row + ":" + prevTheme.getDisplayName() + "["
+ // + alphaForPrevBackground + "]:" + currTheme.getDisplayName()
+ // + "[" + alphaForCurrBackground + "]");
+
+ // At this point the renderer is an instance of
+ // SubstanceDefaultTreeCellRenderer
+ JTree.DropLocation dropLocation = tree.getDropLocation();
+ Rectangle rowRectangle = new Rectangle(this.tree.getInsets().left,
+ bounds.y, this.tree.getWidth() - this.tree.getInsets().right
+ - this.tree.getInsets().left, bounds.height);
+ if (dropLocation != null && dropLocation.getChildIndex() == -1
+ && tree.getRowForPath(dropLocation.getPath()) == row) {
+ // mark drop location
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(tree,
+ ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ currState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(tree, ColorSchemeAssociationKind.BORDER,
+ currState);
+ HighlightPainterUtils.paintHighlight(g2d, this.rendererPane,
+ renderer, rowRectangle, 0.8f, null, scheme, borderScheme);
+ } else {
+ if (hasHighlights) {
+ if (activeStates == null) {
+ float alpha = SubstanceColorSchemeUtilities
+ .getHighlightAlpha(this.tree, currState);
+ if (alpha > 0.0f) {
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tree,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ currState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tree,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ currState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.tree, alpha, g));
+ // Fix for defect 180 - painting the
+ // highlight beneath the entire row
+ HighlightPainterUtils.paintHighlight(g2d,
+ this.rendererPane, renderer, rowRectangle,
+ 0.8f, null, fillScheme, borderScheme);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.tree, g));
+ }
+ } else {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = stateEntry.getKey();
+ float alpha = SubstanceColorSchemeUtilities
+ .getHighlightAlpha(this.tree, activeState)
+ * stateEntry.getValue().getContribution();
+ if (alpha == 0.0f)
+ continue;
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tree,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.tree,
+ ColorSchemeAssociationKind.HIGHLIGHT,
+ activeState);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.tree, alpha, g));
+ // Fix for defect 180 - painting the
+ // highlight beneath the entire row
+ HighlightPainterUtils.paintHighlight(g2d,
+ this.rendererPane, renderer, rowRectangle,
+ 0.8f, null, fillScheme, borderScheme);
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.tree, g));
+ }
+ }
+ }
+ }
+
+ // System.out.println("Painting row " + row);
+ // Play with opacity to make our own gradient background
+ // on selected elements to show - safe to cast and set opacity
+ // since at this point the renderer can only by the
+ // SubstanceDefaultTreeCellRenderer
+ JComponent jRenderer = (JComponent) renderer;
+ boolean newOpaque = !this.tree.isRowSelected(row);
+ if (SubstanceCoreUtilities.toDrawWatermark(this.tree))
+ newOpaque = false;
+
+ Map<Component, Boolean> opacity = new HashMap<Component, Boolean>();
+ if (!newOpaque)
+ SubstanceCoreUtilities.makeNonOpaque(jRenderer, opacity);
+ this.rendererPane.paintComponent(g2d, renderer, this.tree, bounds.x,
+ bounds.y, Math.max(this.tree.getWidth()
+ - this.tree.getInsets().right
+ - this.tree.getInsets().left - bounds.x, bounds.width),
+ bounds.height, true);
+ if (!newOpaque)
+ SubstanceCoreUtilities.restoreOpaque(jRenderer, opacity);
+
+ // Paint the expand control now after the row background has been
+ // overlayed by the highlight background on selected and rolled over
+ // rows. See comments on paintExpandControl().
+ if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded,
+ isLeaf)) {
+ paintExpandControlEnforce(g2d, clipBounds, insets, bounds, path,
+ row, isExpanded, hasBeenExpanded, isLeaf);
+ }
+
+ g2d.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTreeUI#paintExpandControl(java.awt.Graphics,
+ * java.awt.Rectangle, java.awt.Insets, java.awt.Rectangle,
+ * javax.swing.tree.TreePath, int, boolean, boolean, boolean)
+ */
+ @Override
+ protected void paintExpandControl(Graphics g, Rectangle clipBounds,
+ Insets insets, Rectangle bounds, TreePath path, int row,
+ boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) {
+ // This does nothing. The base implementation of paint() paints
+ // the tree lines and tree expand controls *before* painting the
+ // renderer. In Substance, the highlights are painted in the
+ // paintRow, and thus would overlay the expand controls. This results
+ // in expand controls being much less visible under most of the skins.
+ // So, Substance paints the expand controls *after* painting the
+ // highlights (and the renderer which doesn't overlap with the expand
+ // controls in any case). This is done in paintRow() by calling
+ // the paintExpandControlEnforce() instead (that eventually calls the
+ // super implementation of paintExpandControl().
+ }
+
+ /**
+ * Paints the expand control of the specified row.
+ *
+ * @param g
+ * Graphics context.
+ * @param clipBounds
+ * Clip bounds.
+ * @param insets
+ * Insets.
+ * @param bounds
+ * Row bounds.
+ * @param path
+ * Tree path.
+ * @param row
+ * Tree row.
+ * @param isExpanded
+ * Expand indication.
+ * @param hasBeenExpanded
+ * Indication whether this row has ever been expanded.
+ * @param isLeaf
+ * Indication whether this row is a leaf.
+ */
+ protected void paintExpandControlEnforce(Graphics g, Rectangle clipBounds,
+ Insets insets, Rectangle bounds, TreePath path, int row,
+ boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) {
+ // boolean toPaint = (!this.tree.isEnabled()) || this.isInside;
+
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(this.tree,
+ this.tree.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED);
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ // if (toPaint) {
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(this.tree,
+ alpha, g));
+ super.paintExpandControl(graphics, clipBounds, insets, bounds, path,
+ row, isExpanded, hasBeenExpanded, isLeaf);
+ // }
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTreeUI#paintHorizontalPartOfLeg(java.awt.
+ * Graphics, java.awt.Rectangle, java.awt.Insets, java.awt.Rectangle,
+ * javax.swing.tree.TreePath, int, boolean, boolean, boolean)
+ */
+ @Override
+ protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
+ Insets insets, Rectangle bounds, TreePath path, int row,
+ boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicTreeUI#paintVerticalPartOfLeg(java.awt.Graphics
+ * , java.awt.Rectangle, java.awt.Insets, javax.swing.tree.TreePath)
+ */
+ @Override
+ protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
+ Insets insets, TreePath path) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTreeUI#createDefaultCellRenderer()
+ */
+ @Override
+ protected TreeCellRenderer createDefaultCellRenderer() {
+ return new SubstanceDefaultTreeCellRenderer();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTreeUI#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (SubstanceLookAndFeel.WATERMARK_VISIBLE.equals(evt
+ .getPropertyName())) {
+ tree.setOpaque(!SubstanceCoreUtilities
+ .toDrawWatermark(tree));
+ }
+ if ("font".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ tree.updateUI();
+ }
+ });
+ }
+ if ("dropLocation".equals(evt.getPropertyName())) {
+ JTree.DropLocation oldValue = (JTree.DropLocation) evt
+ .getOldValue();
+ if (oldValue != null && oldValue.getPath() != null) {
+ TreePath oldDrop = oldValue.getPath();
+ Rectangle oldBounds = getPathBounds(tree, oldDrop);
+ tree.repaint(0, oldBounds.y, tree.getWidth(),
+ oldBounds.height);
+ }
+ JTree.DropLocation currLocation = tree.getDropLocation();
+ if (currLocation != null) {
+ TreePath newDrop = currLocation.getPath();
+ if (newDrop != null) {
+ Rectangle newBounds = getPathBounds(tree, newDrop);
+ tree.repaint(0, newBounds.y, tree.getWidth(),
+ newBounds.height);
+ }
+ }
+ }
+ }
+ };
+ this.tree
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+
+ this.substanceSelectionFadeListener = new MyTreeSelectionListener();
+ this.tree.getSelectionModel().addTreeSelectionListener(
+ this.substanceSelectionFadeListener);
+
+ this.substanceRowSelectionListener = new RowSelectionListener();
+ this.tree.addMouseListener(this.substanceRowSelectionListener);
+
+ // Add listener for the fade animation
+ this.substanceFadeRolloverListener = new RolloverFadeListener();
+ this.tree.addMouseMotionListener(this.substanceFadeRolloverListener);
+ this.tree.addMouseListener(this.substanceFadeRolloverListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicTreeUI#uninstallListeners()
+ */
+ @Override
+ protected void uninstallListeners() {
+ this.tree.removeMouseListener(this.substanceRowSelectionListener);
+ this.substanceRowSelectionListener = null;
+
+ this.tree.getSelectionModel().removeTreeSelectionListener(
+ this.substanceSelectionFadeListener);
+ this.substanceSelectionFadeListener = null;
+
+ this.tree
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+
+ // Remove listener for the fade animation
+ this.tree.removeMouseMotionListener(this.substanceFadeRolloverListener);
+ this.tree.removeMouseListener(this.substanceFadeRolloverListener);
+ this.substanceFadeRolloverListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /**
+ * ID of a single tree path.
+ *
+ * @author Kirill Grouchnikov
+ */
+ @SuppressWarnings("unchecked")
+ public static class TreePathId implements Comparable {
+ /**
+ * Tree path.
+ */
+ protected TreePath path;
+
+ /**
+ * Creates a tree path ID.
+ *
+ * @param path
+ * Tree path.
+ */
+ public TreePathId(TreePath path) {
+ this.path = path;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ @Override
+ public int compareTo(Object o) {
+ if (o instanceof TreePathId) {
+ TreePathId otherId = (TreePathId) o;
+ if ((this.path == null) && (otherId.path != null))
+ return 1;
+ if ((otherId.path == null) && (this.path != null))
+ return -1;
+ Object[] path1Objs = this.path.getPath();
+ Object[] path2Objs = otherId.path.getPath();
+ if (path1Objs.length != path2Objs.length)
+ return 1;
+ for (int i = 0; i < path1Objs.length; i++)
+ if (!path1Objs[i].equals(path2Objs[i]))
+ return 1;
+ return 0;
+ }
+ return -1;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return this.compareTo(obj) == 0;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ if (this.path == null)
+ return 0;
+ Object[] pathObjs = this.path.getPath();
+ int result = pathObjs[0].hashCode();
+ for (int i = 1; i < pathObjs.length; i++)
+ result = result ^ pathObjs[i].hashCode();
+ return result;
+ }
+ }
+
+ /**
+ * Selection listener for selection animation effects.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class MyTreeSelectionListener implements TreeSelectionListener {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.
+ * event.TreeSelectionEvent)
+ */
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+ // Map<TreePathId, Object> currSelected = (Map<TreePathId, Object>)
+ // tree
+ // .getClientProperty(SELECTED_INDICES);
+ if (tree.getSelectionPaths() != null) {
+ for (TreePath selectionPath : tree.getSelectionPaths()) {
+ TreePathId pathId = new TreePathId(selectionPath);
+
+ // check if was selected before
+ if (!selectedPaths.containsKey(pathId)) {
+ // start fading in
+ StateTransitionTracker tracker = getTracker(pathId,
+ (currRolloverPathId != null)
+ && pathId.equals(currRolloverPathId),
+ false);
+ tracker.getModel().setSelected(true);
+ selectedPaths.put(pathId, selectionPath
+ .getLastPathComponent());
+ }
+ }
+ }
+
+ for (Iterator<Map.Entry<TreePathId, Object>> it = selectedPaths
+ .entrySet().iterator(); it.hasNext();) {
+ Map.Entry<TreePathId, Object> entry = it.next();
+ if (tree.getSelectionModel()
+ .isPathSelected(entry.getKey().path))
+ continue;
+ // fade out for deselected path
+ TreePathId pathId = entry.getKey();
+ StateTransitionTracker tracker = getTracker(pathId,
+ (currRolloverPathId != null)
+ && pathId.equals(currRolloverPathId), true);
+ tracker.getModel().setSelected(false);
+ it.remove();
+ }
+ }
+ }
+
+ /**
+ * Repaints a single path during the fade animation cycle.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class PathRepaintCallback extends UIThreadTimelineCallbackAdapter {
+ /**
+ * Associated tree.
+ */
+ protected JTree tree;
+
+ /**
+ * Associated (animated) path.
+ */
+ protected TreePath treePath;
+
+ /**
+ * Creates a new animation repaint callback.
+ *
+ * @param tree
+ * Associated tree.
+ * @param treePath
+ * Associated (animated) path.
+ */
+ public PathRepaintCallback(JTree tree, TreePath treePath) {
+ super();
+ this.tree = tree;
+ this.treePath = treePath;
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ this.repaintPath();
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ this.repaintPath();
+ }
+
+ /**
+ * Repaints the associated path.
+ */
+ private void repaintPath() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (SubstanceTreeUI.this.tree == null) {
+ // may happen if the LAF was switched in the meantime
+ return;
+ }
+
+ Rectangle boundsBuffer = new Rectangle();
+ Rectangle bounds = treeState.getBounds(treePath,
+ boundsBuffer);
+
+ if (bounds != null) {
+ // still visible
+
+ // fix for defect 180 - refresh the entire row
+ bounds.x = 0;
+ bounds.width = tree.getWidth();
+
+ // fix for defect 188 - rollover effects for trees
+ // with insets
+ Insets insets = tree.getInsets();
+ bounds.x += insets.left;
+ bounds.y += insets.top;
+
+ tree.repaint(bounds);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Listener for rollover animation effects.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class RolloverFadeListener implements MouseListener,
+ MouseMotionListener {
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (!tree.isEnabled())
+ return;
+ // isInside = true;
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (!tree.isEnabled())
+ return;
+ // isInside = false;
+ this.fadeOut();
+ // System.out.println("Nulling RO index");
+ currRolloverPathId = null;
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ if (!tree.isEnabled())
+ return;
+ // isInside = true;
+ handleMove(e);
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ if (!tree.isEnabled())
+ return;
+ handleMove(e);
+ }
+
+ /**
+ * Handles various mouse move events and initiates the fade animation if
+ * necessary.
+ *
+ * @param e
+ * Mouse event.
+ */
+ private void handleMove(MouseEvent e) {
+ TreePath closestPath = tree.getClosestPathForLocation(e.getX(), e
+ .getY());
+ Rectangle bounds = tree.getPathBounds(closestPath);
+ if (bounds == null) {
+ this.fadeOut();
+ currRolloverPathId = null;
+ return;
+ }
+ if ((e.getY() < bounds.y)
+ || (e.getY() > (bounds.y + bounds.height))) {
+ this.fadeOut();
+ currRolloverPathId = null;
+ return;
+ }
+ // check if this is the same index
+ TreePathId newPathId = new TreePathId(closestPath);
+ if ((currRolloverPathId != null)
+ && newPathId.equals(currRolloverPathId)) {
+ // System.out.println("Same location " +
+ // System.currentTimeMillis());
+ // System.out.print("Current : ");
+ // for (Object o1 : currPathId.path.getPath()) {
+ // System.out.print(o1);
+ // }
+ // System.out.println("");
+ // System.out.print("Closest : ");
+ // for (Object o2 : newPathId.path.getPath()) {
+ // System.out.print(o2);
+ // }
+ // System.out.println("");
+ return;
+ }
+
+ this.fadeOut();
+
+ StateTransitionTracker tracker = getTracker(newPathId, false,
+ selectedPaths.containsKey(newPathId));
+ tracker.getModel().setRollover(true);
+
+ currRolloverPathId = newPathId;
+ }
+
+ /**
+ * Initiates the fade out effect.
+ */
+ private void fadeOut() {
+ if (currRolloverPathId == null)
+ return;
+
+ StateTransitionTracker tracker = getTracker(currRolloverPathId,
+ true, selectedPaths.containsKey(currRolloverPathId));
+ tracker.getModel().setRollover(false);
+ }
+ }
+
+ /**
+ * Listener for selecting the entire rows.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private class RowSelectionListener extends MouseAdapter {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseAdapter#mousePressed(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (!tree.isEnabled())
+ return;
+ TreePath closestPath = tree.getClosestPathForLocation(e.getX(), e
+ .getY());
+ if (closestPath == null)
+ return;
+ Rectangle bounds = tree.getPathBounds(closestPath);
+ // Process events outside the immediate bounds - fix for defect
+ // 19 on substance-netbeans. This properly handles Ctrl and Shift
+ // selections on trees.
+ if ((e.getY() >= bounds.y)
+ && (e.getY() < (bounds.y + bounds.height))
+ && ((e.getX() < bounds.x) || (e.getX() > (bounds.x + bounds.width)))) {
+ // tree.setSelectionPath(closestPath);
+
+ // fix - don't select a node if the click was on the
+ // expand control
+ if (isLocationInExpandControl(closestPath, e.getX(), e.getY()))
+ return;
+ selectPathForEvent(closestPath, e);
+ }
+ }
+ }
+
+ /**
+ * Returns the pivot X for the cells rendered in the specified area. Used
+ * for the smart tree scroll (
+ * {@link SubstanceLookAndFeel#TREE_SMART_SCROLL_ANIMATION_KIND}).
+ *
+ * @param paintBounds
+ * Area bounds.
+ * @return Pivot X for the cells rendered in the specified area
+ */
+ public int getPivotRendererX(Rectangle paintBounds) {
+ TreePath initialPath = getClosestPathForLocation(tree, 0, paintBounds.y);
+ Enumeration<?> paintingEnumerator = treeState
+ .getVisiblePathsFrom(initialPath);
+ int endY = paintBounds.y + paintBounds.height;
+
+ int totalY = 0;
+ int count = 0;
+
+ if (initialPath != null && paintingEnumerator != null) {
+ boolean done = false;
+ Rectangle boundsBuffer = new Rectangle();
+ Rectangle bounds;
+ TreePath path;
+ Insets insets = tree.getInsets();
+
+ while (!done && paintingEnumerator.hasMoreElements()) {
+ path = (TreePath) paintingEnumerator.nextElement();
+ if (path != null) {
+ bounds = treeState.getBounds(path, boundsBuffer);
+ bounds.x += insets.left;
+ bounds.y += insets.top;
+
+ int currMedianX = bounds.x;// + bounds.width / 2;
+ totalY += currMedianX;
+ count++;
+ if ((bounds.y + bounds.height) >= endY)
+ done = true;
+ } else {
+ done = true;
+ }
+ }
+ }
+ if (count == 0)
+ return -1;
+ return totalY
+ / count
+ - 2
+ * SubstanceSizeUtils.getTreeIconSize(SubstanceSizeUtils
+ .getComponentFontSize(tree));
+ }
+
+ /**
+ * Returns the current state for the specified path.
+ *
+ * @param pathId
+ * Path index.
+ * @return The current state for the specified path.
+ */
+ public ComponentState getPathState(TreePathId pathId) {
+ boolean isEnabled = this.tree.isEnabled();
+ StateTransitionTracker tracker = this.stateTransitionMultiTracker
+ .getTracker(pathId);
+ if (tracker == null) {
+ int rowIndex = this.tree.getRowForPath(pathId.path);
+ boolean isRollover = (this.currRolloverPathId != null)
+ && pathId.equals(this.currRolloverPathId);
+ boolean isSelected = this.tree.isRowSelected(rowIndex);
+ return ComponentState.getState(isEnabled, isRollover, isSelected);
+ } else {
+ ComponentState fromTracker = tracker.getModelStateInfo()
+ .getCurrModelState();
+ return ComponentState.getState(isEnabled, fromTracker
+ .isFacetActive(ComponentStateFacet.ROLLOVER), fromTracker
+ .isFacetActive(ComponentStateFacet.SELECTION));
+ }
+ }
+
+ public StateTransitionTracker.ModelStateInfo getModelStateInfo(
+ TreePathId pathId) {
+ if (this.stateTransitionMultiTracker.size() == 0)
+ return null;
+ StateTransitionTracker tracker = this.stateTransitionMultiTracker
+ .getTracker(pathId);
+ if (tracker == null) {
+ return null;
+ } else {
+ return tracker.getModelStateInfo();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ BackgroundPaintingUtils.updateIfOpaque(g, c);
+
+ // Should never happen if installed for a UI
+ if (treeState == null) {
+ return;
+ }
+
+ // compute the default color scheme - to optimize the performance
+ // SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ // .getColorScheme(this.tree,
+ // this.tree.isEnabled() ? ComponentState.DEFAULT
+ // : ComponentState.DISABLED_UNSELECTED);
+ // this.currHashColor = scheme.getLineColor();
+ this.currDefaultColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(tree, ComponentState.ENABLED);
+
+ Rectangle paintBounds = g.getClipBounds();
+ Insets insets = tree.getInsets();
+
+ TreePath initialPath = getClosestPathForLocation(tree, 0, paintBounds.y);
+ Enumeration<?> paintingEnumerator = treeState
+ .getVisiblePathsFrom(initialPath);
+ int row = treeState.getRowForPath(initialPath);
+ int endY = paintBounds.y + paintBounds.height;
+
+ // second part - fix for defect 214 (rollover effects on non-opaque
+ // trees resulted in inconsistent behaviour)
+ boolean isWatermarkBleed = SubstanceCoreUtilities.toDrawWatermark(tree)
+ || !tree.isOpaque();
+
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ SubstanceStripingUtils.setup(c);
+ if (initialPath != null && paintingEnumerator != null) {
+ boolean done = false;
+ Rectangle boundsBuffer = new Rectangle();
+ Rectangle bounds;
+ TreePath path;
+
+ while (!done && paintingEnumerator.hasMoreElements()) {
+ path = (TreePath) paintingEnumerator.nextElement();
+ if (path != null) {
+ // respect the background color of the renderer.
+ boolean isLeaf = treeModel.isLeaf(path
+ .getLastPathComponent());
+ boolean isExpanded = isLeaf ? false : treeState
+ .getExpandedState(path);
+ Component renderer = this.currentCellRenderer
+ .getTreeCellRendererComponent(this.tree, path
+ .getLastPathComponent(), this.tree
+ .isRowSelected(row), isExpanded, isLeaf,
+ row, tree.hasFocus() ? (tree
+ .getLeadSelectionRow() == row)
+ : false);
+ Color background = renderer.getBackground();
+ if (background == null)
+ background = tree.getBackground();
+ bounds = treeState.getBounds(path, boundsBuffer);
+ if(bounds != null){
+ bounds.x += insets.left;
+ bounds.y += insets.top;
+ if (!isWatermarkBleed) {
+ g2d.setColor(background);
+ g2d.fillRect(paintBounds.x, bounds.y,
+ paintBounds.width, bounds.height);
+ } else {
+ if (this.tree.getComponentOrientation().isLeftToRight()) {
+ BackgroundPaintingUtils.fillAndWatermark(g2d,
+ this.tree, background, new Rectangle(
+ paintBounds.x, bounds.y,
+ paintBounds.width, bounds.height));
+ } else {
+ BackgroundPaintingUtils.fillAndWatermark(g2d,
+ this.tree, background, new Rectangle(
+ paintBounds.x, bounds.y,
+ paintBounds.width, bounds.height));
+ }
+ }
+ if ((bounds.y + bounds.height) >= endY)
+ done = true;
+ }
+ } else {
+ done = true;
+ }
+ row++;
+ }
+ }
+
+ this.paint(g2d, c);
+ SubstanceStripingUtils.tearDown(c);
+ g2d.dispose();
+ }
+
+ // /*
+ // * (non-Javadoc)
+ // *
+ // * @see javax.swing.plaf.basic.BasicTreeUI#getHashColor()
+ // */
+ // @Override
+ // protected Color getHashColor() {
+ // return this.currHashColor;
+ // }
+
+ /**
+ * Returns the default color scheme of this tree. Is for internal use only.
+ *
+ * @return The default color scheme of this tree.
+ */
+ public SubstanceColorScheme getDefaultColorScheme() {
+ return this.currDefaultColorScheme;
+ }
+
+ /**
+ * Returns the cell renderer insets of this tree. Is for internal use only.
+ *
+ * @return The cell renderer insets of this tree.
+ */
+ public Insets getCellRendererInsets() {
+ return cellRendererInsets;
+ }
+
+ @Override
+ public Rectangle getPathBounds(JTree tree, TreePath path) {
+ Rectangle result = super.getPathBounds(tree, path);
+ if (result != null) {
+ if (!tree.getComponentOrientation().isLeftToRight()) {
+ int delta = result.x - tree.getInsets().left;
+ result.x -= delta;
+ result.width += delta;
+ }
+ }
+ return result;
+ }
+
+ private StateTransitionTracker getTracker(final TreePathId pathId,
+ boolean initialRollover, boolean initialSelected) {
+ StateTransitionTracker tracker = stateTransitionMultiTracker
+ .getTracker(pathId);
+ if (tracker == null) {
+ ButtonModel model = new DefaultButtonModel();
+ model.setSelected(initialSelected);
+ model.setRollover(initialRollover);
+ tracker = new StateTransitionTracker(this.tree, model);
+ tracker.registerModelListeners();
+ tracker.setRepaintCallback(new RepaintCallback() {
+ @Override
+ public TimelineCallback getRepaintCallback() {
+ return new PathRepaintCallback(tree, pathId.path);
+ }
+ });
+ stateTransitionMultiTracker.addTracker(pathId, tracker);
+ }
+ return tracker;
+ }
+
+ public StateTransitionTracker getStateTransitionTracker(TreePathId pathId) {
+ return this.stateTransitionMultiTracker.getTracker(pathId);
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceViewportUI.java b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceViewportUI.java
new file mode 100644
index 0000000..43ad96c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/ui/SubstanceViewportUI.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.ui;
+
+import java.awt.Color;
+import java.awt.Graphics;
+
+import javax.swing.JComponent;
+import javax.swing.JViewport;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicViewportUI;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * UI for panels in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceViewportUI extends BasicViewportUI {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
+ */
+ public static ComponentUI createUI(JComponent comp) {
+ SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
+ return new SubstanceViewportUI();
+ }
+
+ @Override
+ protected void installDefaults(JComponent c) {
+ super.installDefaults(c);
+ // support for per-window skins
+ Color backgr = c.getBackground();
+ if ((backgr == null) || (backgr instanceof UIResource)) {
+ Color backgroundFillColor = SubstanceColorUtilities
+ .getBackgroundFillColor(c);
+ if (backgroundFillColor != null) {
+ c.setBackground(new ColorUIResource(backgroundFillColor));
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
+ * javax.swing.JComponent)
+ */
+ @Override
+ public void update(Graphics g, JComponent c) {
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return;
+
+ if (toPaintBackground(c)) {
+ BackgroundPaintingUtils.update(g, c, false);
+ }
+ super.paint(g, c);
+ }
+
+ /**
+ * Returns indication whether the viewport background should be filled.
+ *
+ * @param c
+ * Component (should be {@link JViewport}).
+ * @return <code>true</code> if the viewport background should be filled
+ * with the background color, <code>false</code> otherwise.
+ */
+ protected boolean toPaintBackground(JComponent c) {
+ return SubstanceCoreUtilities.isOpaque(c);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/ButtonBackgroundDelegate.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/ButtonBackgroundDelegate.java
new file mode 100644
index 0000000..ee0a030
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/ButtonBackgroundDelegate.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.AlphaComposite;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.Shape;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractButton;
+import javax.swing.ButtonModel;
+import javax.swing.JButton;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceConstants;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceConstants.Side;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.shaper.RectangularButtonShaper;
+import org.pushingpixels.substance.api.shaper.StandardButtonShaper;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.internal.animation.ModificationAwareUI;
+import org.pushingpixels.substance.internal.animation.RootPaneDefaultButtonTracker;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.TimelineState;
+
+/**
+ * Delegate class for painting backgrounds of buttons in <b>Substance </b> look
+ * and feel. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ButtonBackgroundDelegate {
+ /**
+ * Cache for background images. Each time
+ * {@link #getFullAlphaBackground(javax.swing.AbstractButton, javax.swing.ButtonModel, org.pushingpixels.substance.api.shaper.SubstanceButtonShaper, org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter, org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter, int, int)}
+ * is called, it checks <code>this</code> map to see if it already contains
+ * such background. If so, the background from the map is returned.
+ */
+ private static LazyResettableHashMap<BufferedImage> regularBackgrounds = new LazyResettableHashMap<BufferedImage>(
+ "ButtonBackgroundDelegate");
+
+ /**
+ * Retrieves the background for the specified button.
+ *
+ * @param button
+ * Button.
+ * @param model
+ * Button model.
+ * @param shaper
+ * Button shaper.
+ * @param fillPainter
+ * Button fill painter.
+ * @param borderPainter
+ * Button border painter.
+ * @param width
+ * Button width.
+ * @param height
+ * Button height.
+ * @return Button background.
+ */
+ public static BufferedImage getFullAlphaBackground(AbstractButton button,
+ ButtonModel model, SubstanceButtonShaper shaper,
+ SubstanceFillPainter fillPainter,
+ SubstanceBorderPainter borderPainter, int width, int height) {
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) button
+ .getUI();
+ StateTransitionTracker.ModelStateInfo modelStateInfo = transitionAwareUI
+ .getTransitionTracker().getModelStateInfo();
+
+ ComponentState currState = modelStateInfo.getCurrModelState();
+
+ // ComponentState prevState = stateTransitionModel.getPrevModelState();
+
+ // System.out.println(button.getText() + ": " + prevState.name() +
+ // " -> "
+ // + state.name() + " at "
+ // + stateTransitionModel.getTransitionPosition());
+
+ // compute cycle count (for animation)
+ float cyclePos = 0.0f;// currState.getCyclePosition();
+ // boolean isPulsating = false;
+ if (button instanceof JButton) {
+ JButton jb = (JButton) button;
+ if (RootPaneDefaultButtonTracker.isPulsating(jb)
+ && (currState != ComponentState.PRESSED_SELECTED)
+ && (currState != ComponentState.PRESSED_UNSELECTED)) {
+ // isPulsating = true;
+ cyclePos = RootPaneDefaultButtonTracker.getTimelinePosition(jb);
+ }
+ }
+
+ // compute the straight sides
+ Set<SubstanceConstants.Side> straightSides = SubstanceCoreUtilities
+ .getSides(button, SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY);
+
+ boolean isRoundButton = StandardButtonShaper.isRoundButton(button);
+ float radius = 0.0f;
+ if (shaper instanceof RectangularButtonShaper) {
+ radius = ((RectangularButtonShaper) shaper).getCornerRadius(button,
+ null);
+ }
+
+ Set<Side> openSides = SubstanceCoreUtilities.getSides(button,
+ SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY);
+ // String openKey = "";
+ // for (Side oSide : openSides) {
+ // openKey += oSide.name() + "-";
+ // }
+ // String extraModelKey = "";
+ // for (String modelKey : extraModelKeys) {
+ // extraModelKey += (modelKey + "-");
+ // }
+ boolean isContentAreaFilled = button.isContentAreaFilled();
+ boolean isBorderPainted = button.isBorderPainted();
+
+ // compute color scheme
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, ColorSchemeAssociationKind.BORDER,
+ currState);
+
+ // see if need to use attention-drawing animation
+ // boolean isWindowModified = false;
+ if (button.getUI() instanceof ModificationAwareUI) {
+ ModificationAwareUI modificationAwareUI = (ModificationAwareUI) button
+ .getUI();
+ Timeline modificationTimeline = modificationAwareUI
+ .getModificationTimeline();
+ if (modificationTimeline != null) {
+ if (modificationTimeline.getState() != TimelineState.IDLE) {
+ // isWindowModified = true;
+ SubstanceColorScheme colorScheme2 = SubstanceColorSchemeUtilities.YELLOW;
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities.ORANGE;
+ cyclePos = modificationTimeline.getTimelinePosition();
+
+ HashMapKey key1 = SubstanceCoreUtilities.getHashKey(width,
+ height, colorScheme.getDisplayName(),
+ baseBorderScheme.getDisplayName(), shaper
+ .getDisplayName(), fillPainter
+ .getDisplayName(), borderPainter
+ .getDisplayName(), straightSides,
+ openSides, button.getClass().getName(),
+ isRoundButton, radius, isContentAreaFilled,
+ isBorderPainted, SubstanceSizeUtils
+ .getComponentFontSize(button));
+ BufferedImage layer1 = regularBackgrounds.get(key1);
+ if (layer1 == null) {
+ layer1 = createBackgroundImage(button, shaper,
+ fillPainter, borderPainter, width, height,
+ colorScheme, baseBorderScheme, openSides,
+ isContentAreaFilled, isBorderPainted);
+
+ regularBackgrounds.put(key1, layer1);
+ }
+ HashMapKey key2 = SubstanceCoreUtilities.getHashKey(width,
+ height, colorScheme2.getDisplayName(),
+ baseBorderScheme.getDisplayName(), shaper
+ .getDisplayName(), fillPainter
+ .getDisplayName(), borderPainter
+ .getDisplayName(), straightSides,
+ openSides, button.getClass().getName(),
+ isRoundButton, radius, isContentAreaFilled,
+ isBorderPainted, SubstanceSizeUtils
+ .getComponentFontSize(button));
+ BufferedImage layer2 = regularBackgrounds.get(key2);
+ if (layer2 == null) {
+ layer2 = createBackgroundImage(button, shaper,
+ fillPainter, borderPainter, width, height,
+ colorScheme2, baseBorderScheme, openSides,
+ isContentAreaFilled, isBorderPainted);
+
+ regularBackgrounds.put(key2, layer2);
+ }
+
+ BufferedImage result = SubstanceCoreUtilities
+ .getBlankImage(width, height);
+ Graphics2D g2d = result.createGraphics();
+ if (cyclePos < 1.0f)
+ g2d.drawImage(layer1, 0, 0, null);
+ if (cyclePos > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(cyclePos));
+ g2d.drawImage(layer2, 0, 0, null);
+ }
+ g2d.dispose();
+ return result;
+ }
+ }
+ }
+
+ // see if need to use transition animation. Important - don't do it
+ // on pulsating buttons (such as default or close buttons
+ // of modified frames).
+
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ SubstanceColorScheme baseFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, currState);
+ HashMapKey keyBase = SubstanceCoreUtilities.getHashKey(width, height,
+ baseFillScheme.getDisplayName(), baseBorderScheme
+ .getDisplayName(), shaper.getDisplayName(), fillPainter
+ .getDisplayName(), borderPainter.getDisplayName(),
+ straightSides, openSides, button.getClass().getName(),
+ isRoundButton, (int) (1000 * radius), isContentAreaFilled,
+ isBorderPainted, SubstanceSizeUtils
+ .getComponentFontSize(button));
+ BufferedImage layerBase = regularBackgrounds.get(keyBase);
+ if (layerBase == null) {
+ layerBase = createBackgroundImage(button, shaper, fillPainter,
+ borderPainter, width, height, baseFillScheme,
+ baseBorderScheme, openSides, isContentAreaFilled,
+ isBorderPainted);
+ regularBackgrounds.put(keyBase, layerBase);
+ }
+ if (currState.isDisabled() || (activeStates.size() == 1)) {
+ return layerBase;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D g2d = result.createGraphics();
+ // draw the base layer
+ g2d.drawImage(layerBase, 0, 0, null);
+ // System.out.println("\nPainting base state " + currState);
+
+ // draw the other active layers
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ // System.out.println("Painting state " + activeState + "[curr is "
+ // + currState + "] with " + activeEntry.getValue());
+ if (activeState == currState)
+ continue;
+
+ float stateContribution = activeEntry.getValue().getContribution();
+ if (stateContribution > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(stateContribution));
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button,
+ ColorSchemeAssociationKind.BORDER, activeState);
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width,
+ height, fillScheme.getDisplayName(), borderScheme
+ .getDisplayName(), shaper.getDisplayName(),
+ fillPainter.getDisplayName(), borderPainter
+ .getDisplayName(), straightSides, openSides,
+ button.getClass().getName(), isRoundButton,
+ (int) (1000 * radius), isContentAreaFilled,
+ isBorderPainted, SubstanceSizeUtils
+ .getComponentFontSize(button));
+ BufferedImage layer = regularBackgrounds.get(key);
+ if (layer == null) {
+ layer = createBackgroundImage(button, shaper, fillPainter,
+ borderPainter, width, height, fillScheme,
+ borderScheme, openSides, isContentAreaFilled,
+ isBorderPainted);
+ regularBackgrounds.put(key, layer);
+ }
+ g2d.drawImage(layer, 0, 0, null);
+ }
+ }
+ g2d.dispose();
+ return result;
+ }
+
+ private static BufferedImage createBackgroundImage(AbstractButton button,
+ SubstanceButtonShaper shaper, SubstanceFillPainter fillPainter,
+ SubstanceBorderPainter borderPainter, int width, int height,
+ SubstanceColorScheme colorScheme,
+ SubstanceColorScheme borderScheme, Set<Side> openSides,
+ boolean isContentAreaFilled, boolean isBorderPainted) {
+ int openDelta = (int) (Math.ceil(3.0 * SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(button))));
+ int deltaLeft = ((openSides != null) && openSides.contains(Side.LEFT)) ? openDelta
+ : 0;
+ int deltaRight = ((openSides != null) && openSides.contains(Side.RIGHT)) ? openDelta
+ : 0;
+ int deltaTop = ((openSides != null) && openSides.contains(Side.TOP)) ? openDelta
+ : 0;
+ int deltaBottom = ((openSides != null) && openSides
+ .contains(Side.BOTTOM)) ? openDelta : 0;
+
+ // System.err.println(key);
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(button)) / 2.0);
+ Shape contour = shaper.getButtonOutline(button, new Insets(borderDelta,
+ borderDelta, borderDelta, borderDelta), width + deltaLeft
+ + deltaRight, height + deltaTop + deltaBottom, false);
+
+ BufferedImage newBackground = SubstanceCoreUtilities.getBlankImage(
+ width, height);
+ Graphics2D finalGraphics = (Graphics2D) newBackground.getGraphics();
+ finalGraphics.translate(-deltaLeft, -deltaTop);
+ if (isContentAreaFilled) {
+ fillPainter.paintContourBackground(finalGraphics, button, width
+ + deltaLeft + deltaRight, height + deltaTop + deltaBottom,
+ contour, false, colorScheme, true);
+ }
+
+ if (isBorderPainted) {
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(button));
+ Shape contourInner = borderPainter.isPaintingInnerContour() ? shaper
+ .getButtonOutline(button, new Insets(borderDelta
+ + borderThickness, borderDelta + borderThickness,
+ borderDelta + borderThickness, borderDelta
+ + borderThickness), width + deltaLeft
+ + deltaRight, height + deltaTop + deltaBottom, true)
+ : null;
+ borderPainter.paintBorder(finalGraphics, button, width + deltaLeft
+ + deltaRight, height + deltaTop + deltaBottom, contour,
+ contourInner, borderScheme);
+ }
+ return newBackground;
+ }
+
+ /**
+ * Simple constructor.
+ */
+ public ButtonBackgroundDelegate() {
+ super();
+ }
+
+ /**
+ * Updates background of the specified button.
+ *
+ * @param g
+ * Graphic context.
+ * @param button
+ * Button to update.
+ */
+ public void updateBackground(Graphics g, AbstractButton button) {
+ // failsafe for LAF change
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return;
+
+ if (SubstanceCoreUtilities.isButtonNeverPainted(button))
+ return;
+
+ int width = button.getWidth();
+ int height = button.getHeight();
+ int y = 0;
+ if (SubstanceCoreUtilities.isScrollButton(button)
+ || SubstanceCoreUtilities.isSpinnerButton(button)) {
+ Sideable sideable = (Sideable) button;
+ PairwiseButtonBackgroundDelegate.updatePairwiseBackground(g,
+ button, width, height, sideable.getSide(), false);
+ return;
+ }
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(button);
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(button);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(button);
+
+ BufferedImage bgImage = getFullAlphaBackground(button, button
+ .getModel(), shaper, fillPainter, borderPainter, width, height);
+
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) button
+ .getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ // Two special cases here:
+ // 1. Button has flat appearance.
+ // 2. Button is disabled.
+ // For both cases, we need to set custom translucency.
+ boolean isFlat = SubstanceCoreUtilities.hasFlatAppearance(button);
+ boolean isSpecial = isFlat || !button.isEnabled();
+ float extraAlpha = 1.0f;
+ if (isSpecial) {
+ if (isFlat) {
+ // Special handling of flat buttons
+ extraAlpha = 0.0f;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState.isDisabled())
+ continue;
+ if (activeState == ComponentState.ENABLED)
+ continue;
+ extraAlpha += activeEntry.getValue().getContribution();
+ }
+ } else {
+ if (!button.isEnabled()) {
+ extraAlpha = SubstanceColorSchemeUtilities.getAlpha(button,
+ modelStateInfo.getCurrModelState());
+ }
+ }
+ }
+ if (extraAlpha > 0.0f) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(button,
+ extraAlpha, g));
+ graphics.drawImage(bgImage, 0, y, null);
+ graphics.dispose();
+ }
+ }
+
+ /**
+ * Checks whether the specified button has round corners.
+ *
+ * @param button
+ * Button to check.
+ * @return <code>true</code> if the specified button has round corners,
+ * <code>false</code> otherwise.
+ */
+ public static boolean isRoundButton(AbstractButton button) {
+ return (!SubstanceCoreUtilities.isComboBoxButton(button))
+ && (!SubstanceCoreUtilities.isScrollButton(button))
+ && SubstanceCoreUtilities.hasText(button);
+ }
+
+ /**
+ * Returns <code>true</code> if the specified <i>x,y </i> location is
+ * contained within the look and feel's defined shape of the specified
+ * component. <code>x</code> and <code>y</code> are defined to be relative
+ * to the coordinate system of the specified component.
+ *
+ * @param button
+ * the component where the <i>x,y </i> location is being queried;
+ * @param x
+ * the <i>x </i> coordinate of the point
+ * @param y
+ * the <i>y </i> coordinate of the point
+ * @return <code>true</code> if the specified <i>x,y </i> location is
+ * contained within the look and feel's defined shape of the
+ * specified component, <code>false</code> otherwise.
+ */
+ public static boolean contains(AbstractButton button, int x, int y) {
+ // failsafe for LAF change
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ return false;
+ }
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(button);
+ if (shaper == null)
+ return false;
+ Shape contour = shaper.getButtonOutline(button, null,
+ button.getWidth(), button.getHeight(), false);
+ return contour.contains(x, y);
+ }
+
+ /**
+ * Returns the memory usage string.
+ *
+ * @return Memory usage string.
+ */
+ static String getMemoryUsage() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("SubstanceBackgroundDelegate: \n");
+ sb.append("\t" + regularBackgrounds.size() + " regular");
+ // + pairwiseBackgrounds.size() + " pairwise");
+ return sb.toString();
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/ButtonVisualStateTracker.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/ButtonVisualStateTracker.java
new file mode 100644
index 0000000..39840be
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/ButtonVisualStateTracker.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.utils.scroll.SubstanceScrollButton;
+import org.pushingpixels.trident.swing.SwingRepaintCallback;
+
+/**
+ * Utility class to track transitions in visual state of buttons.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ButtonVisualStateTracker {
+ /**
+ * The rollover button listener.
+ */
+ private RolloverButtonListener substanceButtonListener;
+ /**
+ * Property change listener. Listens on changes to the
+ * {@link SubstanceLookAndFeel#BUTTON_SHAPER_PROPERTY} property and
+ * {@link AbstractButton#MODEL_CHANGED_PROPERTY} property.
+ */
+ protected PropertyChangeListener substancePropertyListener;
+
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Installs tracking listeners on the specified button.
+ *
+ * @param b
+ * Button.
+ * @param toInstallRolloverListener
+ * If <code>true</code>, the button will have the rollover
+ * listener installed on it.
+ */
+ public void installListeners(final AbstractButton b,
+ boolean toInstallRolloverListener) {
+ this.stateTransitionTracker = new StateTransitionTracker(b, b
+ .getModel());
+ if (b instanceof SubstanceScrollButton) {
+ this.stateTransitionTracker
+ .setRepaintCallback(new StateTransitionTracker.RepaintCallback() {
+ @Override
+ public SwingRepaintCallback getRepaintCallback() {
+ JScrollBar scrollBar = (JScrollBar) SwingUtilities
+ .getAncestorOfClass(JScrollBar.class, b);
+ if (scrollBar != null)
+ return new SwingRepaintCallback(scrollBar);
+ return new SwingRepaintCallback(b);
+ }
+ });
+ }
+ this.stateTransitionTracker.registerModelListeners();
+ this.stateTransitionTracker.registerFocusListeners();
+ if (toInstallRolloverListener) {
+ this.substanceButtonListener = new RolloverButtonListener(b,
+ this.stateTransitionTracker);
+ b.addMouseListener(this.substanceButtonListener);
+ b.addMouseMotionListener(this.substanceButtonListener);
+ b.addFocusListener(this.substanceButtonListener);
+ b.addPropertyChangeListener(this.substanceButtonListener);
+ b.addChangeListener(this.substanceButtonListener);
+ }
+
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (AbstractButton.MODEL_CHANGED_PROPERTY.equals(evt
+ .getPropertyName())) {
+ stateTransitionTracker.setModel((ButtonModel) evt
+ .getNewValue());
+ }
+ }
+ };
+ b.addPropertyChangeListener(this.substancePropertyListener);
+ }
+
+ /**
+ * Uninstalls the tracking listeners from the specified button.
+ *
+ * @param b
+ * Button.
+ */
+ public void uninstallListeners(AbstractButton b) {
+ if (this.substanceButtonListener != null) {
+ b.removeMouseListener(this.substanceButtonListener);
+ b.removeMouseMotionListener(this.substanceButtonListener);
+ b.removeFocusListener(this.substanceButtonListener);
+ b.removePropertyChangeListener(this.substanceButtonListener);
+ b.removeChangeListener(this.substanceButtonListener);
+ this.substanceButtonListener = null;
+ }
+
+ b.removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+
+ this.stateTransitionTracker.unregisterModelListeners();
+ this.stateTransitionTracker.unregisterFocusListeners();
+ }
+
+ public StateTransitionTracker getStateTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/HashMapKey.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/HashMapKey.java
new file mode 100644
index 0000000..755a71a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/HashMapKey.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.util.Arrays;
+
+/**
+ * Implementation of a key for the {@link LazyResettableHashMap}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class HashMapKey {
+ /**
+ * Fields that represent this key object.
+ */
+ private Object[] keyFields;
+
+ /**
+ * Creates a new key object.
+ *
+ * @param fields
+ * Fields of the key object.
+ */
+ public HashMapKey(Object... fields) {
+ this.keyFields = fields;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return Arrays.deepHashCode(this.keyFields);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof HashMapKey))
+ return false;
+ HashMapKey key2 = (HashMapKey) obj;
+ return Arrays.equals(this.keyFields, key2.keyFields);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/LazyResettableHashMap.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/LazyResettableHashMap.java
new file mode 100644
index 0000000..4329584
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/LazyResettableHashMap.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import net.jcip.annotations.GuardedBy;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Lazily initialized hash map for caching images. Note that this class is
+ * <b>not</b> thread safe. In Substance, it is used only from EDT.
+ *
+ * @author Kirill Grouchnikov
+ * @param <T>
+ * Class for the stored values.
+ */
+public class LazyResettableHashMap<T> {
+
+ private static final Object staticLock = new Object();
+ private final Object instanceLock = new Object();
+
+ /**
+ * List of all existing maps.
+ */
+ @GuardedBy("staticLock")
+ private static List<LazyResettableHashMap<?>> all;
+
+ /**
+ * The delegate cache.
+ */
+ @GuardedBy("instanceLock")
+ private Map<HashMapKey, T> cache;
+
+ /**
+ * Display name of this hash map. Is used for tracking the statistics.
+ */
+ private String displayName;
+
+ /**
+ * Creates a new hash map.
+ *
+ * @param displayName
+ * Display name of the new hash map.
+ */
+ public LazyResettableHashMap(String displayName) {
+ this.displayName = displayName;
+ synchronized (staticLock) {
+ if (all == null) {
+ all = new LinkedList<LazyResettableHashMap<?>>();
+ }
+ all.add(this);
+ }
+ }
+
+ /**
+ * Creates the delegate cache if necessary.
+ */
+ private void createIfNecessary() {
+ synchronized (instanceLock) {
+ if (this.cache == null)
+ this.cache = new SoftHashMap<HashMapKey, T>();
+ }
+ }
+
+ /**
+ * Puts a new key-value pair in the map.
+ *
+ * @param key
+ * Pair key.
+ * @param entry
+ * Pair value.
+ */
+ public void put(HashMapKey key, T entry) {
+ synchronized (instanceLock) {
+ this.createIfNecessary();
+ this.cache.put(key, entry);
+ }
+ }
+
+ /**
+ * Returns the value registered for the specified key.
+ *
+ * @param key
+ * Key.
+ * @return Registered value or <code>null</code> if none.
+ */
+ public T get(HashMapKey key) {
+ synchronized (instanceLock) {
+ if (this.cache == null)
+ return null;
+ return this.cache.get(key);
+ }
+ }
+
+ /**
+ * Checks whether there is a value associated with the specified key.
+ *
+ * @param key
+ * Key.
+ * @return <code>true</code> if there is an associated value,
+ * <code>false</code> otherwise.
+ */
+ public boolean containsKey(HashMapKey key) {
+ synchronized (instanceLock) {
+ if (this.cache == null)
+ return false;
+ return this.cache.containsKey(key);
+ }
+ }
+
+ /**
+ * Returns the number of key-value pairs of this hash map.
+ *
+ * @return The number of key-value pairs of this hash map.
+ */
+ public int size() {
+ synchronized (instanceLock) {
+ if (this.cache == null)
+ return 0;
+ return this.cache.size();
+ }
+ }
+
+ /**
+ * Resets all existing hash maps.
+ */
+ public static void reset() {
+ synchronized (staticLock) {
+ if (all != null) {
+ for (LazyResettableHashMap<?> map : all) {
+ // this is too locked!
+ synchronized(map.instanceLock) {
+ //noinspection FieldAccessNotGuarded
+ if (map.cache != null)
+ //noinspection FieldAccessNotGuarded
+ map.cache.clear();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns statistical information of the existing hash maps.
+ *
+ * @return Statistical information of the existing hash maps.
+ */
+ public static List<String> getStats() {
+ synchronized (staticLock) {
+ if (all != null) {
+ List<String> result = new LinkedList<String>();
+
+ Map<String, Integer> mapCounter = new TreeMap<String, Integer>();
+ Map<String, Integer> entryCounter = new TreeMap<String, Integer>();
+
+ for (LazyResettableHashMap<?> map : all) {
+ String key = map.displayName;
+ if (!mapCounter.containsKey(key)) {
+ mapCounter.put(key, 0);
+ entryCounter.put(key, 0);
+ }
+ mapCounter.put(key, mapCounter.get(key) + 1);
+ entryCounter.put(key, entryCounter.get(key) + map.size());
+ }
+
+ for (Map.Entry<String, Integer> entry : mapCounter.entrySet()) {
+ String key = entry.getKey();
+ result.add(entry.getValue() + " " + key + " with "
+ + entryCounter.get(key) + " entries total");
+ }
+
+ return result;
+ }
+ return null;
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/LocaleChangeListener.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/LocaleChangeListener.java
new file mode 100644
index 0000000..5de1b8a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/LocaleChangeListener.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+/**
+ * Listener for the locale changes.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface LocaleChangeListener {
+ /**
+ * Called when the locale is changed.
+ */
+ public void localeChanged();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/MemoryAnalyzer.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/MemoryAnalyzer.java
new file mode 100644
index 0000000..c318f65
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/MemoryAnalyzer.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.*;
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.*;
+
+import org.pushingpixels.lafwidget.utils.TrackableThread;
+
+/**
+ * Tracer for memory usage patterns of <b>Substance</b> look-and-feel. The
+ * tracer is started when VM has <code>-Dsubstancelaf.traceFile</code> flag. The
+ * value of this flag specifies the location of trace log file. When activated,
+ * the tracer runs a thread that collects information on memory usage and
+ * appends it to the trace log file every <code>X</code> seconds. The
+ * <code>X</code> (delay) is specified in the constructor. This class is <b>for
+ * internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class MemoryAnalyzer extends TrackableThread {
+ /**
+ * Sleep delay between trace log iterations.
+ */
+ private long delay;
+
+ /**
+ * Trace logfile name.
+ */
+ private String filename;
+
+ /**
+ * Singleton instance.
+ */
+ private static MemoryAnalyzer instance;
+
+ /**
+ * If <code>true</code>, <code>this</code> tracer has received a request to
+ * stop.
+ */
+ private static boolean isStopRequest = false;
+
+ /**
+ * Usage strings collected during the sleep time.
+ */
+ private static ArrayList<String> usages;
+
+ /**
+ * Formatting object.
+ */
+ private static SimpleDateFormat sdf;
+
+ /**
+ * Simple constructor.
+ *
+ * @param delay
+ * Sleep delay between trace log iterations.
+ * @param filename
+ * Trace logfile name.
+ */
+ private MemoryAnalyzer(long delay, String filename) {
+ super();
+ this.delay = delay;
+ this.filename = filename;
+ this.setName("Substance memory analyzer");
+ }
+
+ /**
+ * Starts the memory tracing.
+ *
+ * @param delay
+ * Sleep delay between trace log iterations.
+ * @param filename
+ * Trace logfile name.
+ */
+ public static synchronized void commence(long delay, String filename) {
+ if (instance == null) {
+ instance = new MemoryAnalyzer(delay, filename);
+ usages = new ArrayList<String>();
+ // yeah, yeah, it's not multi-thread safe.
+ sdf = new SimpleDateFormat("HH:mm:ss.SSS");
+ instance.start();
+ }
+ }
+
+ /**
+ * Issues request to stop tracing.
+ */
+ @Override
+ public synchronized void requestStop() {
+ isStopRequest = true;
+ }
+
+ /**
+ * Checks whether a request to stop tracing has been issued.
+ *
+ * @return <code>true</code> if a request to stop tracing has been issued,
+ * <code>false</code> otherwise.
+ */
+ private static synchronized boolean hasStopRequest() {
+ return isStopRequest;
+ }
+
+ /**
+ * Checks whether tracer is running.
+ *
+ * @return <code>true</code> if tracer is running, <code>false</code>
+ * otherwise.
+ */
+ public static boolean isRunning() {
+ return (instance != null);
+ }
+
+ /**
+ * Adds usage string.
+ *
+ * @param usage
+ * Usage string. Will be output to the trace file at next
+ * iteration of the tracer.
+ */
+ public static synchronized void enqueueUsage(String usage) {
+ if (instance != null) {
+ usages.add(sdf.format(new Date()) + ": " + usage);
+ }
+ }
+
+ /**
+ * Returns all queued usages.
+ *
+ * @return All queued usages.
+ */
+ public static synchronized ArrayList<String> getUsages() {
+ ArrayList<String> copy = new ArrayList<String>();
+ for (String usage : usages)
+ copy.add(usage);
+ usages.clear();
+ return copy;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Runnable#run()
+ */
+ @Override
+ public void run() {
+ // output all settings from UIManager
+ // Need to run on EDT - issue 392
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ BufferedWriter bw = null;
+ try {
+ bw = new BufferedWriter(new FileWriter(new File(
+ filename), true));
+ bw.write(sdf.format(new Date()) + "\n");
+
+ UIDefaults uidefs = UIManager.getLookAndFeel()
+ .getDefaults();
+ // Retrieve the keys. Can't use an iterator since the
+ // map may be modified during the iteration. So retrieve
+ // all at once.
+ Set<Object> keySet = uidefs.keySet();
+ List<String> keyList = new LinkedList<String>();
+ for (Object key : keySet) {
+ keyList.add((String) key);
+ }
+ Collections.sort(keyList);
+
+ for (String key : keyList) {
+ Object v = uidefs.get(key);
+
+ if (v instanceof Integer) {
+ int intVal = uidefs.getInt(key);
+ bw.write(key + " (int) : " + intVal);
+ } else if (v instanceof Boolean) {
+ boolean boolVal = uidefs.getBoolean(key);
+ bw.write(key + " (bool) : " + boolVal);
+ } else if (v instanceof String) {
+ String strVal = uidefs.getString(key);
+ bw.write(key + " (string) : " + strVal);
+ } else if (v instanceof Dimension) {
+ Dimension dimVal = uidefs.getDimension(key);
+ bw.write(key + " (Dimension) : " + dimVal.width
+ + "*" + dimVal.height);
+ } else if (v instanceof Insets) {
+ Insets insetsVal = uidefs.getInsets(key);
+ bw.write(key + " (Insets) : " + insetsVal.top
+ + "*" + insetsVal.left + "*"
+ + insetsVal.bottom + "*"
+ + insetsVal.right);
+ } else if (v instanceof Color) {
+ Color colorVal = uidefs.getColor(key);
+ bw.write(key + " (int) : " + colorVal.getRed()
+ + "," + colorVal.getGreen() + ","
+ + colorVal.getBlue());
+ } else if (v instanceof Font) {
+ Font fontVal = uidefs.getFont(key);
+ bw.write(key + " (Font) : "
+ + fontVal.getFontName() + "*"
+ + fontVal.getSize());
+ } else {
+ bw
+ .write(key + " (Object) : "
+ + uidefs.get(key));
+ }
+ bw.write("\n");
+ }
+ } catch (IOException ioe) {
+ requestStop();
+ } catch (Throwable t) {
+ } finally {
+ if (bw != null) {
+ try {
+ bw.close();
+ } catch (Exception exc) {
+ }
+ }
+ }
+
+ }
+ });
+ } catch (Exception exc) {
+ requestStop();
+ }
+ BufferedWriter bw = null;
+ while (!hasStopRequest()) {
+ // gather statistics and print them to file
+ bw = null;
+ try {
+ bw = new BufferedWriter(new FileWriter(new File(this.filename),
+ true));
+ bw.write(sdf.format(new Date()) + "\n");
+ java.util.List<String> stats = LazyResettableHashMap.getStats();
+ if (stats != null) {
+ for (String stat : stats) {
+ bw.write(stat + "\n");
+ }
+ }
+ ArrayList<String> usages = getUsages();
+ for (String usage : usages) {
+ bw.write(usage + "\n");
+ }
+ bw.write("UIManager has " + UIManager.getDefaults().size()
+ + " entries\n");
+ long heapSize = Runtime.getRuntime().totalMemory();
+ long heapFreeSize = Runtime.getRuntime().freeMemory();
+
+ int heapSizeKB = (int) (heapSize / 1024);
+ int takenHeapSizeKB = (int) ((heapSize - heapFreeSize) / 1024);
+ bw.write("Heap : " + takenHeapSizeKB + " / " + heapSizeKB);
+ bw.write("\n");
+ } catch (IOException ioe) {
+ this.requestStop();
+ } finally {
+ if (bw != null) {
+ try {
+ bw.close();
+ } catch (Exception exc) {
+ }
+ }
+ }
+
+ // sleep
+ try {
+ sleep(this.delay);
+ } catch (InterruptedException ie) {
+ }
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/NoiseFactory.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/NoiseFactory.java
new file mode 100644
index 0000000..dd0acc7
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/NoiseFactory.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.Color;
+import java.awt.image.*;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceSkin;
+
+/**
+ * Factory for creating noise images. This class is part of officially supported
+ * API.
+ *
+ * @author Kirill Grouchnikov.
+ */
+public class NoiseFactory {
+ /**
+ * Returns a noise image.
+ *
+ * @param skin
+ * The skin to use for rendering the image.
+ * @param width
+ * Image width.
+ * @param height
+ * Image height.
+ * @param xFactor
+ * X stretch factor.
+ * @param yFactor
+ * Y stretch factor.
+ * @param hasConstantZ
+ * Indication whether the Z is constant.
+ * @param toBlur
+ * Indication whether the resulting image should be blurred.
+ * @param isPreview
+ * Indication whether the image is in preview mode.
+ * @return Noise image.
+ */
+ public static BufferedImage getNoiseImage(SubstanceSkin skin, int width,
+ int height, double xFactor, double yFactor, boolean hasConstantZ,
+ boolean toBlur, boolean isPreview) {
+ SubstanceColorScheme scheme = skin.getWatermarkColorScheme();
+ Color c1 = scheme.getWatermarkDarkColor();
+ // c1 = new Color(255, 0, 0, 0);
+ // System.out.println(c1.getAlpha());
+ // Color c2 = scheme.getWatermarkStampColor();
+ Color c3 = scheme.getWatermarkLightColor();
+
+ BufferedImage dst = SubstanceCoreUtilities.getBlankImage(width, height);
+ //
+ // new BufferedImage(width, height,
+ // BufferedImage.TYPE_INT_ARGB);
+
+ // Borrow from Sebastien Petrucci fast blur code - direct access
+ // to the raster data
+ int[] dstBuffer = ((DataBufferInt) dst.getRaster().getDataBuffer())
+ .getData();
+ // System.out.println((dstBuffer[0] >>> 24) & 0xFF);
+
+ double m2 = xFactor * width * xFactor * width + yFactor * height
+ * yFactor * height;
+ int pos = 0;
+ for (int j = 0; j < height; j++) {
+ double jj = yFactor * j;
+ for (int i = 0; i < width; i++) {
+ double ii = xFactor * i;
+ double z = hasConstantZ ? 1.0 : Math.sqrt(m2 - ii * ii - jj
+ * jj);
+ double noise = 0.5 + 0.5 * PerlinNoiseGenerator
+ .noise(ii, jj, z);
+
+ double likeness = Math.max(0.0, Math.min(1.0, 2.0 * noise));
+ // likeness = 0.0;
+ dstBuffer[pos++] = SubstanceColorUtilities.getInterpolatedRGB(
+ c3, c1, likeness);
+ }
+ }
+ // System.out.println((dstBuffer[0] >>> 24) & 0xFF);
+ if (toBlur) {
+ ConvolveOp convolve = new ConvolveOp(new Kernel(3, 3, new float[] {
+ .08f, .08f, .08f, .08f, .38f, .08f, .08f, .08f, .08f }),
+ ConvolveOp.EDGE_NO_OP, null);
+ dst = convolve.filter(dst, null);
+ }
+ return dst;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/PairwiseButtonBackgroundDelegate.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/PairwiseButtonBackgroundDelegate.java
new file mode 100644
index 0000000..aeb5589
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/PairwiseButtonBackgroundDelegate.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.AlphaComposite;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.image.BufferedImage;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractButton;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceConstants;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceConstants.Side;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.MatteFillPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.shaper.RectangularButtonShaper;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+
+/**
+ * Delegate class for painting backgrounds of buttons in <b>Substance </b> look
+ * and feel. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class PairwiseButtonBackgroundDelegate {
+ /**
+ * Cache for background images for pairwise backgrounds. Each time
+ * {@link #getPairwiseFullAlphaBackground(javax.swing.AbstractButton, org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter, org.pushingpixels.substance.api.shaper.SubstanceButtonShaper, int, int, org.pushingpixels.substance.api.SubstanceConstants.Side, org.pushingpixels.substance.api.SubstanceColorScheme, org.pushingpixels.substance.api.SubstanceColorScheme, boolean)} is called,
+ * it checks <code>this</code> map to see if it already contains such
+ * background. If so, the background from the map is returned.
+ */
+ private static LazyResettableHashMap<BufferedImage> pairwiseBackgrounds = new LazyResettableHashMap<BufferedImage>(
+ "PairwiseButtonBackgroundDelegate");
+
+ /**
+ * Paints background image for the specified button in button pair (such as
+ * scrollbar arrows, for example).
+ *
+ * @param g
+ * Graphics context.
+ * @param button
+ * Button.
+ * @param width
+ * Button width.
+ * @param height
+ * Button height.
+ * @param side
+ * Button orientation.
+ * @param toIgnoreOpenSides
+ * If <code>true</code>, the open side setting (controlled by the
+ * {@link SubstanceLookAndFeel#BUTTON_OPEN_SIDE_PROPERTY} is
+ * ignored.
+ */
+ public static void updatePairwiseBackground(Graphics g,
+ AbstractButton button, int width, int height,
+ SubstanceConstants.Side side, boolean toIgnoreOpenSides) {
+ if (SubstanceCoreUtilities.isButtonNeverPainted(button))
+ return;
+
+ SubstanceButtonShaper shaper = SubstanceCoreUtilities
+ .getButtonShaper(button);
+
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) button
+ .getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+
+ ComponentState currState = modelStateInfo.getCurrModelState();
+ SubstanceColorScheme baseFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, currState);
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, ColorSchemeAssociationKind.BORDER,
+ currState);
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .isSpinnerButton(button) ? MatteFillPainter.INSTANCE
+ : SubstanceImageCreator.SimplisticSoftBorderReverseFillPainter.INSTANCE;
+
+ BufferedImage baseLayer = getPairwiseFullAlphaBackground(button,
+ fillPainter, shaper, width, height, side, baseFillScheme,
+ baseBorderScheme, toIgnoreOpenSides);
+ BufferedImage fullOpacity = null;
+
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ if (currState.isDisabled() || (activeStates.size() == 1)) {
+ fullOpacity = baseLayer;
+ } else {
+ fullOpacity = SubstanceCoreUtilities.getBlankImage(baseLayer
+ .getWidth(), baseLayer.getHeight());
+ Graphics2D g2fullOpacity = fullOpacity.createGraphics();
+
+ // draw the base layer
+ g2fullOpacity.drawImage(baseLayer, 0, 0, null);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button, activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(button,
+ ColorSchemeAssociationKind.BORDER, activeState);
+ BufferedImage layer = getPairwiseFullAlphaBackground(button,
+ fillPainter, shaper, width, height, side, fillScheme,
+ borderScheme, toIgnoreOpenSides);
+
+ g2fullOpacity.setComposite(AlphaComposite.SrcOver
+ .derive(contribution));
+ g2fullOpacity.drawImage(layer, 0, 0, null);
+ }
+
+ g2fullOpacity.dispose();
+ }
+
+ boolean isFlat = SubstanceCoreUtilities.hasFlatAppearance(button);
+ boolean isSpecial = isFlat || !button.isEnabled();
+ float extraAlpha = 1.0f;
+
+ if (isSpecial) {
+ if (isFlat) {
+ // Special handling of flat buttons
+ extraAlpha = 0.0f;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState.isDisabled())
+ continue;
+ if (activeState == ComponentState.ENABLED)
+ continue;
+ extraAlpha += activeEntry.getValue().getContribution();
+ }
+ } else {
+ if (!button.isEnabled()) {
+ extraAlpha = SubstanceColorSchemeUtilities.getAlpha(button,
+ currState);
+ }
+ }
+ }
+ if (extraAlpha > 0.0f) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(button,
+ extraAlpha, g));
+ graphics.drawImage(fullOpacity, 0, 0, null);
+ graphics.dispose();
+ }
+ }
+
+ /**
+ * Retrieves background image for the specified button in button pair (such
+ * as scrollbar arrows, for example).
+ *
+ * @param button
+ * Button.
+ * @param fillPainter
+ * Gradient painter.
+ * @param width
+ * Button width.
+ * @param height
+ * Button height.
+ * @param side
+ * Button orientation.
+ * @param colorScheme
+ * The fill color scheme.
+ * @param borderScheme
+ * The border color scheme.
+ * @param toIgnoreOpenSides
+ * If <code>true</code>, the open side setting (controlled by the
+ * {@link SubstanceLookAndFeel#BUTTON_OPEN_SIDE_PROPERTY} is
+ * ignored.
+ * @return Button background image.
+ */
+ private static BufferedImage getPairwiseFullAlphaBackground(
+ AbstractButton button, SubstanceFillPainter fillPainter,
+ SubstanceButtonShaper shaper, int width, int height,
+ SubstanceConstants.Side side, SubstanceColorScheme colorScheme,
+ SubstanceColorScheme borderScheme, boolean toIgnoreOpenSides) {
+ if (SubstanceCoreUtilities.isButtonNeverPainted(button))
+ return null;
+ Set<Side> openSides = toIgnoreOpenSides ? EnumSet.noneOf(Side.class)
+ : SubstanceCoreUtilities.getSides(button,
+ SubstanceLookAndFeel.BUTTON_OPEN_SIDE_PROPERTY);
+ // boolean noBorder = SubstanceCoreUtilities.isSpinnerButton(button);
+ // && !button.getParent().isEnabled();
+ boolean isBorderPainted = button.isBorderPainted();
+ boolean isContentAreaFilled = button.isContentAreaFilled();
+
+ // buttons will be rectangular to make two scrolls (horizontal
+ // and vertical) touch the corners.
+ // int radius = 0;
+
+ float radius = 0.0f;
+ if (SubstanceCoreUtilities.isSpinnerButton(button)
+ && shaper instanceof RectangularButtonShaper) {
+ radius = ((RectangularButtonShaper) shaper).getCornerRadius(button,
+ null);
+ }
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height, side,
+ openSides, colorScheme.getDisplayName(), borderScheme
+ .getDisplayName(), button.getClass().getName(),
+ fillPainter.getDisplayName(), shaper.getDisplayName(),
+ isBorderPainted, isContentAreaFilled, radius);
+ // System.out.println("\tKey " + key);
+ BufferedImage finalBackground = pairwiseBackgrounds.get(key);
+ if (finalBackground == null) {
+ // System.out.println("\tNot found");
+
+ int deltaLeft = (openSides != null)
+ && openSides.contains(Side.LEFT) ? 3 : 0;
+ int deltaRight = (openSides != null)
+ && openSides.contains(Side.RIGHT) ? 3 : 0;
+ int deltaTop = (openSides != null) && openSides.contains(Side.TOP) ? 3
+ : 0;
+ int deltaBottom = (openSides != null)
+ && openSides.contains(Side.BOTTOM) ? 3 : 0;
+
+ GeneralPath contour = null;
+
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(button);
+
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(button)) / 2.0);
+ finalBackground = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D finalGraphics = (Graphics2D) finalBackground
+ .getGraphics();
+ // finalGraphics.setColor(Color.red);
+ // finalGraphics.fillRect(0, 0, width, height);
+ finalGraphics.translate(-deltaLeft, -deltaTop);
+ if (side != null) {
+ switch (side) {
+ case TOP:
+ case BOTTOM:
+ // rotate by 90% for better visuals
+ contour = SubstanceOutlineUtilities.getBaseOutline(height
+ + deltaTop + deltaBottom, width + deltaLeft
+ + deltaRight, radius, null, borderDelta);
+
+ int translateY = height;
+ if (SubstanceCoreUtilities.isScrollButton(button)) {
+ translateY += (1 + ((side == SubstanceConstants.Side.BOTTOM) ? 1
+ : -2));
+ }
+ AffineTransform at = AffineTransform.getTranslateInstance(
+ 0, translateY);
+ at.rotate(-Math.PI / 2);
+ finalGraphics.setTransform(at);
+
+ if (isContentAreaFilled) {
+ fillPainter.paintContourBackground(finalGraphics,
+ button, height + deltaTop + deltaBottom, width
+ + deltaLeft + deltaRight, contour,
+ false, colorScheme, true);
+ }
+ if (isBorderPainted) {
+ borderPainter.paintBorder(finalGraphics, button, height
+ + deltaTop + deltaBottom, width + deltaLeft
+ + deltaRight, contour, null, borderScheme);
+ }
+ break;
+ case RIGHT:
+ case LEFT:
+ // arrow in horizontal bar
+ contour = SubstanceOutlineUtilities.getBaseOutline(width
+ + deltaLeft + deltaRight, height + deltaTop
+ + deltaBottom, radius, null, borderDelta);
+
+ if (isContentAreaFilled) {
+ fillPainter.paintContourBackground(finalGraphics,
+ button, width + deltaLeft + deltaRight, height
+ + deltaTop + deltaBottom, contour,
+ false, colorScheme, true);
+ }
+ if (isBorderPainted) {
+ borderPainter.paintBorder(finalGraphics, button, width
+ + deltaLeft + deltaRight, height + deltaTop
+ + deltaBottom, contour, null, borderScheme);
+ }
+ break;
+ }
+ } else {
+ contour = SubstanceOutlineUtilities.getBaseOutline(width
+ + deltaLeft + deltaRight, height + deltaTop
+ + deltaBottom, radius, null, borderDelta);
+
+ fillPainter.paintContourBackground(finalGraphics, button, width
+ + deltaLeft + deltaRight, height + deltaTop
+ + deltaBottom, contour, false, colorScheme, true);
+ if (isBorderPainted) {
+ borderPainter.paintBorder(finalGraphics, button, width
+ + deltaLeft + deltaRight, height + deltaTop
+ + deltaBottom, contour, null, borderScheme);
+ }
+ }
+
+ // System.out.println("\tCreated new background " + width + ":" +
+ // height);
+ pairwiseBackgrounds.put(key, finalBackground);
+ }
+ return finalBackground;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/PerlinNoiseGenerator.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/PerlinNoiseGenerator.java
new file mode 100644
index 0000000..c4102b5
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/PerlinNoiseGenerator.java
@@ -0,0 +1,115 @@
+package org.pushingpixels.substance.internal.utils;
+
+/**
+ * A class for producing Perlin-inspired noise. The code written by Ken Perlin.
+ *
+ * @author Ken Perlin http://mrl.nyu.edu/~perlin/
+ */
+public class PerlinNoiseGenerator {
+ /**
+ * Returns noise for the specified coordinates.
+ *
+ * @param x
+ * X coordinate.
+ * @param y
+ * Y coordinate.
+ * @param z
+ * Z coordinate.
+ * @return Noise for the specified coordinates.
+ */
+ static public double noise(double x, double y, double z) {
+ int X = (int) Math.floor(x) & 255, // FIND UNIT CUBE THAT
+ Y = (int) Math.floor(y) & 255, // CONTAINS POINT.
+ Z = (int) Math.floor(z) & 255;
+ x -= Math.floor(x); // FIND RELATIVE X,Y,Z
+ y -= Math.floor(y); // OF POINT IN CUBE.
+ z -= Math.floor(z);
+ double u = fade(x), // COMPUTE FADE CURVES
+ v = fade(y), // FOR EACH OF X,Y,Z.
+ w = fade(z);
+ int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z, // HASH COORDINATES
+ // OF
+ B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; // THE 8 CUBE
+ // CORNERS,
+
+ return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), // AND ADD
+ grad(p[BA], x - 1, y, z)), // BLENDED
+ lerp(u, grad(p[AB], x, y - 1, z), // RESULTS
+ grad(p[BB], x - 1, y - 1, z))),// FROM 8
+ lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), // CORNERS
+ grad(p[BA + 1], x - 1, y, z - 1)), // OF CUBE
+ lerp(u, grad(p[AB + 1], x, y - 1, z - 1), grad(
+ p[BB + 1], x - 1, y - 1, z - 1))));
+ }
+
+ /**
+ * Fades the specified value.
+ *
+ * @param t
+ * Value to fade.
+ * @return faded value.
+ */
+ static double fade(double t) {
+ return t * t * t * (t * (t * 6 - 15) + 10);
+ }
+
+ /**
+ * Interpolates the specified value.
+ *
+ * @param t
+ * Value.
+ * @param a
+ * Starting interpolation value.
+ * @param b
+ * Ending interpolation value.
+ * @return Interpolated value.
+ */
+ static double lerp(double t, double a, double b) {
+ return a + t * (b - a);
+ }
+
+ /**
+ * @param hash
+ * @param x
+ * @param y
+ * @param z
+ * @return
+ */
+ static double grad(int hash, double x, double y, double z) {
+ int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE
+ double u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS.
+ v = h < 4 ? y : (h == 12) || (h == 14) ? x : z;
+ return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
+ }
+
+ /**
+ * Permutations.
+ */
+ static final int p[] = new int[512];
+
+ /**
+ * Permutations.
+ */
+ static final int permutation[] = { 151, 160, 137, 91, 90, 15, 131, 13, 201,
+ 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37,
+ 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62,
+ 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56,
+ 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139,
+ 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133,
+ 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25,
+ 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200,
+ 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3,
+ 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255,
+ 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42,
+ 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153,
+ 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79,
+ 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242,
+ 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249,
+ 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204,
+ 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222,
+ 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 };
+ static {
+ for (int i = 0; i < 256; i++)
+ p[256 + i] = p[i] = permutation[i];
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/RolloverButtonListener.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/RolloverButtonListener.java
new file mode 100755
index 0000000..92d54f1
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/RolloverButtonListener.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.MouseInfo;
+import java.awt.PointerInfo;
+import java.awt.event.*;
+import java.security.AccessControlException;
+
+import javax.swing.AbstractButton;
+import javax.swing.plaf.basic.BasicButtonListener;
+
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+
+/**
+ * Button listener for rollover effects. Tracks the mouse motion and focus
+ * interaction for the associated button. This class is <b>for internal use
+ * only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RolloverButtonListener extends BasicButtonListener {
+ /**
+ * If the mouse pointer is currently inside the associated button area,
+ * <code>this</code> flag is <code>true</code>.
+ */
+ private boolean isMouseInside;
+
+ /**
+ * The associated button.
+ */
+ private AbstractButton button;
+
+ private StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Simple constructor.
+ *
+ * @param b
+ * The associated button.
+ */
+ public RolloverButtonListener(AbstractButton b,
+ StateTransitionTracker stateTransitionTracker) {
+ super(b);
+ this.button = b;
+ this.isMouseInside = false;
+ this.stateTransitionTracker = stateTransitionTracker;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ super.mouseEntered(e);
+ this.isMouseInside = true;
+ boolean isMouseDrag = ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0);
+ if (!isMouseDrag) {
+ this.button.getModel().setRollover(true);
+ }
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseExited(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ super.mouseExited(e);
+ this.isMouseInside = false;
+ this.button.getModel().setRollover(false);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ super.mouseReleased(e);
+ for (ActionListener al : this.button.getActionListeners())
+ if (al instanceof SubstanceInternalFrameTitlePane.ClickListener)
+ return;
+ this.button.getModel().setRollover(this.isMouseInside);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicButtonListener#mouseMoved(java.awt.event.
+ * MouseEvent)
+ */
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ super.mouseMoved(e);
+ for (ActionListener al : this.button.getActionListeners())
+ if (al instanceof SubstanceInternalFrameTitlePane.ClickListener)
+ return;
+ this.button.getModel().setRollover(this.isMouseInside);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicButtonListener#focusGained(java.awt.event
+ * .FocusEvent)
+ */
+ @Override
+ public void focusGained(FocusEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ super.focusGained(e);
+ if (!this.button.isShowing()) {
+ // shouldn't happen. Is some lurking Swing bug
+ return;
+ }
+ try {
+ PointerInfo pi = MouseInfo.getPointerInfo();
+ if (pi == null) return; //pointer not on a graphics device
+ int px = pi.getLocation().x
+ - this.button.getLocationOnScreen().x;
+ int py = pi.getLocation().y
+ - this.button.getLocationOnScreen().y;
+ this.button.getModel()
+ .setRollover(this.button.contains(px, py));
+ } catch (AccessControlException ace) {
+ // sandbox - give up
+ }
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejavax.swing.plaf.basic.BasicButtonListener#focusLost(java.awt.event.
+ * FocusEvent)
+ */
+ @Override
+ public void focusLost(FocusEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ super.focusLost(e);
+ this.button.getModel().setRollover(false);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ super.mouseClicked(e);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ super.mouseDragged(e);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ super.mousePressed(e);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/RolloverControlListener.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/RolloverControlListener.java
new file mode 100644
index 0000000..4191eb4
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/RolloverControlListener.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.Component;
+import java.awt.event.*;
+
+import javax.swing.ButtonModel;
+
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+
+/**
+ * Control listener for rollover effects. Tracks the mouse motion interaction
+ * for the associated {@link TransitionAwareUI} control. This class is <b>for
+ * internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RolloverControlListener implements MouseListener,
+ MouseMotionListener {
+ /**
+ * If the mouse pointer is currently inside the designated area (fetched
+ * from the associated {@link #trackableUI}), <code>this</code> flag is
+ * <code>true</code>.
+ */
+ private boolean isMouseInside;
+
+ /**
+ * Surrogate model for tracking control status.
+ */
+ private ButtonModel model;
+
+ /**
+ * Object that is queried for mouse events. This object is responsible for
+ * handling the designated (hot-spot) area of the associated control.
+ */
+ private TransitionAwareUI trackableUI;
+
+ private StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Simple constructor.
+ *
+ * @param trackableUI
+ * Object that is queried for mouse events.
+ * @param model
+ * Surrogate model for tracking control status.
+ */
+ public RolloverControlListener(TransitionAwareUI trackableUI,
+ ButtonModel model) {
+ this.trackableUI = trackableUI;
+ this.model = model;
+ this.isMouseInside = false;
+ this.stateTransitionTracker = trackableUI.getTransitionTracker();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ Component component = (Component) e.getSource();
+ if (!component.isEnabled())
+ return;
+ boolean isInside = this.trackableUI.isInside(e);
+ // boolean isInsideChanged = (this.isMouseInside != isInside);
+ this.isMouseInside = isInside;
+ this.model.setRollover(isInside);
+ this.model.setEnabled(component.isEnabled());
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseExited(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ Component component = (Component) e.getSource();
+ if (!component.isEnabled())
+ return;
+ // boolean isInsideChanged = (this.isMouseInside != false);
+ this.isMouseInside = false;
+ this.model.setRollover(false);
+ this.model.setEnabled(component.isEnabled());
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ // System.out.println("mouse released [" + e.getX() + ":" + e.getY() +
+ // "]");
+ try {
+ Component component = (Component) e.getSource();
+ if (!component.isEnabled())
+ return;
+ boolean isInside = this.trackableUI.isInside(e);
+ // boolean isInsideChanged = (this.isMouseInside != isInside);
+ this.isMouseInside = isInside;
+ this.model.setRollover(this.isMouseInside);
+ this.model.setPressed(false);
+ this.model.setArmed(false);
+ this.model.setSelected(false);
+ this.model.setEnabled(component.isEnabled());
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mousePressed(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ // System.out.println("mouse pressed [" + e.getX() + ":" + e.getY()
+ // +
+ // "]");
+ Component component = (Component) e.getSource();
+ if (!component.isEnabled())
+ return;
+ boolean isInside = this.trackableUI.isInside(e);
+ // boolean isInsideChanged = (this.isMouseInside != isInside);
+ this.isMouseInside = isInside;
+ this.model.setRollover(this.isMouseInside);
+ if (this.isMouseInside) {
+ this.model.setPressed(true);
+ this.model.setArmed(true);
+ this.model.setSelected(true);
+ }
+ this.model.setEnabled(component.isEnabled());
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent
+ * )
+ */
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ // System.out.println("mouse dragged [" + e.getX() + ":" + e.getY()
+ // +
+ // "]");
+ Component component = (Component) e.getSource();
+ if (!component.isEnabled())
+ return;
+ boolean isInside = this.trackableUI.isInside(e);
+ // boolean isInsideChanged = (this.isMouseInside != isInside);
+ this.isMouseInside = isInside;
+ this.model.setEnabled(component.isEnabled());
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ // System.out.println("mouse moved [" + e.getX() + ":" + e.getY() +
+ // "]");
+ Component component = (Component) e.getSource();
+ if (!component.isEnabled())
+ return;
+ boolean isInside = this.trackableUI.isInside(e);
+ // System.out.println("inside");
+ // boolean isInsideChanged = (this.isMouseInside != isInside);
+ this.isMouseInside = isInside;
+ this.model.setRollover(isInside);
+ this.model.setEnabled(component.isEnabled());
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/RolloverMenuItemListener.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/RolloverMenuItemListener.java
new file mode 100644
index 0000000..061023e
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/RolloverMenuItemListener.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.event.MouseEvent;
+
+import javax.swing.ButtonModel;
+import javax.swing.JMenuItem;
+import javax.swing.event.MouseInputListener;
+
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+
+/**
+ * Menu item listener for rollover effects. Tracks the mouse motion interaction
+ * for the associated menu item. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RolloverMenuItemListener implements MouseInputListener {
+ /**
+ * If the mouse pointer is currently inside the associated menu item area,
+ * <code>this</code> flag is <code>true</code>.
+ */
+ private boolean isMouseInside;
+
+ /**
+ * The associated menu item.
+ */
+ private JMenuItem item;
+
+ private StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Simple constructor.
+ *
+ * @param item
+ * The associated menu item.
+ */
+ public RolloverMenuItemListener(JMenuItem item,
+ StateTransitionTracker stateTransitionTracker) {
+ this.item = item;
+ this.stateTransitionTracker = stateTransitionTracker;
+ this.isMouseInside = false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ this.isMouseInside = true;
+ this.item.getModel().setRollover(true);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseExited(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ this.isMouseInside = false;
+ this.item.getModel().setRollover(false);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ ButtonModel model = this.item.getModel();
+ model.setRollover(false);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mousePressed(MouseEvent e) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent
+ * )
+ */
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ this.item.getModel().setRollover(this.isMouseInside);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/RolloverTextControlListener.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/RolloverTextControlListener.java
new file mode 100644
index 0000000..3ad0346
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/RolloverTextControlListener.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.Component;
+import java.awt.event.*;
+
+import javax.swing.ButtonModel;
+import javax.swing.JComponent;
+
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+
+/**
+ * Control listener for rollover effects. Tracks the mouse motion interaction
+ * for the associated {@link TransitionAwareUI} control.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RolloverTextControlListener implements MouseListener,
+ MouseMotionListener, FocusListener {
+ /**
+ * If the mouse pointer is currently inside the designated area (fetched
+ * from the associated {@link #trackableUI}), <code>this</code> flag is
+ * <code>true</code>.
+ */
+ private boolean isMouseInside;
+
+ /**
+ * Surrogate model for tracking control status.
+ */
+ private ButtonModel model;
+
+ /**
+ * Object that is queried for mouse events. This object is responsible for
+ * handling the designated (hot-spot) area of the associated control.
+ */
+ private TransitionAwareUI trackableUI;
+
+ private StateTransitionTracker stateTransitionTracker;
+
+ private JComponent component;
+
+ /**
+ * Simple constructor.
+ *
+ * @param trackableUI
+ * Object that is queried for mouse events.
+ * @param model
+ * Surrogate model for tracking control status.
+ */
+ public RolloverTextControlListener(JComponent component,
+ TransitionAwareUI trackableUI, ButtonModel model) {
+ this.component = component;
+ this.trackableUI = trackableUI;
+ this.model = model;
+ this.isMouseInside = false;
+ this.stateTransitionTracker = this.trackableUI.getTransitionTracker();
+ }
+
+ public void registerListeners() {
+ this.component.addMouseListener(this);
+ this.component.addMouseMotionListener(this);
+ this.component.addFocusListener(this);
+ }
+
+ public void unregisterListeners() {
+ this.component.removeMouseListener(this);
+ this.component.removeMouseMotionListener(this);
+ this.component.removeFocusListener(this);
+ }
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ this.model.setSelected(true);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ this.model.setSelected(false);
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ Component component = (Component) e.getSource();
+ if (!component.isEnabled())
+ return;
+ boolean isInside = this.trackableUI.isInside(e);
+ // boolean isInsideChanged = (this.isMouseInside != isInside);
+ this.isMouseInside = isInside;
+ this.model.setRollover(isInside);
+ this.model.setEnabled(component.isEnabled());
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseExited(MouseEvent e) {
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ Component component = (Component) e.getSource();
+ if (!component.isEnabled())
+ return;
+ // boolean isInsideChanged = (this.isMouseInside != false);
+ this.isMouseInside = false;
+ this.model.setRollover(false);
+ this.model.setEnabled(component.isEnabled());
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ // System.out.println("mouse released [" + e.getX() + ":" + e.getY() +
+ // "]");
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ Component component = (Component) e.getSource();
+ if (!component.isEnabled())
+ return;
+ boolean isInside = this.trackableUI.isInside(e);
+ // boolean isInsideChanged = (this.isMouseInside != isInside);
+ this.isMouseInside = isInside;
+ this.model.setRollover(this.isMouseInside);
+ this.model.setEnabled(component.isEnabled());
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mousePressed(MouseEvent e) {
+ // System.out.println("mouse pressed [" + e.getX() + ":" + e.getY() +
+ // "]");
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ Component component = (Component) e.getSource();
+ if (!component.isEnabled())
+ return;
+ boolean isInside = this.trackableUI.isInside(e);
+ // boolean isInsideChanged = (this.isMouseInside != isInside);
+ this.isMouseInside = isInside;
+ this.model.setRollover(this.isMouseInside);
+ this.model.setEnabled(component.isEnabled());
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent
+ * )
+ */
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ // System.out.println("mouse dragged [" + e.getX() + ":" + e.getY() +
+ // "]");
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ Component component = (Component) e.getSource();
+ if (!component.isEnabled())
+ return;
+ boolean isInside = this.trackableUI.isInside(e);
+ // boolean isInsideChanged = (this.isMouseInside != isInside);
+ this.isMouseInside = isInside;
+ this.model.setEnabled(component.isEnabled());
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ // System.out.println("mouse moved [" + e.getX() + ":" + e.getY() +
+ // "]");
+ this.stateTransitionTracker.turnOffModelChangeTracking();
+ try {
+ Component component = (Component) e.getSource();
+ if (!component.isEnabled())
+ return;
+ boolean isInside = this.trackableUI.isInside(e);
+ // System.out.println("inside");
+ // boolean isInsideChanged = (this.isMouseInside != isInside);
+ this.isMouseInside = isInside;
+ this.model.setRollover(isInside);
+ this.model.setEnabled(component.isEnabled());
+ } finally {
+ this.stateTransitionTracker.onModelStateChanged();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
+ */
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/Sideable.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/Sideable.java
new file mode 100644
index 0000000..ee82c94
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/Sideable.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import org.pushingpixels.substance.api.SubstanceConstants;
+
+/**
+ * Interface for components that have a side. An example of such a component is
+ * scroll bar button or spinner button.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface Sideable {
+ /**
+ * Returns side that corresponds to the orientation of the associated
+ * button.
+ *
+ * @return Side that corresponds to the orientation of the associated
+ * button.
+ */
+ public SubstanceConstants.Side getSide();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SkinUtilities.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SkinUtilities.java
new file mode 100755
index 0000000..b5625af
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SkinUtilities.java
@@ -0,0 +1,1376 @@
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.*;
+import java.util.Locale;
+
+import javax.swing.Icon;
+import javax.swing.UIDefaults;
+import javax.swing.UIManager;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.*;
+import javax.swing.plaf.basic.BasicBorders;
+import javax.swing.plaf.basic.BasicBorders.MarginBorder;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.inputmaps.InputMapSet;
+import org.pushingpixels.substance.api.inputmaps.SubstanceInputMapUtilities;
+import org.pushingpixels.substance.api.renderers.SubstanceDefaultListCellRenderer;
+import org.pushingpixels.substance.internal.utils.border.*;
+import org.pushingpixels.substance.internal.utils.icon.*;
+import org.pushingpixels.substance.internal.utils.scroll.SubstanceScrollPaneBorder;
+
+public class SkinUtilities {
+ /**
+ * Adds skin-specific entries to the UI defaults table.
+ *
+ * @param table
+ * UI defaults table.
+ */
+ public static void addCustomEntriesToTable(UIDefaults table,
+ SubstanceSkin skin) {
+ Object menuArrowIcon = new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new MenuArrowIcon(null);
+ }
+ };
+
+ Object listCellRendererActiveValue = new UIDefaults.ActiveValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new SubstanceDefaultListCellRenderer.SubstanceUIResource();
+ }
+ };
+
+ SubstanceColorScheme mainActiveScheme = skin
+ .getActiveColorScheme(DecorationAreaType.NONE);
+ SubstanceColorScheme mainEnabledScheme = skin
+ .getEnabledColorScheme(DecorationAreaType.NONE);
+ SubstanceColorScheme mainDisabledScheme = skin
+ .getDisabledColorScheme(DecorationAreaType.NONE);
+ Color control = new ColorUIResource(
+ mainActiveScheme.getLightColor());
+ Color foregroundColor = SubstanceColorUtilities
+ .getForegroundColor(mainEnabledScheme);
+ Color backgroundActiveColor = new ColorUIResource(
+ mainActiveScheme.getBackgroundFillColor());
+ Color backgroundDefaultColor = new ColorUIResource(
+ mainEnabledScheme.getBackgroundFillColor());
+ Color textBackgroundColor = new ColorUIResource(
+ mainActiveScheme.getTextBackgroundFillColor());
+
+ Color disabledForegroundColor = SubstanceColorUtilities
+ .getForegroundColor(mainDisabledScheme);
+ Color disabledTextComponentForegroundColor = disabledForegroundColor;
+ float alpha = skin.getAlpha(null, ComponentState.DISABLED_UNSELECTED);
+ if (alpha < 1.0f) {
+ ColorUIResource defaultTextBackgroundColor = SubstanceColorUtilities
+ .getDefaultBackgroundColor(true, skin, false);
+ disabledTextComponentForegroundColor = new ColorUIResource(
+ SubstanceColorUtilities.getInterpolatedColor(
+ disabledTextComponentForegroundColor,
+ defaultTextBackgroundColor, alpha));
+ }
+
+ Color lineColor = new ColorUIResource(mainActiveScheme.getLineColor());
+
+ Color lineColorDefault = new ColorUIResource(
+ mainEnabledScheme.getLineColor());
+
+ int lcb = SubstanceColorUtilities
+ .getColorBrightness(lineColor.getRGB());
+ Color lineBwColor = new ColorUIResource(new Color(lcb, lcb, lcb));
+
+ SubstanceColorScheme textHighlightColorScheme = skin.getColorScheme(
+ (Component) null, ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
+ ComponentState.SELECTED);
+ if (textHighlightColorScheme == null) {
+ textHighlightColorScheme = skin.getColorScheme(null,
+ ComponentState.ROLLOVER_SELECTED);
+ }
+ Color selectionTextBackgroundColor = new ColorUIResource(
+ textHighlightColorScheme.getSelectionBackgroundColor());
+ Color selectionTextForegroundColor = new ColorUIResource(
+ textHighlightColorScheme.getSelectionForegroundColor());
+
+ Color selectionCellForegroundColor = new ColorUIResource(
+ textHighlightColorScheme.getForegroundColor());
+ Color selectionCellBackgroundColor = new ColorUIResource(
+ textHighlightColorScheme.getBackgroundFillColor());
+
+ Object regularMarginBorder = new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new BorderUIResource.CompoundBorderUIResource(
+ new SubstanceBorder(), new BasicBorders.MarginBorder());
+ }
+ };
+
+ Object textBorder = new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new BorderUIResource.CompoundBorderUIResource(
+ new SubstanceTextComponentBorder(
+ SubstanceSizeUtils
+ .getTextBorderInsets(SubstanceSizeUtils
+ .getControlFontSize())),
+ new BasicBorders.MarginBorder());
+ }
+ };
+
+ Object textMarginBorder = new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new BasicBorders.MarginBorder();
+ }
+ };
+
+ Object tooltipBorder = new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new SubstanceBorder(
+ SubstanceSizeUtils
+ .getToolTipBorderInsets(SubstanceSizeUtils
+ .getControlFontSize()));
+ }
+ };
+
+ Object comboBorder = new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new SubstanceBorder(
+ SubstanceSizeUtils
+ .getComboBorderInsets(SubstanceSizeUtils
+ .getControlFontSize()));
+ }
+ };
+
+ Object spinnerBorder = new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new BorderUIResource.CompoundBorderUIResource(
+ new SubstanceTextComponentBorder(
+ SubstanceSizeUtils
+ .getSpinnerBorderInsets(SubstanceSizeUtils
+ .getControlFontSize())),
+ new BasicBorders.MarginBorder());
+ }
+ };
+
+ // SubstanceColorSchemeBundle titlePaneBundle =
+ // skin.colorSchemeBundleMap
+ // .containsKey(DecorationAreaType.PRIMARY_TITLE_PANE) ?
+ // skin.colorSchemeBundleMap
+ // .get(DecorationAreaType.PRIMARY_TITLE_PANE)
+ // : skin.colorSchemeBundleMap.get(DecorationAreaType.NONE);
+ final SubstanceColorScheme titlePaneScheme = skin
+ .getBackgroundColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE);
+ // /skin.getColorScheme(
+ // DecorationAreaType.PRIMARY_TITLE_PANE,
+ // ColorSchemeAssociationKind.FILL, ComponentState.ACTIVE);
+ //
+ // titlePaneBundle.getActiveColorScheme();
+
+ Object menuItemInsets = new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ int menuItemMargin = SubstanceSizeUtils
+ .getMenuItemMargin(SubstanceSizeUtils
+ .getComponentFontSize(null));
+ return new InsetsUIResource(menuItemMargin, menuItemMargin,
+ menuItemMargin, menuItemMargin);
+ }
+ };
+
+ Object emptyIcon = new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new IconUIResource(new Icon() {
+ @Override
+ public int getIconHeight() {
+ // return the value that matches the core height, so
+ // that the DefaultTreeCellEditor.EditorContainer
+ // returns the correct value in its getPreferredSize
+ // when it consults the "editingIcon" height.
+ return 16;
+ }
+
+ @Override
+ public int getIconWidth() {
+ return 2;
+ }
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ }
+ });
+ }
+ };
+
+ Object[] defaults = new Object[] {
+ "activeCaption",
+ new ColorUIResource(skin.getBackgroundColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE).getBackgroundFillColor()),
+
+ "activeCaptionBorder",
+ new ColorUIResource(skin.getColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE, ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED).getBackgroundFillColor()),
+
+ "activeCaptionText",
+ SubstanceColorUtilities.getForegroundColor(skin.getBackgroundColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE)),
+
+ "control",
+ new ColorUIResource(mainEnabledScheme.getBackgroundFillColor()),
+
+ "controlDkShadow",
+ new ColorUIResource(mainEnabledScheme.getUltraDarkColor()),
+
+ "controlHighlight",
+ new ColorUIResource(mainEnabledScheme.getLightColor()),
+
+ "controlLtHighlight",
+ new ColorUIResource(mainEnabledScheme.getExtraLightColor()),
+
+ "controlShadow",
+ new ColorUIResource(mainEnabledScheme.getDarkColor()),
+
+ "controlText",
+ new ColorUIResource(mainEnabledScheme.getForegroundColor()),
+
+ "desktop",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "inactiveCaption",
+ new ColorUIResource(skin.getBackgroundColorScheme(
+ UIManager.getBoolean(SubstanceLookAndFeel.WINDOW_AUTO_DEACTIVATE)
+ ? DecorationAreaType.PRIMARY_TITLE_PANE
+ : DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE
+ ).getBackgroundFillColor()),
+
+ "inactiveCaptionBorder",
+ new ColorUIResource(skin.getColorScheme(
+ UIManager.getBoolean(SubstanceLookAndFeel.WINDOW_AUTO_DEACTIVATE)
+ ? DecorationAreaType.PRIMARY_TITLE_PANE
+ : DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED).getBackgroundFillColor()),
+
+ "inactiveCaptionText",
+ SubstanceColorUtilities.getForegroundColor(skin.getBackgroundColorScheme(
+ UIManager.getBoolean(SubstanceLookAndFeel.WINDOW_AUTO_DEACTIVATE)
+ ? DecorationAreaType.PRIMARY_TITLE_PANE
+ : DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE)),
+
+ "info", // tooltip
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "infoText",
+ foregroundColor,
+
+ "menu",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "menuText",
+ foregroundColor,
+
+ "scrollbar",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "text",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ false),
+
+ "textHighlight",
+ selectionTextBackgroundColor,
+
+ "textHighlightText",
+ selectionTextForegroundColor,
+
+ "textInactiveText",
+ disabledTextComponentForegroundColor,
+
+ "textText",
+ foregroundColor,
+
+ "window",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "windowBorder",
+ control,
+
+ "windowText",
+ foregroundColor,
+
+
+ "Button.defaultButtonFollowsFocus",
+ Boolean.FALSE,
+
+ "Button.disabledText",
+ disabledForegroundColor,
+
+ "Button.foreground",
+ foregroundColor,
+
+ "Button.margin",
+ new InsetsUIResource(0, 0, 0, 0),
+
+ "CheckBox.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "CheckBox.border",
+ new BorderUIResource.CompoundBorderUIResource(
+ SubstanceSizeUtils.getCheckBoxBorder(
+ SubstanceSizeUtils.getControlFontSize(),
+ ComponentOrientation.getOrientation(
+ Locale.getDefault()).isLeftToRight()),
+ new MarginBorder()),
+
+ "CheckBox.disabledText",
+ disabledForegroundColor,
+
+ "CheckBox.foreground",
+ foregroundColor,
+
+ "CheckBoxMenuItem.acceleratorForeground",
+ foregroundColor,
+
+ "CheckBoxMenuItem.acceleratorSelectionForeground",
+ foregroundColor,
+
+ "CheckBoxMenuItem.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "CheckBoxMenuItem.borderPainted",
+ Boolean.FALSE,
+
+ "CheckBoxMenuItem.checkIcon",
+ new CheckBoxMenuItemIcon(null,
+ 1 + SubstanceSizeUtils
+ .getMenuCheckMarkSize(SubstanceSizeUtils
+ .getControlFontSize())),
+
+ "CheckBoxMenuItem.disabledForeground",
+ disabledForegroundColor,
+
+ "CheckBoxMenuItem.foreground",
+ foregroundColor,
+
+ "CheckBoxMenuItem.margin",
+ menuItemInsets,
+
+ "CheckBoxMenuItem.selectionForeground",
+ selectionCellForegroundColor,
+
+ "ColorChooser.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "ColorChooser.foreground",
+ foregroundColor,
+
+ "ComboBox.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "ComboBox.border",
+ comboBorder,
+
+ "ComboBox.disabledBackground",
+ textBackgroundColor,
+
+ "ComboBox.disabledForeground",
+ disabledForegroundColor,
+
+ "ComboBox.foreground",
+ foregroundColor,
+
+ "ComboBox.selectionBackground",
+ selectionCellBackgroundColor,
+
+ "ComboBox.selectionForeground",
+ selectionCellForegroundColor,
+
+ "DesktopIcon.border",
+ regularMarginBorder,
+
+ "DesktopIcon.width",
+ 140,
+
+ "Desktop.background",
+ new ColorUIResource(new Color(0x0, true)),
+
+ "Desktop.foreground",
+ foregroundColor,
+
+ "Dialog.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "EditorPane.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ false),
+
+ "EditorPane.border",
+ textMarginBorder,
+
+ "EditorPane.foreground",
+ foregroundColor,
+
+ "EditorPane.caretForeground",
+ foregroundColor,
+
+ "EditorPane.disabledBackground",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ true),
+
+ "EditorPane.inactiveBackground",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ true),
+
+ "EditorPane.inactiveForeground",
+ disabledTextComponentForegroundColor,
+
+ "EditorPane.selectionBackground",
+ selectionTextBackgroundColor,
+
+ "EditorPane.selectionForeground",
+ selectionTextForegroundColor,
+
+ "FileChooser.upFolderIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/go-up.png");
+ }
+ },
+
+ "FileChooser.newFolderIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/folder-new.png");
+ }
+ },
+
+ "FileChooser.homeFolderIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/user-home.png");
+ }
+ },
+
+ "FileChooser.listViewIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/application_view_list.png");
+ }
+ },
+
+ "FileChooser.detailsViewIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/application_view_detail.png");
+ }
+ },
+
+ "FileChooser.usesSingleFilePane",
+ Boolean.TRUE,
+
+ "FileView.computerIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/computer.png");
+ }
+ },
+
+ "FileView.directoryIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/folder.png");
+ }
+ },
+
+ "FileView.fileIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/text-x-generic.png");
+ }
+ },
+
+ "FileView.floppyDriveIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/media-floppy.png");
+ }
+ },
+
+ "FileView.hardDriveIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/drive-harddisk.png");
+ }
+ },
+
+ "FormattedTextField.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ false),
+
+ "FormattedTextField.border",
+ textBorder,
+
+ "FormattedTextField.caretForeground",
+ foregroundColor,
+
+ "FormattedTextField.disabledBackground",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ true),
+
+ "FormattedTextField.foreground",
+ foregroundColor,
+
+ "FormattedTextField.inactiveBackground",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ true),
+
+ "FormattedTextField.inactiveForeground",
+ disabledTextComponentForegroundColor,
+
+ "FormattedTextField.selectionBackground",
+ selectionTextBackgroundColor,
+
+ "FormattedTextField.selectionForeground",
+ selectionTextForegroundColor,
+
+ "InternalFrame.activeTitleBackground",
+ new ColorUIResource(skin.getBackgroundColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE).getBackgroundFillColor()),
+
+ "InternalFrame.activeTitleForeground",
+ SubstanceColorUtilities.getForegroundColor(skin.getBackgroundColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE)),
+
+ "InternalFrame.inactiveTitleBackground",
+ new ColorUIResource(skin.getBackgroundColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE).getBackgroundFillColor()),
+
+ "InternalFrame.inactiveTitleForeground",
+ SubstanceColorUtilities.getForegroundColor(skin.getBackgroundColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE)),
+
+ "InternalFrame.border",
+ new BorderUIResource(new SubstancePaneBorder()),
+
+ "InternalFrame.closeIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceImageCreator.getCloseIcon(
+ titlePaneScheme, titlePaneScheme);
+ }
+ },
+
+ "InternalFrame.iconifyIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceImageCreator.getMinimizeIcon(
+ titlePaneScheme, titlePaneScheme);
+ }
+ },
+
+ "InternalFrame.maximizeIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceImageCreator.getMaximizeIcon(
+ titlePaneScheme, titlePaneScheme);
+ }
+ },
+
+ "InternalFrame.minimizeIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceImageCreator.getRestoreIcon(
+ titlePaneScheme, titlePaneScheme);
+ }
+ },
+
+ "InternalFrame.paletteCloseIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceImageCreator.getCloseIcon(
+ titlePaneScheme, titlePaneScheme);
+ }
+ },
+
+ "Label.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "Label.foreground",
+ foregroundColor,
+
+ "Label.disabledText",
+ disabledForegroundColor,
+
+ "Label.disabledForeground",
+ disabledForegroundColor,
+
+ "List.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "List.cellRenderer",
+ listCellRendererActiveValue,
+
+ "List.focusCellHighlightBorder",
+ new SubstanceBorder(new Insets(1, 1, 1, 1)),
+
+ "List.focusSelectedCellHighlightBorder",
+ new BorderUIResource.EmptyBorderUIResource(1, 1, 1, 1),
+
+ "List.foreground",
+ foregroundColor,
+
+ "List.selectionBackground",
+ selectionCellBackgroundColor,
+
+ "List.selectionForeground",
+ selectionCellForegroundColor,
+
+ "Menu.arrowIcon",
+ menuArrowIcon,
+
+ "Menu.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "Menu.borderPainted",
+ Boolean.FALSE,
+
+ "Menu.checkIcon",
+ null,
+
+ "Menu.disabledForeground",
+ disabledForegroundColor,
+
+ "Menu.foreground",
+ foregroundColor,
+
+ "Menu.margin",
+ menuItemInsets,
+
+ "Menu.selectionForeground",
+ selectionCellForegroundColor,
+
+ "MenuBar.background",
+ skin.isRegisteredAsDecorationArea(DecorationAreaType.HEADER) ? new ColorUIResource(
+ skin.getActiveColorScheme(DecorationAreaType.HEADER)
+ .getMidColor()) : SubstanceColorUtilities
+ .getDefaultBackgroundColor(false, skin, false),
+
+ "MenuBar.foreground",
+ new ColorUIResource(skin.getActiveColorScheme(
+ DecorationAreaType.HEADER).getForegroundColor()),
+
+ "MenuBar.border",
+ null,
+
+ "MenuItem.acceleratorForeground",
+ foregroundColor,
+
+ "MenuItem.acceleratorSelectionForeground",
+ foregroundColor,
+
+ "MenuItem.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "MenuItem.borderPainted",
+ Boolean.FALSE,
+
+ "MenuItem.checkIcon",
+ null,
+
+ "MenuItem.disabledForeground",
+ disabledForegroundColor,
+
+ "MenuItem.foreground",
+ foregroundColor,
+
+ "MenuItem.margin",
+ menuItemInsets,
+
+ "MenuItem.selectionForeground",
+ selectionCellForegroundColor,
+
+ "OptionPane.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "OptionPane.errorIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/32/dialog-error.png");
+ }
+ },
+
+ "OptionPane.foreground",
+ foregroundColor,
+
+ "OptionPane.informationIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/32/dialog-information.png");
+ }
+ },
+
+ "OptionPane.messageForeground",
+ foregroundColor,
+
+ "OptionPane.questionIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/32/help-browser.png");
+ }
+ },
+
+ "OptionPane.warningIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return SubstanceCoreUtilities
+ .getIcon("resource/32/dialog-warning.png");
+ }
+ },
+
+ "Panel.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "Panel.foreground",
+ foregroundColor,
+
+ "PasswordField.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ false),
+
+ "PasswordField.border",
+ textBorder,
+
+ "PasswordField.caretForeground",
+ foregroundColor,
+
+ "PasswordField.disabledBackground",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ true),
+
+ "PasswordField.foreground",
+ foregroundColor,
+
+ "PasswordField.inactiveBackground",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ true),
+
+ "PasswordField.inactiveForeground",
+ disabledTextComponentForegroundColor,
+
+ "PasswordField.selectionBackground",
+ selectionTextBackgroundColor,
+
+ "PasswordField.selectionForeground",
+ selectionTextForegroundColor,
+
+ "PopupMenu.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "PopupMenu.border",
+ regularMarginBorder,
+
+ "ProgressBar.border",
+ new BorderUIResource(new SubstanceBorder()),
+
+ "ProgressBar.cycleTime",
+ 1000,
+
+ "ProgressBar.repaintInterval",
+ 50,
+
+ "ProgressBar.horizontalSize",
+ new DimensionUIResource(146,
+ SubstanceSizeUtils.getControlFontSize()),
+
+ "ProgressBar.verticalSize",
+ new DimensionUIResource(
+ SubstanceSizeUtils.getControlFontSize(), 146),
+
+ "ProgressBar.selectionBackground",
+ foregroundColor,
+
+ "ProgressBar.selectionForeground",
+ foregroundColor,
+
+ "RadioButton.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "RadioButton.border",
+ new BorderUIResource.CompoundBorderUIResource(
+ SubstanceSizeUtils.getRadioButtonBorder(
+ SubstanceSizeUtils.getControlFontSize(),
+ ComponentOrientation.getOrientation(
+ Locale.getDefault()).isLeftToRight()),
+ new MarginBorder()),
+
+ "RadioButton.foreground",
+ foregroundColor,
+
+ "RadioButton.disabledText",
+ disabledForegroundColor,
+
+ "RadioButtonMenuItem.acceleratorForeground",
+ foregroundColor,
+
+ "RadioButtonMenuItem.acceleratorSelectionForeground",
+ foregroundColor,
+
+ "RadioButtonMenuItem.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "RadioButtonMenuItem.borderPainted",
+ Boolean.FALSE,
+
+ "RadioButtonMenuItem.checkIcon",
+ new RadioButtonMenuItemIcon(null,
+ SubstanceSizeUtils
+ .getMenuCheckMarkSize(SubstanceSizeUtils
+ .getControlFontSize())),
+
+ "RadioButtonMenuItem.disabledForeground",
+ disabledForegroundColor,
+
+ "RadioButtonMenuItem.foreground",
+ foregroundColor,
+
+ "RadioButtonMenuItem.margin",
+ menuItemInsets,
+
+ "RadioButtonMenuItem.selectionForeground",
+ selectionCellForegroundColor,
+
+ "RootPane.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "RootPane.border",
+ new SubstancePaneBorder(),
+
+ "ScrollBar.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "ScrollBar.width",
+ SubstanceSizeUtils.getScrollBarWidth(SubstanceSizeUtils
+ .getControlFontSize()),
+
+ "ScrollBar.minimumThumbSize",
+ new DimensionUIResource(
+ SubstanceSizeUtils.getScrollBarWidth(SubstanceSizeUtils
+ .getControlFontSize()) - 2,
+ SubstanceSizeUtils.getScrollBarWidth(SubstanceSizeUtils
+ .getControlFontSize()) - 2),
+
+ "ScrollPane.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "ScrollPane.foreground",
+ foregroundColor,
+
+ "ScrollPane.border",
+ new SubstanceScrollPaneBorder(),
+
+ "Separator.background",
+ backgroundDefaultColor,
+
+ "Separator.foreground",
+ lineBwColor,
+
+ "Slider.altTrackColor",
+ lineColor,
+
+ "Slider.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "Slider.darkShadow",
+ lineColor,
+
+ "Slider.focus",
+ lineColor,
+
+ "Slider.focusInsets",
+ new InsetsUIResource(2, 2, 0, 2),
+
+ "Slider.foreground",
+ lineColor,
+
+ "Slider.highlight",
+ textBackgroundColor,
+
+ "Slider.shadow",
+ lineColor,
+
+ "Slider.tickColor",
+ foregroundColor,
+
+ "Spinner.arrowButtonInsets",
+ SubstanceSizeUtils
+ .getSpinnerArrowButtonInsets(SubstanceSizeUtils
+ .getControlFontSize()),
+
+ "Spinner.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ false),
+
+ "Spinner.border",
+ spinnerBorder,
+
+ "Spinner.disableOnBoundaryValues",
+ Boolean.TRUE,
+
+ "Spinner.foreground",
+ foregroundColor,
+
+ "Spinner.editorBorderPainted",
+ Boolean.TRUE,
+
+ "SplitPane.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "SplitPane.foreground",
+ foregroundColor,
+
+ "SplitPane.dividerFocusColor",
+ backgroundDefaultColor,
+
+ "SplitPaneDivider.draggingColor",
+ backgroundActiveColor,
+
+ "SplitPane.border",
+ new BorderUIResource(new EmptyBorder(0, 0, 0, 0)),
+
+ "SplitPane.dividerSize",
+ (int) (SubstanceSizeUtils.getArrowIconWidth(SubstanceSizeUtils
+ .getControlFontSize()) + SubstanceSizeUtils
+ .getAdjustedSize(
+ SubstanceSizeUtils.getControlFontSize(), -1, 6,
+ -1, true)),
+
+ "SplitPaneDivider.border",
+ new BorderUIResource(new EmptyBorder(1, 1, 1, 1)),
+
+ "TabbedPane.tabAreaBackground",
+ backgroundDefaultColor,
+
+ "TabbedPane.unselectedBackground",
+ backgroundDefaultColor,
+
+ "TabbedPane.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "TabbedPane.borderHightlightColor",
+ new ColorUIResource(mainActiveScheme.getMidColor()),
+
+ "TabbedPane.contentAreaColor",
+ null,
+
+ "TabbedPane.contentBorderInsets",
+ new InsetsUIResource(4, 4, 4, 4),
+
+ "TabbedPane.contentOpaque",
+ Boolean.FALSE,
+
+ "TabbedPane.darkShadow",
+ new ColorUIResource(skin.getColorScheme((Component) null,
+ ColorSchemeAssociationKind.BORDER,
+ ComponentState.SELECTED).getLineColor()),
+
+ "TabbedPane.focus",
+ foregroundColor,
+
+ "TabbedPane.foreground",
+ foregroundColor,
+
+ "TabbedPane.highlight",
+ new ColorUIResource(mainActiveScheme.getLightColor()),
+
+ "TabbedPane.light",
+ mainEnabledScheme.isDark() ? new ColorUIResource(
+ SubstanceColorUtilities.getAlphaColor(
+ mainEnabledScheme.getUltraDarkColor(), 100))
+ : new ColorUIResource(mainEnabledScheme.getLightColor()),
+
+ "TabbedPane.selected",
+ new ColorUIResource(mainActiveScheme.getExtraLightColor()),
+
+ "TabbedPane.selectedForeground",
+ foregroundColor,
+
+ "TabbedPane.selectHighlight",
+ new ColorUIResource(mainActiveScheme.getMidColor()),
+
+ "TabbedPane.shadow",
+ new ColorUIResource(
+ SubstanceColorUtilities.getInterpolatedColor(
+ mainEnabledScheme.getExtraLightColor(),
+ mainEnabledScheme.getLightColor(), 0.5)),
+
+ "TabbedPane.tabRunOverlay",
+ 0,
+
+ "Table.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "Table.cellNoFocusBorder",
+ new BorderUIResource.EmptyBorderUIResource(
+ SubstanceSizeUtils
+ .getDefaultBorderInsets(SubstanceSizeUtils
+ .getComponentFontSize(null))),
+
+ "Table.focusCellBackground",
+ backgroundActiveColor,
+
+ "Table.focusCellForeground",
+ foregroundColor,
+
+ "Table.focusCellHighlightBorder",
+ new SubstanceBorder(),
+
+ "Table.foreground",
+ foregroundColor,
+
+ "Table.gridColor",
+ lineColorDefault,
+
+ "Table.scrollPaneBorder",
+ new SubstanceScrollPaneBorder(),
+
+ "Table.selectionBackground",
+ selectionCellBackgroundColor,
+
+ "Table.selectionForeground",
+ selectionCellForegroundColor,
+
+ "TableHeader.cellBorder",
+ null,
+
+ "TableHeader.foreground",
+ foregroundColor,
+
+ "TableHeader.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "TextArea.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ false),
+
+ "TextArea.border",
+ textMarginBorder,
+
+ "TextArea.caretForeground",
+ foregroundColor,
+
+ "TextArea.disabledBackground",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ true),
+
+ "TextArea.foreground",
+ foregroundColor,
+
+ "TextArea.inactiveBackground",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ true),
+
+ "TextArea.inactiveForeground",
+ disabledTextComponentForegroundColor,
+
+ "TextArea.selectionBackground",
+ selectionTextBackgroundColor,
+
+ "TextArea.selectionForeground",
+ selectionTextForegroundColor,
+
+ "TextField.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ false),
+
+ "TextField.border",
+ textBorder,
+
+ "TextField.caretForeground",
+ foregroundColor,
+
+ "TextField.disabledBackground",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ true),
+
+ "TextField.foreground",
+ foregroundColor,
+
+ "TextField.inactiveBackground",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ true),
+
+ "TextField.inactiveForeground",
+ disabledTextComponentForegroundColor,
+
+ "TextField.selectionBackground",
+ selectionTextBackgroundColor,
+
+ "TextField.selectionForeground",
+ selectionTextForegroundColor,
+
+ "TextPane.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ false),
+
+ "TextPane.border",
+ textMarginBorder,
+
+ "TextPane.disabledBackground",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ true),
+
+ "TextPane.foreground",
+ foregroundColor,
+
+ "TextPane.caretForeground",
+ foregroundColor,
+
+ "TextPane.inactiveBackground",
+ SubstanceColorUtilities.getDefaultBackgroundColor(true, skin,
+ true),
+
+ "TextPane.inactiveForeground",
+ disabledTextComponentForegroundColor,
+
+ "TextPane.selectionBackground",
+ selectionTextBackgroundColor,
+
+ "TextPane.selectionForeground",
+ selectionTextForegroundColor,
+
+ "TitledBorder.titleColor",
+ foregroundColor,
+
+ "TitledBorder.border",
+ new SubstanceEtchedBorder(),
+
+ "ToggleButton.foreground",
+ foregroundColor,
+
+ "ToggleButton.disabledText",
+ disabledForegroundColor,
+
+ "ToggleButton.margin",
+ new InsetsUIResource(0, 0, 0, 0),
+
+ "ToolBar.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "ToolBar.border",
+ new BorderUIResource(new SubstanceToolBarBorder()),
+
+ "ToolBar.isRollover",
+ Boolean.TRUE,
+
+ "ToolBar.foreground",
+ foregroundColor,
+
+ "ToolBarSeparator.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "ToolBarSeparator.foreground",
+ lineBwColor,
+
+ "ToolBar.separatorSize",
+ null,
+
+ "ToolTip.border",
+ tooltipBorder,
+
+ "ToolTip.borderInactive",
+ tooltipBorder,
+
+ "ToolTip.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "ToolTip.backgroundInactive",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ true),
+
+ "ToolTip.foreground",
+ foregroundColor,
+
+ "ToolTip.foregroundInactive",
+ disabledForegroundColor,
+
+ "Tree.closedIcon",
+ emptyIcon,
+
+ "Tree.collapsedIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new IconUIResource(
+ SubstanceIconFactory.getTreeIcon(null, true));
+ }
+ },
+
+ "Tree.expandedIcon",
+ new UIDefaults.LazyValue() {
+ @Override
+ public Object createValue(UIDefaults table) {
+ return new IconUIResource(
+ SubstanceIconFactory.getTreeIcon(null, false));
+ }
+ },
+
+ "Tree.leafIcon",
+ emptyIcon,
+
+ "Tree.openIcon",
+ emptyIcon,
+
+ "Tree.background",
+ SubstanceColorUtilities.getDefaultBackgroundColor(false, skin,
+ false),
+
+ "Tree.selectionBackground", selectionCellBackgroundColor,
+
+ "Tree.foreground", foregroundColor,
+
+ "Tree.hash", lineColorDefault,
+
+ "Tree.rowHeight", 0,
+
+ "Tree.selectionBorderColor", lineColor,
+
+ "Tree.selectionForeground", selectionCellForegroundColor,
+
+ "Tree.textBackground", backgroundDefaultColor,
+
+ "Tree.textForeground", foregroundColor,
+
+ "Viewport.background", backgroundDefaultColor,
+
+ "Viewport.foreground", foregroundColor,
+
+ SubstanceLookAndFeel.WINDOW_AUTO_DEACTIVATE,
+ skin.isRegisteredAsDecorationArea(DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE),
+
+ };
+ table.putDefaults(defaults);
+
+ // input maps
+ InputMapSet inputMapSet = SubstanceInputMapUtilities
+ .getSystemInputMapSet();
+ if (inputMapSet == null) {
+ throw new IllegalStateException("Input map set is null!");
+ }
+
+ table.put("Button.focusInputMap", inputMapSet.getButtonFocusInputMap()
+ .getUiMap());
+ table.put("CheckBox.focusInputMap", inputMapSet
+ .getCheckBoxFocusInputMap().getUiMap());
+ table.put("ComboBox.ancestorInputMap", inputMapSet
+ .getComboBoxAncestorInputMap().getUiMap());
+ table.put("Desktop.ancestorInputMap", inputMapSet
+ .getDesktopAncestorInputMap().getUiMap());
+ table.put("EditorPane.focusInputMap", inputMapSet
+ .getEditorPaneFocusInputMap().getUiMap());
+ table.put("FileChooser.ancestorInputMap", inputMapSet
+ .getFileChooserAncestorInputMap().getUiMap());
+ table.put("FormattedTextField.focusInputMap", inputMapSet
+ .getFormattedTextFieldFocusInputMap().getUiMap());
+ table.put("List.focusInputMap", inputMapSet.getListFocusInputMap()
+ .getUiMap());
+ table.put("PasswordField.focusInputMap", inputMapSet
+ .getPasswordFieldFocusInputMap().getUiMap());
+ table.put("RadioButton.focusInputMap", inputMapSet
+ .getRadioButtonFocusInputMap().getUiMap());
+ table.put("RootPane.ancestorInputMap", inputMapSet
+ .getRootPaneAncestorInputMap().getUiMap());
+ table.put("ScrollBar.ancestorInputMap", inputMapSet
+ .getScrollBarAncestorInputMap().getUiMap());
+ table.put("ScrollPane.ancestorInputMap", inputMapSet
+ .getScrollPaneAncestorInputMap().getUiMap());
+ table.put("Slider.focusInputMap", inputMapSet.getSliderFocusInputMap()
+ .getUiMap());
+ table.put("Spinner.ancestorInputMap", inputMapSet
+ .getSpinnerAncestorInputMap().getUiMap());
+ table.put("SplitPane.ancestorInputMap", inputMapSet
+ .getSplitPaneAncestorInputMap().getUiMap());
+ table.put("TabbedPane.ancestorInputMap", inputMapSet
+ .getTabbedPaneAncestorInputMap().getUiMap());
+ table.put("TabbedPane.focusInputMap", inputMapSet
+ .getTabbedPaneFocusInputMap().getUiMap());
+ table.put("Table.ancestorInputMap", inputMapSet
+ .getTableAncestorInputMap().getUiMap());
+ table.put("TableHeader.ancestorInputMap", inputMapSet
+ .getTableHeaderAncestorInputMap().getUiMap());
+ table.put("TextArea.focusInputMap", inputMapSet
+ .getTextAreaFocusInputMap().getUiMap());
+ table.put("TextField.focusInputMap", inputMapSet
+ .getTextFieldFocusInputMap().getUiMap());
+ table.put("TextPane.focusInputMap", inputMapSet
+ .getTextPaneFocusInputMap().getUiMap());
+ table.put("ToggleButton.focusInputMap", inputMapSet
+ .getToggleButtonFocusInputMap().getUiMap());
+ table.put("ToolBar.ancestorInputMap", inputMapSet
+ .getToolBarAncestorInputMap().getUiMap());
+ table.put("Tree.ancestorInputMap", inputMapSet
+ .getTreeAncestorInputMap().getUiMap());
+ table.put("Tree.focusInputMap", inputMapSet.getTreeFocusInputMap()
+ .getUiMap());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SoftHashMap.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SoftHashMap.java
new file mode 100644
index 0000000..fb768e0
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SoftHashMap.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.io.Serializable;
+import java.lang.ref.*;
+import java.util.*;
+
+/**
+ * The original implementation is taken from <a
+ * href="http://www.javaspecialists.co.za/archive/Issue098.html">The Java
+ * Specialists' Newsletter [Issue 098]</a> with permission of the original
+ * author. Tweaked code by Endre Stolsvik was <a href=
+ * "https://substance.dev.java.net/servlets/ReadMsg?list=users&msgNo=1396"
+ * >contributed</a> in December 2009.
+ *
+ * @author Dr. Heinz M. Kabutz
+ * @author Endre Stolsvik
+ */
+class SoftHashMap<K, V> extends AbstractMap<K, V> implements Serializable {
+ /** The internal HashMap that will hold the SoftReference. */
+ private final Map<K, KeySoftReference<K, V>> hash = new HashMap<K, KeySoftReference<K, V>>();
+
+ /** Reference queue for cleared SoftReference objects. */
+ private final ReferenceQueue<V> queue = new ReferenceQueue<V>();
+
+ public static class KeySoftReference<K, V> extends SoftReference<V> {
+ final K key;
+
+ public KeySoftReference(K key, V referent, ReferenceQueue<V> q) {
+ super(referent, q);
+ this.key = key;
+ }
+ }
+
+ @Override
+ public V get(Object key) {
+ expungeStaleEntries();
+ V result = null;
+ // We get the SoftReference represented by that key
+ KeySoftReference<K, V> soft_ref = hash.get(key);
+ if (soft_ref != null) {
+ // From the SoftReference we get the value, which can be
+ // null if it has been garbage collected
+ result = soft_ref.get();
+ if (result == null) {
+ // If the value has been garbage collected, remove the
+ // entry from the HashMap.
+ hash.remove(key);
+ }
+ }
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void expungeStaleEntries() {
+ Reference<? extends V> ref;
+ while ((ref = queue.poll()) != null) {
+ KeySoftReference keyRef = (KeySoftReference<K, V>) ref;
+ hash.remove(keyRef.key);
+ }
+ }
+
+ @Override
+ public V put(K key, V value) {
+ expungeStaleEntries();
+ KeySoftReference<K, V> keyRef = new KeySoftReference<K, V>(key, value,
+ queue);
+ SoftReference<V> result = hash.put(key, keyRef);
+ if (result == null)
+ return null;
+ return result.get();
+ }
+
+ @Override
+ public V remove(Object key) {
+ expungeStaleEntries();
+ SoftReference<V> result = hash.remove(key);
+ if (result == null)
+ return null;
+ return result.get();
+ }
+
+ @Override
+ public void clear() {
+ hash.clear();
+ }
+
+ @Override
+ public int size() {
+ expungeStaleEntries();
+ return hash.size();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ expungeStaleEntries();
+ SoftReference<V> keyRef = hash.get(key);
+ if (keyRef != null) {
+ // From the SoftReference we get the value, which can be
+ // null if it has been garbage collected
+ V result = keyRef.get();
+ if (result != null) {
+ return true;
+ }
+ // If the value has been garbage collected, remove the
+ // entry from the HashMap.
+ hash.remove(key);
+ }
+ return false;
+ }
+
+ /**
+ * Returns a copy of the key/values in the map at the point of calling.
+ * However, setValue still sets the value in the actual SoftHashMap.
+ */
+ @Override
+ public Set<Entry<K, V>> entrySet() {
+ expungeStaleEntries();
+ Set<Entry<K, V>> result = new LinkedHashSet<Entry<K, V>>();
+ for (final Entry<K, KeySoftReference<K, V>> entry : hash.entrySet()) {
+ final V value = entry.getValue().get();
+ if (value != null) {
+ result.add(new Entry<K, V>() {
+ @Override
+ public K getKey() {
+ return entry.getKey();
+ }
+
+ @Override
+ public V getValue() {
+ return value;
+ }
+
+ @Override
+ public V setValue(V v) {
+ entry.setValue(new KeySoftReference<K, V>(entry
+ .getKey(), v, queue));
+ return value;
+ }
+ });
+ }
+ }
+ return result;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceColorResource.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceColorResource.java
new file mode 100644
index 0000000..a576fef
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceColorResource.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.Color;
+
+/**
+ * This class is used to propagate a color set by the application code to the
+ * children / related components. For example, table and table header are two
+ * different components, but are visually two parts of the same user-facing
+ * control. Setting background on the table should be propagated to the table
+ * header - unless the application code explicitly changed the background color
+ * on the table header. This is where this class comes into play - to mark the
+ * propagated color so that it can be replaced when the table background is
+ * changed from the application code, but not reset during skin switch.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceColorResource extends Color {
+ /**
+ * Creates a new Substance color resource.
+ *
+ * @param c
+ * Color.
+ */
+ public SubstanceColorResource(Color c) {
+ super(c.getRGB(), (c.getRGB() & 0xFF000000) != 0xFF000000);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceColorSchemeUtilities.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceColorSchemeUtilities.java
new file mode 100644
index 0000000..15c5082
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceColorSchemeUtilities.java
@@ -0,0 +1,780 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.AbstractButton;
+import javax.swing.JTabbedPane;
+import javax.swing.JToolBar;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SchemeBaseColors;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceColorSchemeBundle;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.colorscheme.BaseDarkColorScheme;
+import org.pushingpixels.substance.api.colorscheme.BaseLightColorScheme;
+import org.pushingpixels.substance.api.colorscheme.BottleGreenColorScheme;
+import org.pushingpixels.substance.api.colorscheme.LightGrayColorScheme;
+import org.pushingpixels.substance.api.colorscheme.MetallicColorScheme;
+import org.pushingpixels.substance.api.colorscheme.SunGlareColorScheme;
+import org.pushingpixels.substance.api.colorscheme.SunfireRedColorScheme;
+import org.pushingpixels.substance.internal.colorscheme.ShiftColorScheme;
+
+/**
+ * Utilities related to color schemes. This class is for internal use only.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceColorSchemeUtilities {
+ private static enum ColorSchemeKind {
+ LIGHT, DARK
+ }
+
+ /**
+ * Metallic skin.
+ */
+ public static final SubstanceSkin METALLIC_SKIN = getMetallicSkin();
+
+ /**
+ * Returns a metallic skin.
+ *
+ * @return Metallic skin.
+ */
+ private static SubstanceSkin getMetallicSkin() {
+ SubstanceSkin res = new SubstanceSkin() {
+ @Override
+ public String getDisplayName() {
+ return "Metallic Skin";
+ }
+ };
+ res.registerDecorationAreaSchemeBundle(new SubstanceColorSchemeBundle(
+ new MetallicColorScheme(), new MetallicColorScheme(),
+ new LightGrayColorScheme()), DecorationAreaType.NONE);
+ return res;
+ }
+
+ /**
+ * Returns a colorized version of the specified color scheme.
+ *
+ * @param component
+ * Component.
+ * @param scheme
+ * Color scheme.
+ * @param isEnabled
+ * Indicates whether the component is enabled.
+ * @return Colorized version of the specified color scheme.
+ */
+ private static SubstanceColorScheme getColorizedScheme(Component component,
+ SubstanceColorScheme scheme, boolean isEnabled) {
+ Component forQuerying = component;
+ if ((component != null)
+ && (component.getParent() != null)
+ && ((component instanceof SubstanceInternalArrowButton) || (component instanceof SubstanceTitleButton))) {
+ forQuerying = component.getParent();
+ }
+ return getColorizedScheme(component, scheme,
+ (forQuerying == null) ? null : forQuerying.getForeground(),
+ (forQuerying == null) ? null : forQuerying.getBackground(),
+ isEnabled);
+ }
+
+ /**
+ * Returns a colorized version of the specified color scheme.
+ *
+ * @param component
+ * Component.
+ * @param scheme
+ * Color scheme.
+ * @param isEnabled
+ * Indicates whether the component is enabled.
+ * @return Colorized version of the specified color scheme.
+ */
+ private static SubstanceColorScheme getColorizedScheme(Component component,
+ SubstanceColorScheme scheme, Color fgColor, Color bgColor,
+ boolean isEnabled) {
+ if (component != null) {
+ // Support for enhancement 256 - colorizing
+ // controls.
+ if (bgColor instanceof UIResource)
+ bgColor = null;
+ if (fgColor instanceof UIResource) {
+ fgColor = null;
+ }
+ if ((bgColor != null) || (fgColor != null)) {
+ double colorization = SubstanceCoreUtilities
+ .getColorizationFactor(component);
+ if (!isEnabled)
+ colorization /= 2.0;
+ if (colorization > 0.0) {
+ return ShiftColorScheme.getShiftedScheme(scheme, bgColor,
+ colorization, fgColor, colorization);
+ }
+ }
+ }
+ return scheme;
+ }
+
+ // /**
+ // * Returns a colorized version of the specified color scheme.
+ // *
+ // * @param component
+ // * Component.
+ // * @param scheme
+ // * Color scheme.
+ // * @param support
+ // * Used to compute the colorized scheme.
+ // * @param isEnabled
+ // * Indicates whether the component is enabled.
+ // * @return Colorized version of the specified color scheme.
+ // */
+ // private static SubstanceColorScheme getColorizedScheme(Component
+ // component,
+ // SubstanceColorScheme scheme, Component forQuerying,
+ // boolean isEnabled) {
+ // if (component != null) {
+ // // Support for enhancement 256 - colorizing
+ // // controls.
+ // Color bk = forQuerying.getBackground();
+ // Color fg = forQuerying.getForeground();
+ // // if (component instanceof SubstanceTitleButton) {
+ // // if ((fg != null) && (bk != null)) {
+ // // // guard for issue 322 - these are null when JavaHelp
+ // // // window is printed.
+ // // fg = SubstanceColorUtilities.getInterpolatedColor(fg, bk,
+ // // 0.5);
+ // // }
+ // // }
+ // if (bk instanceof UIResource)
+ // bk = null;
+ // if (fg instanceof UIResource) {
+ // fg = null;
+ // }
+ // if ((bk != null) || (fg != null)) {
+ // double colorization = SubstanceCoreUtilities
+ // .getColorizationFactor(component);
+ // if (!isEnabled)
+ // colorization /= 2.0;
+ // if (colorization > 0.0) {
+ // return ShiftColorScheme.getShiftedScheme(scheme, bk,
+ // colorization, fg, colorization);
+ // }
+ // }
+ // }
+ // return scheme;
+ // }
+
+ /**
+ * Returns the color scheme of the specified tabbed pane tab.
+ *
+ * @param jtp
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ * @param componentState
+ * Tab component state.
+ * @return The color scheme of the specified tabbed pane tab.
+ */
+ public static SubstanceColorScheme getColorScheme(final JTabbedPane jtp,
+ final int tabIndex, ColorSchemeAssociationKind associationKind,
+ ComponentState componentState) {
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(jtp);
+ if (skin == null) {
+ SubstanceCoreUtilities
+ .traceSubstanceApiUsage(jtp,
+ "Substance delegate used when Substance is not the current LAF");
+ }
+ SubstanceColorScheme nonColorized = skin.getColorScheme(jtp,
+ associationKind, componentState);
+ if (tabIndex >= 0) {
+ Component component = jtp.getComponentAt(tabIndex);
+ SubstanceColorScheme colorized = getColorizedScheme(component,
+ nonColorized, jtp.getForegroundAt(tabIndex), jtp
+ .getBackgroundAt(tabIndex), !componentState
+ .isDisabled());
+ return colorized;
+ } else {
+ return getColorizedScheme(jtp, nonColorized, !componentState
+ .isDisabled());
+ }
+ }
+
+ /**
+ * Returns the color scheme of the specified component.
+ *
+ * @param component
+ * Component.
+ * @param componentState
+ * Component state.
+ * @return Component color scheme.
+ */
+ public static SubstanceColorScheme getColorScheme(Component component,
+ ComponentState componentState) {
+ Component orig = component;
+ // special case - if the component is marked as flat and
+ // it is in the default state, or it is a button
+ // that is never painting its background - get the color scheme of the
+ // parent
+ boolean isButtonThatIsNeverPainted = ((component instanceof AbstractButton) && SubstanceCoreUtilities
+ .isButtonNeverPainted((AbstractButton) component));
+ if (isButtonThatIsNeverPainted
+ || (SubstanceCoreUtilities.hasFlatAppearance(component, false) && (componentState == ComponentState.ENABLED))) {
+ component = component.getParent();
+ }
+
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(component);
+ if (skin == null) {
+ SubstanceCoreUtilities
+ .traceSubstanceApiUsage(component,
+ "Substance delegate used when Substance is not the current LAF");
+ }
+ SubstanceColorScheme nonColorized = skin.getColorScheme(component,
+ componentState);
+
+ return getColorizedScheme(orig, nonColorized, !componentState
+ .isDisabled());
+ }
+
+ /**
+ * Returns the color scheme of the component.
+ *
+ * @param component
+ * Component.
+ * @param associationKind
+ * Association kind.
+ * @param componentState
+ * Component state.
+ * @return Component color scheme.
+ */
+ public static SubstanceColorScheme getColorScheme(Component component,
+ ColorSchemeAssociationKind associationKind,
+ ComponentState componentState) {
+ // special case - if the component is marked as flat and
+ // it is in the default state, get the color scheme of the parent.
+ // However, flat toolbars should be ignored, since they are
+ // the "top" level decoration area.
+ if (!(component instanceof JToolBar)
+ && SubstanceCoreUtilities.hasFlatAppearance(component, false)
+ && (componentState == ComponentState.ENABLED)) {
+ component = component.getParent();
+ }
+
+ SubstanceColorScheme nonColorized = SubstanceCoreUtilities.getSkin(
+ component).getColorScheme(component, associationKind,
+ componentState);
+ return getColorizedScheme(component, nonColorized, !componentState
+ .isDisabled());
+ }
+
+ /**
+ * Returns the color scheme of the component.
+ *
+ * @param component
+ * Component.
+ * @param componentState
+ * Component state.
+ * @return Component color scheme.
+ */
+ public static SubstanceColorScheme getActiveColorScheme(
+ Component component, ComponentState componentState) {
+ // special case - if the component is marked as flat and
+ // it is in the default state, get the color scheme of the parent.
+ // However, flat toolbars should be ignored, since they are
+ // the "top" level decoration area.
+ if (!(component instanceof JToolBar)
+ && SubstanceCoreUtilities.hasFlatAppearance(component, false)
+ && (componentState == ComponentState.ENABLED)) {
+ component = component.getParent();
+ }
+
+ SubstanceColorScheme nonColorized = SubstanceCoreUtilities.getSkin(
+ component).getActiveColorScheme(
+ SubstanceLookAndFeel.getDecorationType(component));
+ return getColorizedScheme(component, nonColorized, !componentState
+ .isDisabled());
+ }
+
+ /**
+ * Returns the alpha channel of the highlight color scheme of the component.
+ *
+ * @param component
+ * Component.
+ * @param componentState
+ * Component state.
+ * @return Highlight color scheme alpha channel.
+ */
+ public static float getHighlightAlpha(Component component,
+ ComponentState componentState) {
+ return SubstanceCoreUtilities.getSkin(component).getHighlightAlpha(
+ component, componentState);
+ }
+
+ /**
+ * Returns the alpha channel of the color scheme of the component.
+ *
+ * @param component
+ * Component.
+ * @param componentState
+ * Component state.
+ * @return Color scheme alpha channel.
+ */
+ public static float getAlpha(Component component,
+ ComponentState componentState) {
+ return SubstanceCoreUtilities.getSkin(component).getAlpha(component,
+ componentState);
+ }
+
+ /**
+ * Used as reference in attention-drawing animations. This field is <b>for
+ * internal use only</b>.
+ */
+ public final static SubstanceColorScheme YELLOW = new SunGlareColorScheme();
+
+ /**
+ * Used as reference in attention-drawing animations. This field is <b>for
+ * internal use only</b>.
+ */
+ public final static SubstanceColorScheme ORANGE = new SunfireRedColorScheme();
+
+ /**
+ * Used as reference to the green color scheme. This field is <b>for
+ * internal use only</b>.
+ */
+ public final static SubstanceColorScheme GREEN = new BottleGreenColorScheme();
+
+ public static SchemeBaseColors getBaseColorScheme(InputStream is) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+ Color ultraLight = null;
+ Color extraLight = null;
+ Color light = null;
+ Color mid = null;
+ Color dark = null;
+ Color ultraDark = null;
+ Color foreground = null;
+ try {
+ while (true) {
+ String line = reader.readLine();
+ if (line == null)
+ break;
+ String[] split = line.split("=");
+ if (split.length != 2) {
+ throw new IllegalArgumentException(
+ "Unsupported format in line " + line);
+ }
+ String key = split[0];
+ String value = split[1];
+ if ("colorUltraLight".equals(key)) {
+ if (ultraLight == null) {
+ ultraLight = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'ultraLight' should only be defined once");
+ }
+ if ("colorExtraLight".equals(key)) {
+ if (extraLight == null) {
+ extraLight = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'extraLight' should only be defined once");
+ }
+ if ("colorLight".equals(key)) {
+ if (light == null) {
+ light = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'light' should only be defined once");
+ }
+ if ("colorMid".equals(key)) {
+ if (mid == null) {
+ mid = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'mid' should only be defined once");
+ }
+ if ("colorDark".equals(key)) {
+ if (dark == null) {
+ dark = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'dark' should only be defined once");
+ }
+ if ("colorUltraDark".equals(key)) {
+ if (ultraDark == null) {
+ ultraDark = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'ultraDark' should only be defined once");
+ }
+ if ("colorForeground".equals(key)) {
+ if (foreground == null) {
+ foreground = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'foreground' should only be defined once");
+ }
+ throw new IllegalArgumentException(
+ "Unsupported format in line " + line);
+ }
+ final Color[] colors = new Color[] { ultraLight, extraLight, light,
+ mid, dark, ultraDark, foreground };
+ return new SchemeBaseColors() {
+ @Override
+ public String getDisplayName() {
+ return null;
+ }
+
+ @Override
+ public Color getUltraLightColor() {
+ return colors[0];
+ }
+
+ @Override
+ public Color getExtraLightColor() {
+ return colors[1];
+ }
+
+ @Override
+ public Color getLightColor() {
+ return colors[2];
+ }
+
+ @Override
+ public Color getMidColor() {
+ return colors[3];
+ }
+
+ @Override
+ public Color getDarkColor() {
+ return colors[4];
+ }
+
+ @Override
+ public Color getUltraDarkColor() {
+ return colors[5];
+ }
+
+ @Override
+ public Color getForegroundColor() {
+ return colors[6];
+ }
+ };
+ } catch (IOException ioe) {
+ throw new IllegalArgumentException(ioe);
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException ioe) {
+ }
+ }
+ }
+ }
+
+ public static SubstanceColorScheme getLightColorScheme(String name,
+ final Color[] colors) {
+ if (colors == null)
+ throw new IllegalArgumentException("Color encoding cannot be null");
+ if (colors.length != 7)
+ throw new IllegalArgumentException(
+ "Color encoding must have 7 components");
+ return new BaseLightColorScheme(name) {
+ @Override
+ public Color getUltraLightColor() {
+ return colors[0];
+ }
+
+ @Override
+ public Color getExtraLightColor() {
+ return colors[1];
+ }
+
+ @Override
+ public Color getLightColor() {
+ return colors[2];
+ }
+
+ @Override
+ public Color getMidColor() {
+ return colors[3];
+ }
+
+ @Override
+ public Color getDarkColor() {
+ return colors[4];
+ }
+
+ @Override
+ public Color getUltraDarkColor() {
+ return colors[5];
+ }
+
+ @Override
+ public Color getForegroundColor() {
+ return colors[6];
+ }
+ };
+ }
+
+ public static SubstanceColorScheme getDarkColorScheme(String name,
+ final Color[] colors) {
+ if (colors == null)
+ throw new IllegalArgumentException("Color encoding cannot be null");
+ if (colors.length != 7)
+ throw new IllegalArgumentException(
+ "Color encoding must have 7 components");
+ return new BaseDarkColorScheme(name) {
+ @Override
+ public Color getUltraLightColor() {
+ return colors[0];
+ }
+
+ @Override
+ public Color getExtraLightColor() {
+ return colors[1];
+ }
+
+ @Override
+ public Color getLightColor() {
+ return colors[2];
+ }
+
+ @Override
+ public Color getMidColor() {
+ return colors[3];
+ }
+
+ @Override
+ public Color getDarkColor() {
+ return colors[4];
+ }
+
+ @Override
+ public Color getUltraDarkColor() {
+ return colors[5];
+ }
+
+ @Override
+ public Color getForegroundColor() {
+ return colors[6];
+ }
+ };
+ }
+
+ public static SubstanceSkin.ColorSchemes getColorSchemes(URL url) {
+ List<SubstanceColorScheme> schemes = new ArrayList<SubstanceColorScheme>();
+
+ BufferedReader reader = null;
+ Color ultraLight = null;
+ Color extraLight = null;
+ Color light = null;
+ Color mid = null;
+ Color dark = null;
+ Color ultraDark = null;
+ Color foreground = null;
+ String name = null;
+ ColorSchemeKind kind = null;
+ boolean inColorSchemeBlock = false;
+ try {
+ reader = new BufferedReader(new InputStreamReader(url.openStream()));
+ while (true) {
+ String line = reader.readLine();
+ if (line == null)
+ break;
+
+ line = line.trim();
+ if (line.length() == 0)
+ continue;
+
+ if (line.startsWith("#")) {
+ // enhancement 476 - allow comments
+ continue;
+ }
+
+ if (line.indexOf("{") >= 0) {
+ if (inColorSchemeBlock) {
+ throw new IllegalArgumentException(
+ "Already in color scheme definition");
+ }
+ inColorSchemeBlock = true;
+ name = line.substring(0, line.indexOf("{")).trim();
+ continue;
+ }
+
+ if (line.indexOf("}") >= 0) {
+ if (!inColorSchemeBlock) {
+ throw new IllegalArgumentException(
+ "Not in color scheme definition");
+ }
+ inColorSchemeBlock = false;
+
+ if ((name == null) || (kind == null)
+ || (ultraLight == null) || (extraLight == null)
+ || (light == null) || (mid == null)
+ || (dark == null) || (ultraDark == null)
+ || (foreground == null)) {
+ throw new IllegalArgumentException(
+ "Incomplete specification");
+ }
+ Color[] colors = new Color[] { ultraLight, extraLight,
+ light, mid, dark, ultraDark, foreground };
+ if (kind == ColorSchemeKind.LIGHT) {
+ schemes.add(getLightColorScheme(name, colors));
+ } else {
+ schemes.add(getDarkColorScheme(name, colors));
+ }
+ name = null;
+ kind = null;
+ ultraLight = null;
+ extraLight = null;
+ light = null;
+ mid = null;
+ dark = null;
+ ultraDark = null;
+ foreground = null;
+ continue;
+ }
+
+ String[] split = line.split("=");
+ if (split.length != 2) {
+ throw new IllegalArgumentException(
+ "Unsupported format in line " + line);
+ }
+ String key = split[0].trim();
+ String value = split[1].trim();
+ if ("kind".equals(key)) {
+ if (kind == null) {
+ if ("Light".equals(value)) {
+ kind = ColorSchemeKind.LIGHT;
+ continue;
+ }
+ if ("Dark".equals(value)) {
+ kind = ColorSchemeKind.DARK;
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "Unsupported format in line " + line);
+ }
+ throw new IllegalArgumentException(
+ "'kind' should only be defined once");
+ }
+ if ("colorUltraLight".equals(key)) {
+ if (ultraLight == null) {
+ ultraLight = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'ultraLight' should only be defined once");
+ }
+ if ("colorExtraLight".equals(key)) {
+ if (extraLight == null) {
+ extraLight = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'extraLight' should only be defined once");
+ }
+ if ("colorLight".equals(key)) {
+ if (light == null) {
+ light = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'light' should only be defined once");
+ }
+ if ("colorMid".equals(key)) {
+ if (mid == null) {
+ mid = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'mid' should only be defined once");
+ }
+ if ("colorDark".equals(key)) {
+ if (dark == null) {
+ dark = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'dark' should only be defined once");
+ }
+ if ("colorUltraDark".equals(key)) {
+ if (ultraDark == null) {
+ ultraDark = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'ultraDark' should only be defined once");
+ }
+ if ("colorForeground".equals(key)) {
+ if (foreground == null) {
+ foreground = Color.decode(value);
+ continue;
+ }
+ throw new IllegalArgumentException(
+ "'foreground' should only be defined once");
+ }
+ throw new IllegalArgumentException(
+ "Unsupported format in line " + line);
+ }
+ ;
+ } catch (IOException ioe) {
+ throw new IllegalArgumentException(ioe);
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException ioe) {
+ }
+ }
+ }
+ return new SubstanceSkin.ColorSchemes(schemes);
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceColorUtilities.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceColorUtilities.java
new file mode 100644
index 0000000..d9bed37
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceColorUtilities.java
@@ -0,0 +1,807 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.plaf.ColorUIResource;
+import javax.swing.plaf.UIResource;
+import javax.swing.text.JTextComponent;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+
+/**
+ * Various color-related utilities. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceColorUtilities {
+ /**
+ * Returns the color of the top portion of border in control backgrounds.
+ *
+ * @param scheme
+ * The color scheme.
+ * @return The color of the top portion of border in control backgrounds.
+ */
+ public static Color getTopBorderColor(SubstanceColorScheme scheme) {
+ return scheme.getUltraDarkColor();
+ }
+
+ /**
+ * Returns the color of the middle portion of border in control backgrounds.
+ *
+ * @param scheme
+ * The color scheme.
+ * @return The color of the middle portion of border in control backgrounds.
+ */
+ public static Color getMidBorderColor(SubstanceColorScheme scheme) {
+ return scheme.getDarkColor();
+ }
+
+ /**
+ * Returns the color of the bottom portion of border in control backgrounds.
+ *
+ * @param scheme
+ * The color scheme.
+ * @return The color of the bottom portion of border in control backgrounds.
+ */
+ public static Color getBottomBorderColor(SubstanceColorScheme scheme) {
+ return SubstanceColorUtilities.getInterpolatedColor(scheme
+ .getDarkColor(), scheme.getMidColor(), 0.5);
+ }
+
+ /**
+ * Returns the color of the top portion of fill in control backgrounds.
+ *
+ * @return The color of the top portion of fill in control backgrounds.
+ */
+ public static Color getTopFillColor(SubstanceColorScheme scheme) {
+ Color c = SubstanceColorUtilities.getInterpolatedColor(scheme
+ .getDarkColor(), scheme.getMidColor(), 0.4);
+ return c;
+ }
+
+ /**
+ * Returns the color of the middle portion of fill in control backgrounds.
+ *
+ * @return The color of the middle portion of fill in control backgrounds.
+ */
+ public static Color getMidFillColor(SubstanceColorScheme scheme) {
+ return scheme.getMidColor();
+ }
+
+ /**
+ * Returns the color of the bottom portion of fill in control backgrounds.
+ *
+ * @return The color of the bottom portion of fill in control backgrounds.
+ */
+ public static Color getBottomFillColor(SubstanceColorScheme scheme) {
+ return scheme.getUltraLightColor();
+ }
+
+ /**
+ * Returns the color of the top portion of shine in control backgrounds.
+ *
+ * @return The color of the top portion of shine in control backgrounds.
+ */
+ public static Color getTopShineColor(SubstanceColorScheme scheme) {
+ return getBottomFillColor(scheme);
+ }
+
+ /**
+ * Returns the color of the bottom portion of shine in control backgrounds.
+ *
+ * @return The color of the bottom portion of shine in control backgrounds.
+ */
+ public static Color getBottomShineColor(SubstanceColorScheme scheme) {
+ return scheme.getLightColor();
+ }
+
+ /**
+ * Interpolates color.
+ *
+ * @param color1
+ * The first color
+ * @param color2
+ * The second color
+ * @param color1Likeness
+ * The closer this value is to 0.0, the closer the resulting
+ * color will be to <code>color2</code>.
+ * @return Interpolated RGB value.
+ */
+ public static int getInterpolatedRGB(Color color1, Color color2,
+ double color1Likeness) {
+ if ((color1Likeness < 0.0) || (color1Likeness > 1.0))
+ throw new IllegalArgumentException(
+ "Color likeness should be in 0.0-1.0 range [is "
+ + color1Likeness + "]");
+ int lr = color1.getRed();
+ int lg = color1.getGreen();
+ int lb = color1.getBlue();
+ int la = color1.getAlpha();
+ int dr = color2.getRed();
+ int dg = color2.getGreen();
+ int db = color2.getBlue();
+ int da = color2.getAlpha();
+
+ // using some interpolation values (such as 0.29 from issue 401)
+ // results in an incorrect final value without Math.round.
+ int r = (lr == dr) ? lr : (int) Math.round(color1Likeness * lr
+ + (1.0 - color1Likeness) * dr);
+ int g = (lg == dg) ? lg : (int) Math.round(color1Likeness * lg
+ + (1.0 - color1Likeness) * dg);
+ int b = (lb == db) ? lb : (int) Math.round(color1Likeness * lb
+ + (1.0 - color1Likeness) * db);
+ int a = (la == da) ? la : (int) Math.round(color1Likeness * la
+ + (1.0 - color1Likeness) * da);
+
+ return (a << 24) | (r << 16) | (g << 8) | b;
+ }
+
+ /**
+ * Interpolates color.
+ *
+ * @param color1
+ * The first color
+ * @param color2
+ * The second color
+ * @param color1Likeness
+ * The closer this value is to 0.0, the closer the resulting
+ * color will be to <code>color2</code>.
+ * @return Interpolated color.
+ */
+ public static Color getInterpolatedColor(Color color1, Color color2,
+ double color1Likeness) {
+ if (color1.equals(color2))
+ return color1;
+ if (color1Likeness == 1.0)
+ return color1;
+ if (color1Likeness == 0.0)
+ return color2;
+ return new Color(getInterpolatedRGB(color1, color2, color1Likeness),
+ true);
+ }
+
+ /**
+ * Inverts the specified color.
+ *
+ * @param color
+ * The original color.
+ * @return The inverted color.
+ */
+ public static Color invertColor(Color color) {
+ return new Color(255 - color.getRed(), 255 - color.getGreen(),
+ 255 - color.getBlue(), color.getAlpha());
+ }
+
+ /**
+ * Returns a negative of the specified color.
+ *
+ * @param color
+ * Color.
+ * @return Negative of the specified color.
+ */
+ public static Color getNegativeColor(Color color) {
+ return new Color(255 - color.getRed(), 255 - color.getGreen(),
+ 255 - color.getBlue(), color.getAlpha());
+ }
+
+ /**
+ * Returns a negative of the specified color.
+ *
+ * @param rgb
+ * Color RGB.
+ * @return Negative of the specified color.
+ */
+ public static int getNegativeColor(int rgb) {
+ int transp = (rgb >>> 24) & 0xFF;
+ int r = (rgb >>> 16) & 0xFF;
+ int g = (rgb >>> 8) & 0xFF;
+ int b = (rgb >>> 0) & 0xFF;
+
+ return (transp << 24) | ((255 - r) << 16) | ((255 - g) << 8)
+ | (255 - b);
+ }
+
+ /**
+ * Returns a translucent of the specified color.
+ *
+ * @param color
+ * Color.
+ * @param alpha
+ * Alpha channel value.
+ * @return Translucent of the specified color that matches the requested
+ * alpha channel value.
+ */
+ public static Color getAlphaColor(Color color, int alpha) {
+ return new Color(color.getRed(), color.getGreen(), color.getBlue(),
+ alpha);
+ }
+
+ /**
+ * Returns saturated version of the specified color.
+ *
+ * @param color
+ * Color.
+ * @param factor
+ * Saturation factor.
+ * @return Saturated color.
+ */
+ public static Color getSaturatedColor(Color color, double factor) {
+ int red = color.getRed();
+ int green = color.getGreen();
+ int blue = color.getBlue();
+ if ((red == green) || (green == blue)) {
+ // monochrome
+ return color;
+ }
+
+ float[] hsbvals = new float[3];
+ Color.RGBtoHSB(red, green, blue, hsbvals);
+ float sat = hsbvals[1];
+ if (factor > 0.0) {
+ sat = sat + (float) factor * (1.0f - sat);
+ } else {
+ sat = sat + (float) factor * sat;
+ }
+ return new Color(Color.HSBtoRGB(hsbvals[0], sat, hsbvals[2]));
+ }
+
+ /**
+ * Returns hue-shifted (in HSV space) version of the specified color.
+ *
+ * @param color
+ * Color.
+ * @param hueShift
+ * hue shift factor.
+ * @return Hue-shifted (in HSV space) color.
+ */
+ public static Color getHueShiftedColor(Color color, double hueShift) {
+ float[] hsbvals = new float[3];
+ Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(),
+ hsbvals);
+ float hue = hsbvals[0];
+ hue += hueShift;
+ if (hue < 0.0)
+ hue += 1.0;
+ if (hue > 1.0)
+ hue -= 1.0;
+ return new Color(Color.HSBtoRGB(hue, hsbvals[1], hsbvals[2]));
+ }
+
+ /**
+ * Derives a color based on the original color and a brightness source. The
+ * resulting color has the same hue and saturation as the original color,
+ * but its brightness is shifted towards the brightness of the brightness
+ * source. Thus, a light red color shifted towards dark green will become
+ * dark red.
+ *
+ * @param original
+ * Original color.
+ * @param brightnessSource
+ * Brightness source.
+ * @return Derived color that has the same hue and saturation as the
+ * original color, but its brightness is shifted towards the
+ * brightness of the brightness source.
+ */
+ public static Color deriveByBrightness(Color original,
+ Color brightnessSource) {
+ float[] hsbvalsOrig = new float[3];
+ Color.RGBtoHSB(original.getRed(), original.getGreen(), original
+ .getBlue(), hsbvalsOrig);
+ float[] hsbvalsBrightnessSrc = new float[3];
+ Color.RGBtoHSB(brightnessSource.getRed(), brightnessSource.getGreen(),
+ brightnessSource.getBlue(), hsbvalsBrightnessSrc);
+ return new Color(Color.HSBtoRGB(hsbvalsOrig[0], hsbvalsOrig[1],
+ (hsbvalsBrightnessSrc[2] + hsbvalsOrig[2]) / 2.0f));
+
+ }
+
+ /**
+ * Returns the foreground color of the specified color scheme.
+ *
+ * @param scheme
+ * Color scheme.
+ * @return Color scheme foreground color.
+ */
+ public static ColorUIResource getForegroundColor(SubstanceColorScheme scheme) {
+ return new ColorUIResource(scheme.getForegroundColor());
+ }
+
+ /**
+ * Returns lighter version of the specified color.
+ *
+ * @param color
+ * Color.
+ * @param diff
+ * Difference factor (values closer to 1.0 will produce results
+ * closer to white color).
+ * @return Lighter version of the specified color.
+ */
+ public static Color getLighterColor(Color color, double diff) {
+ int r = color.getRed() + (int) (diff * (255 - color.getRed()));
+ int g = color.getGreen() + (int) (diff * (255 - color.getGreen()));
+ int b = color.getBlue() + (int) (diff * (255 - color.getBlue()));
+ return new Color(r, g, b);
+ }
+
+ /**
+ * Returns darker version of the specified color.
+ *
+ * @param color
+ * Color.
+ * @param diff
+ * Difference factor (values closer to 1.0 will produce results
+ * closer to black color).
+ * @return Darker version of the specified color.
+ */
+ public static Color getDarkerColor(Color color, double diff) {
+ int r = (int) ((1.0 - diff) * color.getRed());
+ int g = (int) ((1.0 - diff) * color.getGreen());
+ int b = (int) ((1.0 - diff) * color.getBlue());
+ return new Color(r, g, b);
+ }
+
+ /**
+ * Returns the brightness of the specified color.
+ *
+ * @param rgb
+ * RGB value of a color.
+ * @return The brightness of the specified color.
+ */
+ public static int getColorBrightness(int rgb) {
+ int oldR = (rgb >>> 16) & 0xFF;
+ int oldG = (rgb >>> 8) & 0xFF;
+ int oldB = (rgb >>> 0) & 0xFF;
+
+ return (222 * oldR + 707 * oldG + 71 * oldB) / 1000;
+ }
+
+ /**
+ * Returns the color of the focus ring for the specified component.
+ *
+ * @param comp
+ * Component.
+ * @return The color of the focus ring for the specified component.
+ */
+ public static Color getFocusColor(Component comp,
+ TransitionAwareUI transitionAwareUI) {
+ // SubstanceColorScheme activeScheme =
+ // / SubstanceCoreUtilities.getSkin(comp).getMainActiveColorScheme(
+ // );
+ // // SubstanceColorSchemeUtilities
+ // .getColorScheme(comp, ComponentState.ACTIVE);
+
+ // if (comp instanceof AbstractButton) {
+ // AbstractButton ab = (AbstractButton) comp;
+
+ // TransitionAwareUI transitionAwareUI = (TransitionAwareUI) ab
+ // .getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+
+ ComponentState currState = modelStateInfo.getCurrModelState();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(comp, ColorSchemeAssociationKind.MARK,
+ currState);
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ return colorScheme.getFocusRingColor();
+ }
+
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ float alpha = activeEntry.getValue().getContribution();
+ SubstanceColorScheme activeColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(comp, ColorSchemeAssociationKind.MARK,
+ activeState);
+ Color activeForeground = activeColorScheme.getFocusRingColor();
+ aggrRed += alpha * activeForeground.getRed();
+ aggrGreen += alpha * activeForeground.getGreen();
+ aggrBlue += alpha * activeForeground.getBlue();
+ }
+ return new Color((int) aggrRed, (int) aggrGreen, (int) aggrBlue);
+ // }
+
+ // Color color = activeScheme.getFocusRingColor();
+ // return color;
+ }
+
+ /**
+ * Returns the color strength.
+ *
+ * @param color
+ * Color.
+ * @return Color strength.
+ */
+ public static float getColorStrength(Color color) {
+ return Math.max(getColorBrightness(color.getRGB()),
+ getColorBrightness(getNegativeColor(color.getRGB()))) / 255.0f;
+ }
+
+ /**
+ * Returns the color of mark icons (checkbox, radio button, scrollbar
+ * arrows, combo arrows, menu arrows etc) for the specified color scheme.
+ *
+ * @param colorScheme
+ * Color scheme.
+ * @param isEnabled
+ * If <code>true</code>, the mark should be painted in enabled
+ * state.
+ * @return Color of mark icons.
+ */
+ public static Color getMarkColor(SubstanceColorScheme colorScheme,
+ boolean isEnabled) {
+ if (colorScheme.isDark()) {
+ if (!isEnabled) {
+ return colorScheme.getDarkColor();
+
+ } else {
+ return getInterpolatedColor(colorScheme.getForegroundColor(),
+ colorScheme.getUltraLightColor(), 0.9);
+ }
+ } else {
+ Color color1 = isEnabled ? colorScheme.getUltraDarkColor()
+ : colorScheme.getUltraDarkColor();
+ Color color2 = isEnabled ? colorScheme.getDarkColor() : colorScheme
+ .getLightColor();
+ return getInterpolatedColor(color1, color2, 0.9);
+ }
+ }
+
+ /**
+ * Returns the foreground text color of the specified component.
+ *
+ * @param component
+ * Component.
+ * @param modelStateInfo
+ * The mode state info
+ * @return The foreground text color of the specified component.
+ */
+ public static Color getForegroundColor(Component component,
+ StateTransitionTracker.ModelStateInfo modelStateInfo) {
+ ComponentState currState = modelStateInfo.getCurrModelState();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ // special case for enabled buttons with no background -
+ // always use the color scheme for the default state.
+ if (component instanceof AbstractButton) {
+ AbstractButton button = (AbstractButton) component;
+ if (SubstanceCoreUtilities.isButtonNeverPainted(button)
+ || !button.isContentAreaFilled()
+ || (button instanceof JRadioButton)
+ || (button instanceof JCheckBox)) {
+ if (!currState.isDisabled()) {
+ currState = ComponentState.ENABLED;
+ activeStates = null;
+ }
+ }
+ }
+
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(component, currState);
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ return colorScheme.getForegroundColor();
+ }
+
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ float alpha = activeEntry.getValue().getContribution();
+ SubstanceColorScheme activeColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(component, activeState);
+ Color activeForeground = activeColorScheme.getForegroundColor();
+ aggrRed += alpha * activeForeground.getRed();
+ aggrGreen += alpha * activeForeground.getGreen();
+ aggrBlue += alpha * activeForeground.getBlue();
+ }
+ return new Color((int) aggrRed, (int) aggrGreen, (int) aggrBlue);
+ }
+
+ /**
+ * Returns the foreground text color of the specified menu component.
+ *
+ * @param menuComponent
+ * Menu component.
+ * @param modelStateInfo
+ * Model state info for the component.
+ * @return The foreground text color of the specified menu component.
+ */
+ public static Color getMenuComponentForegroundColor(Component menuComponent,
+ StateTransitionTracker.ModelStateInfo modelStateInfo) {
+ ComponentState currState = modelStateInfo
+ .getCurrModelStateNoSelection();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateNoSelectionContributionMap();
+
+ ColorSchemeAssociationKind currAssocKind = ColorSchemeAssociationKind.FILL;
+ // use HIGHLIGHT on active menu items
+ if (!currState.isDisabled() && (currState != ComponentState.ENABLED))
+ currAssocKind = ColorSchemeAssociationKind.HIGHLIGHT;
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(menuComponent, currAssocKind, currState);
+ if (currState.isDisabled() || (activeStates == null)
+ || (activeStates.size() == 1)) {
+ return colorScheme.getForegroundColor();
+ }
+
+ float aggrRed = 0;
+ float aggrGreen = 0;
+ float aggrBlue = 0;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ float alpha = activeEntry.getValue().getContribution();
+ ColorSchemeAssociationKind assocKind = ColorSchemeAssociationKind.FILL;
+ // use HIGHLIGHT on active menu items
+ if (!activeState.isDisabled()
+ && (activeState != ComponentState.ENABLED))
+ assocKind = ColorSchemeAssociationKind.HIGHLIGHT;
+ SubstanceColorScheme activeColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(menuComponent, assocKind, activeState);
+ Color activeForeground = activeColorScheme.getForegroundColor();
+ aggrRed += alpha * activeForeground.getRed();
+ aggrGreen += alpha * activeForeground.getGreen();
+ aggrBlue += alpha * activeForeground.getBlue();
+ }
+ return new Color((int) aggrRed, (int) aggrGreen, (int) aggrBlue);
+ }
+
+ /**
+ * Returns the background fill color of the specified component.
+ *
+ * @param component
+ * Component.
+ * @return The background fill color of the specified component.
+ */
+ public static Color getBackgroundFillColor(Component component) {
+ // special case - sliders, check boxes and radio buttons. For this,
+ // switch to component parent
+ if ((component instanceof JCheckBox)
+ || (component instanceof JRadioButton)
+ || (component instanceof JSlider)) {
+ // null checks on getParent() table/list/combo renderers
+ component = component.getParent() != null ? component.getParent() : component;
+ } else {
+ // Fix for 325 - respect the opacity setting of the text
+ // component
+ if (component instanceof JTextComponent && !component.isOpaque())
+ // null checks on getParent() table/list/combo renderers
+ component = component.getParent() != null ? component.getParent() : component;
+ }
+
+ Color backgr = component.getBackground();
+ // do not change the background color on cell renderers
+ if (SwingUtilities
+ .getAncestorOfClass(CellRendererPane.class, component) != null)
+ return backgr;
+
+ boolean isBackgroundUiResource = backgr instanceof UIResource;
+
+ if (!isBackgroundUiResource) {
+ // special case for issue 386 - if the colorization
+ // is 1.0, return the component background
+ if ((SubstanceCoreUtilities.getColorizationFactor(component) == 1.0f)
+ && component.isEnabled()) {
+ return backgr;
+ }
+
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(component,
+ component.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED);
+ backgr = scheme.getBackgroundFillColor();
+ } else {
+ ComponentState state = component.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED;
+ JTextComponent matchingTextComp = SubstanceCoreUtilities
+ .getTextComponentForTransitions(component);
+ if (matchingTextComp != null) {
+ component = matchingTextComp;
+ boolean isEditable = matchingTextComp.isEditable();
+ if (isEditable) {
+ state = component.isEnabled() ? EDITABLE
+ : EDITABLE_DISABLED;
+ } else {
+ state = component.isEnabled() ? UNEDITABLE
+ : UNEDITABLE_DISABLED;
+ }
+ }
+ // menu items always use the same background color so that the
+ // menu looks continuous
+ if (component instanceof JMenuItem) {
+ state = ComponentState.ENABLED;
+ }
+ backgr = SubstanceColorUtilities.getDefaultBackgroundColor(
+ component, state);
+
+ if (state.isDisabled()) {
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(component,
+ state);
+ if (alpha < 1.0f) {
+ Color defaultColor = SubstanceColorUtilities
+ .getDefaultBackgroundColor(component,
+ ComponentState.ENABLED);
+ backgr = SubstanceColorUtilities.getInterpolatedColor(
+ backgr, defaultColor, 1.0f - (1.0f - alpha) / 2.0f);
+ }
+ }
+ }
+ return backgr;
+ }
+
+ private static final ComponentState EDITABLE = new ComponentState(
+ "editable", ComponentState.ENABLED, new ComponentStateFacet[] {
+ ComponentStateFacet.ENABLE, ComponentStateFacet.EDITABLE },
+ null);
+
+ private static final ComponentState UNEDITABLE = new ComponentState(
+ "uneditable", ComponentState.DISABLED_SELECTED,
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE },
+ new ComponentStateFacet[] { ComponentStateFacet.EDITABLE });
+
+ private static final ComponentState EDITABLE_DISABLED = new ComponentState(
+ "editable disabled", ComponentState.DISABLED_UNSELECTED,
+ new ComponentStateFacet[] { ComponentStateFacet.EDITABLE },
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE });
+
+ private static final ComponentState UNEDITABLE_DISABLED = new ComponentState(
+ "uneditable disabled", ComponentState.DISABLED_UNSELECTED, null,
+ new ComponentStateFacet[] { ComponentStateFacet.ENABLE,
+ ComponentStateFacet.EDITABLE });
+
+ public static Color getOuterTextComponentBorderColor(
+ Color fillBackgroundColor) {
+ float[] hsb = Color.RGBtoHSB(fillBackgroundColor.getRed(),
+ fillBackgroundColor.getGreen(), fillBackgroundColor.getBlue(),
+ null);
+ if (hsb[2] < 0.3f) {
+ hsb[2] = 1.0f - (float) Math.pow((1.0f - hsb[2]), 1.4);
+ } else if (hsb[2] < 0.5f) {
+ hsb[2] = 1.0f - (float) Math.pow((1.0f - hsb[2]), 1.2);
+ } else if (hsb[2] < 0.75f) {
+ hsb[2] = 1.0f - (float) Math.pow((1.0f - hsb[2]), 1.7);
+ } else {
+ hsb[2] = 1.0f - (float) Math.pow((1.0f - hsb[2]), 2.0);
+ }
+ return new Color(Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]));
+ }
+
+ /**
+ * Returns the default background color for the components of the specified
+ * class.
+ *
+ * @param toTreatAsTextComponent
+ * if the component is to be colored as a text component.
+ * @param skin
+ * Skin.
+ * @param isDisabled
+ * Indication whether the result should be for disabled
+ * components.
+ * @return The default background color for the components of the specified
+ * class.
+ */
+ public static ColorUIResource getDefaultBackgroundColor(
+ boolean toTreatAsTextComponent, SubstanceSkin skin,
+ boolean isDisabled) {
+ if (toTreatAsTextComponent || isDisabled)
+ return new ColorUIResource(skin.getEnabledColorScheme(
+ DecorationAreaType.NONE).getTextBackgroundFillColor());
+ return new ColorUIResource(skin.getEnabledColorScheme(
+ DecorationAreaType.NONE).getBackgroundFillColor());
+ }
+
+ /**
+ * Returns the default background color for the specified component.
+ *
+ * @param comp
+ * Component.
+ * @param compState
+ * Component state.
+ * @return The default background color for the components of the specified
+ * class.
+ */
+ public static ColorUIResource getDefaultBackgroundColor(Component comp,
+ ComponentState compState) {
+ if (comp instanceof JTextComponent) {
+ // special case for text-based components
+ return new ColorUIResource(SubstanceColorSchemeUtilities
+ .getColorScheme(comp, compState)
+ .getTextBackgroundFillColor());
+ }
+ return new ColorUIResource(SubstanceLookAndFeel.getCurrentSkin(comp)
+ .getBackgroundColorScheme(
+ SubstanceLookAndFeel.getDecorationType(comp))
+ .getBackgroundFillColor());
+ }
+
+ /**
+ * Returns the striped background for the specified component. This method
+ * is relevant for components such as trees, tables and lists that use
+ * odd-even striping for the alternating rows.
+ *
+ * @param component
+ * Component.
+ * @param rowIndex
+ * Row index.
+ * @return The striped background for the specified component.
+ */
+ public static Color getStripedBackground(JComponent component, int rowIndex) {
+ Color backgr = getBackgroundFillColor(component);
+ if (backgr == null)
+ return null;
+
+ if (rowIndex % 2 == 0)
+ return backgr;
+ int r = backgr.getRed();
+ int g = backgr.getGreen();
+ int b = backgr.getBlue();
+ double coef = 0.96;
+ if (!component.isEnabled())
+ coef = 1.0 - (1.0 - coef) / 2.0;
+ Color darkerColor = new ColorUIResource((int) (coef * r),
+ (int) (coef * g), (int) (coef * b));
+
+ return darkerColor;
+ }
+
+ public static String encode(int number) {
+ if ((number < 0) || (number > 255))
+ throw new IllegalArgumentException("" + number);
+ String hex = "0123456789ABCDEF";
+ char c1 = hex.charAt(number / 16);
+ char c2 = hex.charAt(number % 16);
+ return c1 + "" + c2;
+ }
+
+ public static String encode(Color color) {
+ return "#" + encode(color.getRed()) + encode(color.getGreen())
+ + encode(color.getBlue());
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceCoreUtilities.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceCoreUtilities.java
new file mode 100755
index 0000000..067c1ed
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceCoreUtilities.java
@@ -0,0 +1,2282 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.*;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+import java.awt.image.VolatileImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.net.URL;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.plaf.*;
+import javax.swing.text.JTextComponent;
+
+import com.sun.awt.AWTUtilities;
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.utils.TrackableThread;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.SubstanceConstants.*;
+import org.pushingpixels.substance.api.colorscheme.*;
+import org.pushingpixels.substance.api.combo.ComboPopupPrototypeCallback;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.SubstanceDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.api.tabbed.TabCloseCallback;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.ui.*;
+import org.pushingpixels.substance.internal.utils.combo.SubstanceComboPopup;
+import org.pushingpixels.substance.internal.utils.icon.*;
+import org.pushingpixels.substance.internal.utils.menu.SubstanceMenu;
+import org.pushingpixels.substance.internal.utils.scroll.SubstanceScrollButton;
+import org.pushingpixels.trident.swing.SwingRepaintCallback;
+
+/**
+ * Various utility functions. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ * @author Romain Guy
+ */
+public class SubstanceCoreUtilities {
+ /**
+ * Client property name for marking components covered by lightweight
+ * popups. This is tracking the fix for issue 297. The client property value
+ * should be an instance of {@link Boolean}.
+ */
+ public static final String IS_COVERED_BY_LIGHTWEIGHT_POPUPS = "substancelaf.internal.paint.isCoveredByLightweightPopups";
+
+ public static final String TEXT_COMPONENT_AWARE = "substancelaf.internal.textComponentAware";
+
+ public static final boolean reallyThrow = Boolean.valueOf(System.getProperty("insubstantial.checkEDT", "false"));
+ public static final boolean reallyPrint = Boolean.valueOf(System.getProperty("insubstantial.logEDT", "true"));
+
+ public static interface TextComponentAware<T> {
+ public JTextComponent getTextComponent(T t);
+ }
+
+ /**
+ * Private constructor. Is here to enforce using static methods only.
+ */
+ private SubstanceCoreUtilities() {
+ }
+
+ /**
+ * Clips string based on specified font metrics and available width (in
+ * pixels). Returns the clipped string, which contains the beginning and the
+ * end of the input string separated by ellipses (...) in case the string is
+ * too long to fit into the specified width, and the origianl string
+ * otherwise.
+ *
+ * @param metrics
+ * Font metrics.
+ * @param availableWidth
+ * Available width in pixels.
+ * @param fullText
+ * String to clip.
+ * @return The clipped string, which contains the beginning and the end of
+ * the input string separated by ellipses (...) in case the string
+ * is too long to fit into the specified width, and the origianl
+ * string otherwise.
+ */
+ public static String clipString(FontMetrics metrics, int availableWidth,
+ String fullText) {
+
+ if (metrics.stringWidth(fullText) <= availableWidth)
+ return fullText;
+
+ String ellipses = "...";
+ int ellipsesWidth = metrics.stringWidth(ellipses);
+ if (ellipsesWidth > availableWidth)
+ return "";
+
+ String starter = "";
+ String ender = "";
+
+ int w = fullText.length();
+ int w2 = (w / 2) + (w % 2);
+ String prevTitle = "";
+ for (int i = 0; i < w2; i++) {
+ String newStarter = starter + fullText.charAt(i);
+ String newEnder = ender;
+ if ((w - i) > w2)
+ newEnder = fullText.charAt(w - i - 1) + newEnder;
+ String newTitle = newStarter + ellipses + newEnder;
+ if (metrics.stringWidth(newTitle) <= availableWidth) {
+ starter = newStarter;
+ ender = newEnder;
+ prevTitle = newTitle;
+ continue;
+ }
+ return prevTitle;
+ }
+ return fullText;
+ }
+
+ /**
+ * Checks whether the specified button has associated icon.
+ *
+ * @param button
+ * Button.
+ * @return If the button has associated icon, <code>true</code> is returned,
+ * otherwise <code>false</code>.
+ */
+ public static boolean hasIcon(AbstractButton button) {
+ return (button.getIcon() != null);
+ }
+
+ /**
+ * Checks whether the specified button has associated text.
+ *
+ * @param button
+ * Button.
+ * @return If the button has associated text, <code>true</code> is returned,
+ * otherwise <code>false</code>.
+ */
+ public static boolean hasText(AbstractButton button) {
+ String text = button.getText();
+ if ((text != null) && (text.length() > 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks and answers if the specified button is in a combo box.
+ *
+ * @param button
+ * the button to check
+ * @return <code>true</code> if in combo box, <code>false</code> otherwise
+ */
+ public static boolean isComboBoxButton(AbstractButton button) {
+ Container parent = button.getParent();
+ return (parent != null)
+ && ((parent instanceof JComboBox) || (parent.getParent() instanceof JComboBox));
+ }
+
+ /**
+ * Checks and answers if the specified button is in a scroll bar.
+ *
+ * @param button
+ * the button to check
+ * @return <code>true</code> if in scroll bar, <code>false</code> otherwise
+ */
+ public static boolean isScrollBarButton(AbstractButton button) {
+ Container parent = button.getParent();
+ return (parent != null)
+ && ((parent instanceof JScrollBar) || (parent.getParent() instanceof JScrollBar));
+ }
+
+ /**
+ * Checks and answers if the specified button is in a spinner.
+ *
+ * @param button
+ * the button to check
+ * @return <code>true</code> if in spinner, <code>false</code> otherwise
+ */
+ public static boolean isSpinnerButton(AbstractButton button) {
+ Container parent = button.getParent();
+ if (!(button instanceof SubstanceSpinnerButton))
+ return false;
+ return (parent != null)
+ && ((parent instanceof JSpinner) || (parent.getParent() instanceof JSpinner));
+ }
+
+ /**
+ * Checks and answers if the specified button is in a toolbar.
+ *
+ * @param component
+ * the button to check
+ * @return <code>true</code> if in toolbar, <code>false</code> otherwise
+ */
+ public static boolean isToolBarButton(JComponent component) {
+ if (component instanceof SubstanceDropDownButton)
+ return false;
+ if (component instanceof SubstanceSpinnerButton)
+ return false;
+ Container parent = component.getParent();
+ return (parent != null)
+ && ((parent instanceof JToolBar) || (parent.getParent() instanceof JToolBar));
+ }
+
+ /**
+ * Checks answers if the specified component is a button in a scroll
+ * control, such as scroll bar or tabbed pane (as tab scroller).
+ *
+ * @param comp
+ * The component to check
+ * @return <code>true</code> if the specified component is a button in a
+ * scroll control, <code>false</code> otherwise
+ */
+ public static boolean isScrollButton(JComponent comp) {
+ return (comp instanceof SubstanceScrollButton);
+ }
+
+ /**
+ * Checks whether the specified button never paints its background.
+ *
+ * @param button
+ * Button.
+ * @return <code>true</code> if the specified button never paints its
+ * background, <code>false</code> otherwise.
+ * @see SubstanceLookAndFeel#BUTTON_PAINT_NEVER_PROPERTY
+ */
+ public static boolean isButtonNeverPainted(JComponent button) {
+ // small optimizations for checkboxes and radio buttons
+ if (button instanceof JCheckBox)
+ return false;
+ if (button instanceof JRadioButton)
+ return false;
+
+ Object prop = button
+ .getClientProperty(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY);
+ if (prop != null) {
+ if (Boolean.TRUE.equals(prop))
+ return true;
+ if (Boolean.FALSE.equals(prop))
+ return false;
+ }
+
+ if (button != null) {
+ Container parent = button.getParent();
+ if (parent instanceof JComponent) {
+ JComponent jparent = (JComponent) parent;
+ Object flatProperty = jparent
+ .getClientProperty(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY);
+ if (flatProperty != null) {
+ if (Boolean.TRUE.equals(flatProperty))
+ return true;
+ if (Boolean.FALSE.equals(flatProperty))
+ return false;
+ }
+ }
+ }
+
+ return Boolean.TRUE.equals(UIManager
+ .get(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY));
+ }
+
+ /**
+ * Returns the focus ring kind of the specified component.
+ *
+ * @param component
+ * Component.
+ * @return The focus ring kind of the specified component.
+ * @see SubstanceLookAndFeel#FOCUS_KIND
+ */
+ public static FocusKind getFocusKind(Component component) {
+ while (component != null) {
+ if (component instanceof JComponent) {
+ JComponent jcomp = (JComponent) component;
+ Object jcompFocusKind = jcomp
+ .getClientProperty(SubstanceLookAndFeel.FOCUS_KIND);
+ if (jcompFocusKind instanceof FocusKind)
+ return (FocusKind) jcompFocusKind;
+ }
+ component = component.getParent();
+ }
+ Object globalFocusKind = UIManager.get(SubstanceLookAndFeel.FOCUS_KIND);
+ if (globalFocusKind instanceof FocusKind)
+ return (FocusKind) globalFocusKind;
+ return FocusKind.ALL_INNER;
+ }
+
+ /**
+ * Returns indication whether the watermark should be drawn on the specified
+ * component.
+ *
+ * @param component
+ * Component.
+ * @return <code>true</code> if the watermark should be drawn on the
+ * specified component, <code>false</code> otherwise.
+ * @see SubstanceLookAndFeel#WATERMARK_VISIBLE
+ */
+ public static boolean toDrawWatermark(Component component) {
+ Component c = component;
+ while (c != null) {
+ if (c instanceof JComponent) {
+ JComponent jcomp = (JComponent) component;
+ Object obj = jcomp
+ .getClientProperty(SubstanceLookAndFeel.WATERMARK_VISIBLE);
+ if (obj != null) {
+ if (Boolean.TRUE.equals(obj))
+ return true;
+ if (Boolean.FALSE.equals(obj))
+ return false;
+ }
+ }
+ c = c.getParent();
+ }
+ Object obj = UIManager.get(SubstanceLookAndFeel.WATERMARK_VISIBLE);
+ if (Boolean.TRUE.equals(obj))
+ return true;
+ if (Boolean.FALSE.equals(obj))
+ return false;
+
+ // special cases - lists, tables and trees that show watermarks only
+ // when the WATERMARK_VISIBLE is set to Boolean.TRUE
+ if (component instanceof JList)
+ return false;
+ if (component instanceof JTree)
+ return false;
+ if (component instanceof JTable)
+ return false;
+ if (component instanceof JTextComponent)
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Returns the button shaper of the specified button.
+ *
+ * @param comp
+ * The button.
+ * @return The button shaper of the specified button.
+ * @see SubstanceLookAndFeel#BUTTON_SHAPER_PROPERTY
+ * @see SubstanceSkin#getButtonShaper()
+ */
+ public static SubstanceButtonShaper getButtonShaper(Component comp) {
+ if (comp instanceof JComponent) {
+ Object prop = ((JComponent) comp)
+ .getClientProperty(SubstanceLookAndFeel.BUTTON_SHAPER_PROPERTY);
+ if (prop instanceof SubstanceButtonShaper)
+ return (SubstanceButtonShaper) prop;
+ }
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(comp);
+ if (skin == null)
+ return null;
+ return skin.getButtonShaper();
+ }
+
+ /**
+ * Returns the fill painter of the specified component.
+ *
+ * @param comp
+ * Component.
+ * @return The fill painter of the specified component.
+ * @see SubstanceSkin#getFillPainter()
+ */
+ public static SubstanceFillPainter getFillPainter(Component comp) {
+ return SubstanceCoreUtilities.getSkin(comp).getFillPainter();
+ }
+
+ /**
+ * Retrieves the <code>modified</code> state for the specified component in
+ * a tabbed pane.
+ *
+ * @param tabComponent
+ * The associated tab component.
+ * @return <code>true</code> if the specified component in a tabbed pane is
+ * marked as modified, <code>false</code> otherwise.
+ * @see SubstanceLookAndFeel#WINDOW_MODIFIED
+ */
+ public static boolean isTabModified(Component tabComponent) {
+ boolean isWindowModified = false;
+ Component comp = tabComponent;
+ if (comp instanceof JComponent) {
+ JComponent jc = (JComponent) comp;
+
+ isWindowModified = Boolean.TRUE.equals(jc
+ .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
+ }
+ return isWindowModified;
+ }
+
+ /**
+ * Retrieves the <code>modified</code> state for the specified root pane.
+ *
+ * @param rootPane
+ * The root pane.
+ * @return <code>true</code> if the specified root pane is marked as
+ * modified, <code>false</code> otherwise.
+ * @see SubstanceLookAndFeel#WINDOW_MODIFIED
+ */
+ public static boolean isRootPaneModified(JRootPane rootPane) {
+ return Boolean.TRUE.equals(rootPane
+ .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
+ }
+
+ /**
+ * Retrieves the <code>modified</code> state for the specified internal
+ * frame.
+ *
+ * @param internalFrame
+ * The internal frame.
+ * @return <code>true</code> if the specified internal frame is marked as
+ * modified, <code>false</code> otherwise.
+ * @see SubstanceLookAndFeel#WINDOW_MODIFIED
+ */
+ public static boolean isInternalFrameModified(JInternalFrame internalFrame) {
+ return Boolean.TRUE.equals(internalFrame.getRootPane()
+ .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
+ }
+
+ public static boolean isRootPaneAutoDeactivate(JRootPane rp) {
+ if (!UIManager.getBoolean(SubstanceLookAndFeel.WINDOW_AUTO_DEACTIVATE)) {
+ return false;
+ }
+ if (rp == null) {
+ return false;
+ }
+ Object paneSpecific = rp.getClientProperty(SubstanceLookAndFeel.WINDOW_AUTO_DEACTIVATE);
+ if (paneSpecific instanceof Boolean && !((Boolean)paneSpecific)) {
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean isPaintRootPaneActivated(JRootPane rp) {
+ if (isRootPaneAutoDeactivate(rp)) {
+ Component c = rp.getParent();
+ if (c instanceof JInternalFrame) {
+ return ((JInternalFrame)c).isSelected();
+ } else if (c instanceof Window) {
+ return ((Window)c).isActive();
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+
+ public static boolean isSecondaryWindow(JRootPane rp) {
+ Component c = rp.getParent();
+ if (c instanceof JInternalFrame) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private static Boolean globalRoundingEnable = null;
+ private static boolean defaultRoundingEnable = true;
+
+ public static boolean isRoundedCorners(Component c) {
+ // first time through, things have yet to be set up
+ if (globalRoundingEnable == null) {
+ //first step, check the system property for kill switch
+ String s = System.getProperty(SubstanceLookAndFeel.WINDOW_ROUNDED_CORNERS_PROPERTY);
+ globalRoundingEnable = s == null || s.length() == 0 || Boolean.valueOf(s);
+ UIManager.getDefaults().addPropertyChangeListener(new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (SubstanceLookAndFeel.WINDOW_ROUNDED_CORNERS.equals(evt.getPropertyName())) {
+ defaultRoundingEnable = (!(evt.getNewValue() instanceof Boolean)) || ((Boolean)evt.getNewValue());
+ }
+ }
+ });
+
+ // next step, check AWTUtilities capabilities
+ if (globalRoundingEnable) {
+ globalRoundingEnable = AWTUtilities.isTranslucencySupported(AWTUtilities.Translucency.PERPIXEL_TRANSPARENT);
+ }
+
+ // finally, add one listener to listen to the UIManager defaults value when the default changes.
+ if (globalRoundingEnable) {
+ Object o = UIManager.get(SubstanceLookAndFeel.WINDOW_ROUNDED_CORNERS);
+ if (o instanceof Boolean) {
+ defaultRoundingEnable = (Boolean) o;
+ }
+ }
+ }
+
+ // guard condition, if the kill switch is pressed stop. Hotspot will optimize this nicely.
+ if (!globalRoundingEnable) {
+ return false;
+ }
+
+ // for real this time. Start with the "default" from UI defaults.
+ boolean round = defaultRoundingEnable;
+ if (c instanceof JComponent) {
+ // next possibly override this per-window.
+ Object o = ((JComponent)c).getClientProperty(SubstanceLookAndFeel.WINDOW_ROUNDED_CORNERS);
+ if (o instanceof Boolean) { // Java trivia: null instanceof <anything> is false. Free null check
+ round = (Boolean) o;
+ }
+ }
+
+ // check for maximized windows
+ if (round) {
+ Component p = c;
+ while (!(p instanceof Window || p instanceof JInternalFrame) && p != null) {
+ p = p.getParent();
+ }
+ if (p instanceof Frame) {
+ if ((((Frame)p).getExtendedState() & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH) {
+ round = false;
+ }
+ } else if (c instanceof JInternalFrame) {
+ round = !(((JInternalFrame)c).isMaximum());
+ }
+ }
+
+ return round;
+ }
+
+ /**
+ * Checks whether the specified tab has a close button.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ * @return <code>true</code> if the specified tab has a close button,
+ * <code>false</code> otherwise.
+ * @see SubstanceLookAndFeel#TABBED_PANE_CLOSE_BUTTONS_PROPERTY
+ */
+ public static boolean hasCloseButton(JTabbedPane tabbedPane, int tabIndex) {
+ int tabCount = tabbedPane.getTabCount();
+ if ((tabIndex < 0) || (tabIndex >= tabCount))
+ return false;
+
+ // if tab is disabled, it doesn't have a close button
+ if (!tabbedPane.isEnabledAt(tabIndex))
+ return false;
+
+ // check property on tab component
+ Component tabComponent = tabbedPane.getComponentAt(tabIndex);
+ if (tabComponent instanceof JComponent) {
+ Object compProp = ((JComponent) tabComponent)
+ .getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY);
+ if (Boolean.TRUE.equals(compProp))
+ return true;
+ if (Boolean.FALSE.equals(compProp))
+ return false;
+ }
+ // check property on tabbed pane
+ Object tabProp = tabbedPane
+ .getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY);
+ if (Boolean.TRUE.equals(tabProp))
+ return true;
+ if (Boolean.FALSE.equals(tabProp))
+ return false;
+ // check property in UIManager
+ return UIManager
+ .getBoolean(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY);
+ }
+
+ /**
+ * Returns the size of the close button for a tab in the specified tabbed
+ * pane.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ * @return The size of the close button for a tab in the specified tabbed
+ * pane.
+ */
+ public static int getCloseButtonSize(JTabbedPane tabbedPane, int tabIndex) {
+ if (!SubstanceCoreUtilities.hasCloseButton(tabbedPane, tabIndex))
+ return 0;
+ return SubstanceSizeUtils.getTabCloseIconSize(SubstanceSizeUtils
+ .getComponentFontSize(tabbedPane));
+ }
+
+ /**
+ * Returns the content border kind of the specified tabbed pane.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @return Content border kind of the specified tabbed pane.
+ * @see SubstanceLookAndFeel#TABBED_PANE_CONTENT_BORDER_KIND
+ */
+ public static TabContentPaneBorderKind getContentBorderKind(
+ JTabbedPane tabbedPane) {
+ // check property on tabbed pane
+ Object tabProp = tabbedPane
+ .getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CONTENT_BORDER_KIND);
+ if (tabProp instanceof TabContentPaneBorderKind)
+ return (TabContentPaneBorderKind) tabProp;
+ // check property in UIManager
+ Object globalProp = UIManager
+ .get(SubstanceLookAndFeel.TABBED_PANE_CONTENT_BORDER_KIND);
+ if (globalProp instanceof TabContentPaneBorderKind)
+ return (TabContentPaneBorderKind) globalProp;
+ return TabContentPaneBorderKind.DOUBLE_FULL;
+ }
+
+ /**
+ * Checks whether the specified tab should show modified animation only on
+ * its close button.
+ *
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ * @return <code>true</code> if the specified tab should show modified
+ * animation only on its close button, <code>false</code> otherwise.
+ * @see SubstanceLookAndFeel#TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION
+ */
+ public static boolean toAnimateCloseIconOfModifiedTab(
+ JTabbedPane tabbedPane, int tabIndex) {
+ int tabCount = tabbedPane.getTabCount();
+ if ((tabIndex < 0) || (tabIndex >= tabCount))
+ return false;
+
+ if (!hasCloseButton(tabbedPane, tabIndex))
+ return false;
+
+ // check property on tab component
+ Component tabComponent = tabbedPane.getComponentAt(tabIndex);
+ if (tabComponent instanceof JComponent) {
+ Object compProp = ((JComponent) tabComponent)
+ .getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION);
+ if (Boolean.TRUE.equals(compProp))
+ return true;
+ if (Boolean.FALSE.equals(compProp))
+ return false;
+ }
+ // check property on tabbed pane
+ Object tabProp = tabbedPane
+ .getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION);
+ if (Boolean.TRUE.equals(tabProp))
+ return true;
+ if (Boolean.FALSE.equals(tabProp))
+ return false;
+ // check property in UIManager
+ return UIManager
+ .getBoolean(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION);
+ }
+
+ /**
+ * Retrieves transparent image of specified dimension.
+ *
+ * @param width
+ * Image width.
+ * @param height
+ * Image height.
+ * @return Transparent image of specified dimension.
+ */
+ public static BufferedImage getBlankImage(int width, int height) {
+ if (MemoryAnalyzer.isRunning()) {
+ // see if the request is unusual
+ if ((width >= 100) || (height >= 100)) {
+ StackTraceElement[] stack = Thread.currentThread()
+ .getStackTrace();
+ StringBuilder sb = new StringBuilder();
+ int count = 0;
+ for (StackTraceElement stackEntry : stack) {
+ if (count++ > 8)
+ break;
+ sb.append(stackEntry.getClassName())
+ .append(".")
+ .append(stackEntry.getMethodName())
+ .append(" [")
+ .append(stackEntry.getLineNumber())
+ .append("]")
+ .append("\n");
+ }
+ MemoryAnalyzer.enqueueUsage("Blank " + width + "*" + height
+ + "\n" + sb.toString());
+ }
+ }
+
+ BufferedImage compatibleImage;
+ if (GraphicsEnvironment.isHeadless()) {
+ compatibleImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ } else {
+ GraphicsEnvironment e = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice d = e.getDefaultScreenDevice();
+ GraphicsConfiguration c = d.getDefaultConfiguration();
+ compatibleImage = c.createCompatibleImage(width, height,
+ Transparency.TRANSLUCENT);
+ }
+ return compatibleImage;
+ }
+
+ /**
+ * Retrieves transparent image of specified dimension.
+ *
+ * @param width
+ * Image width.
+ * @param height
+ * Image height.
+ * @return Transparent image of specified dimension.
+ */
+ public static VolatileImage getBlankVolatileImage(int width, int height) {
+ if (MemoryAnalyzer.isRunning()) {
+ // see if the request is unusual
+ if ((width >= 100) || (height >= 100)) {
+ StackTraceElement[] stack = Thread.currentThread()
+ .getStackTrace();
+ StringBuilder sb = new StringBuilder();
+ int count = 0;
+ for (StackTraceElement stackEntry : stack) {
+ if (count++ > 8)
+ break;
+ sb.append(stackEntry.getClassName())
+ .append(".")
+ .append(stackEntry.getMethodName())
+ .append(" [")
+ .append(stackEntry.getLineNumber())
+ .append("]")
+ .append("\n");
+ }
+ MemoryAnalyzer.enqueueUsage("Blank " + width + "*" + height
+ + "\n" + sb.toString());
+ }
+ }
+
+ GraphicsEnvironment e = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice d = e.getDefaultScreenDevice();
+ GraphicsConfiguration c = d.getDefaultConfiguration();
+ VolatileImage compatibleImage = c.createCompatibleVolatileImage(width,
+ height, Transparency.TRANSLUCENT);
+ return compatibleImage;
+ }
+
+ /**
+ * Checks whether the specified button should have minimal size.
+ *
+ * @param button
+ * Button.
+ * @return <code>false</code> if the specified button should have minimal
+ * size, <code>true</code> otherwise.
+ * @see SubstanceLookAndFeel#BUTTON_NO_MIN_SIZE_PROPERTY
+ */
+ public static boolean hasNoMinSizeProperty(AbstractButton button) {
+ Object noMinSizeProperty = button
+ .getClientProperty(SubstanceLookAndFeel.BUTTON_NO_MIN_SIZE_PROPERTY);
+ if (Boolean.TRUE.equals(noMinSizeProperty))
+ return true;
+ if (Boolean.FALSE.equals(noMinSizeProperty))
+ return false;
+ Container parent = button.getParent();
+ if (parent instanceof JComponent) {
+ noMinSizeProperty = ((JComponent) parent)
+ .getClientProperty(SubstanceLookAndFeel.BUTTON_NO_MIN_SIZE_PROPERTY);
+ if (Boolean.TRUE.equals(noMinSizeProperty))
+ return true;
+ if (Boolean.FALSE.equals(noMinSizeProperty))
+ return false;
+ }
+ return (Boolean.TRUE.equals(UIManager
+ .get(SubstanceLookAndFeel.BUTTON_NO_MIN_SIZE_PROPERTY)));
+ }
+
+ /**
+ * Checks whether the specified component is flat.
+ *
+ * @param comp
+ * Component.
+ * @param defaultValue
+ * The value to return if there is no
+ * {@link SubstanceLookAndFeel#FLAT_PROPERTY} defined on button
+ * hierarchy or {@link UIManager}.
+ * @return <code>false</code> if the specified button is flat,
+ * <code>true</code> otherwise.
+ * @see SubstanceLookAndFeel#FLAT_PROPERTY
+ */
+ public static boolean hasFlatAppearance(Component comp, boolean defaultValue) {
+ // small optimizations for checkboxes and radio buttons
+ if (comp instanceof JCheckBox)
+ return defaultValue;
+ if (comp instanceof JRadioButton)
+ return defaultValue;
+ Component c = comp;
+ // while (c != null) {
+ if (c instanceof JComponent) {
+ JComponent jcomp = (JComponent) c;
+ Object flatProperty = jcomp
+ .getClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY);
+ if (flatProperty != null) {
+ if (Boolean.TRUE.equals(flatProperty))
+ return true;
+ if (Boolean.FALSE.equals(flatProperty))
+ return false;
+ }
+ }
+ if (c != null) {
+ Container parent = c.getParent();
+ if (parent instanceof JComponent) {
+ JComponent jparent = (JComponent) parent;
+ Object flatProperty = jparent
+ .getClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY);
+ if (flatProperty != null) {
+ if (Boolean.TRUE.equals(flatProperty))
+ return true;
+ if (Boolean.FALSE.equals(flatProperty))
+ return false;
+ }
+ }
+ }
+
+ // }
+ Object flatProperty = UIManager.get(SubstanceLookAndFeel.FLAT_PROPERTY);
+ if (flatProperty != null) {
+ if (Boolean.TRUE.equals(flatProperty))
+ return true;
+ if (Boolean.FALSE.equals(flatProperty))
+ return false;
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Computes whether the specified button has flat appearance.
+ *
+ * @param button
+ * Button.
+ * @return <code>true</code> if the button has flat appearance,
+ * <code>false</code> otherwise.
+ */
+ public static boolean hasFlatAppearance(AbstractButton button) {
+ // small optimizations for checkboxes and radio buttons
+ if (button instanceof JCheckBox)
+ return false;
+ if (button instanceof JRadioButton)
+ return false;
+ return ((SubstanceCoreUtilities.isToolBarButton(button) && SubstanceCoreUtilities
+ .hasFlatAppearance(button, true)) || SubstanceCoreUtilities
+ .hasFlatAppearance(button, false));
+ }
+
+ /**
+ * Returns the popup flyout orientation for the specified combobox.
+ *
+ * @param combobox
+ * Combobox.
+ * @return The popup flyout orientation for the specified combobox.
+ * @see SubstanceLookAndFeel#COMBO_BOX_POPUP_FLYOUT_ORIENTATION
+ */
+ public static int getPopupFlyoutOrientation(JComboBox combobox) {
+ Object comboProperty = combobox
+ .getClientProperty(SubstanceLookAndFeel.COMBO_BOX_POPUP_FLYOUT_ORIENTATION);
+ if (comboProperty instanceof Integer)
+ return (Integer) comboProperty;
+ Object globalProperty = UIManager
+ .get(SubstanceLookAndFeel.COMBO_BOX_POPUP_FLYOUT_ORIENTATION);
+ if (globalProperty instanceof Integer)
+ return (Integer) globalProperty;
+ return SwingConstants.SOUTH;
+ }
+
+ /**
+ * Makes the specified component and all its descendants non-opaque.
+ *
+ * @param comp
+ * Component.
+ * @param opacitySnapshot
+ * The "snapshot" map that will contain the original opacity
+ * status of the specified component and all its descendants.
+ */
+ public static void makeNonOpaque(Component comp,
+ Map<Component, Boolean> opacitySnapshot) {
+ if (comp instanceof JComponent) {
+ JComponent jcomp = (JComponent) comp;
+ opacitySnapshot.put(comp, jcomp.isOpaque());
+ jcomp.setOpaque(false);
+ }
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++)
+ makeNonOpaque(cont.getComponent(i), opacitySnapshot);
+ }
+ }
+
+ /**
+ * Restores the opacity of the specified component and all its descendants.
+ *
+ * @param comp
+ * Component.
+ * @param opacitySnapshot
+ * The "snapshot" map that contains the original opacity status
+ * of the specified component and all its descendants.
+ */
+ public static void restoreOpaque(Component comp,
+ Map<Component, Boolean> opacitySnapshot) {
+ if (comp instanceof JComponent) {
+ JComponent jcomp = (JComponent) comp;
+ // fix for defect 159 - snapshot may not contain
+ // opacity for table header of a table when it's used
+ // inside tree cell renderer (wrapper in a scroll pane).
+ if (opacitySnapshot.containsKey(comp))
+ jcomp.setOpaque(opacitySnapshot.get(comp));
+ else
+ jcomp.setOpaque(true);
+ }
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++)
+ restoreOpaque(cont.getComponent(i), opacitySnapshot);
+ }
+ }
+
+ /**
+ * Creates a compatible image (for efficient processing and drawing).
+ *
+ * @param image
+ * The original image.
+ * @return Compatible version of the original image.
+ * @author Romain Guy
+ */
+ public static BufferedImage createCompatibleImage(BufferedImage image) {
+ GraphicsEnvironment e = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice d = e.getDefaultScreenDevice();
+ GraphicsConfiguration c = d.getDefaultConfiguration();
+ BufferedImage compatibleImage = c.createCompatibleImage(image
+ .getWidth(), image.getHeight(), Transparency.TRANSLUCENT);
+ Graphics g = compatibleImage.getGraphics();
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+ return compatibleImage;
+ }
+
+ /**
+ * Checks whether the specified component will show scheme-colorized icon in
+ * the default state.
+ *
+ * @param comp
+ * Component.
+ * @return <code>true</code> if the specified component will show
+ * scheme-colorized icon in the default state, <code>false</code>
+ * otherwise.
+ * @see SubstanceLookAndFeel#USE_THEMED_DEFAULT_ICONS
+ */
+ public static boolean useThemedDefaultIcon(JComponent comp) {
+ if (comp instanceof SubstanceInternalButton) {
+ return false;
+ }
+ return Boolean.TRUE.equals(UIManager
+ .get(SubstanceLookAndFeel.USE_THEMED_DEFAULT_ICONS));
+ }
+
+ /**
+ * Returns the callback to be called upon tab closing (using the tab close
+ * button).
+ *
+ * @param me
+ * Mouse event.
+ * @param tabbedPane
+ * Tabbed pane.
+ * @param tabIndex
+ * Tab index.
+ * @return Callback to be called upon tab closing (using the tab close
+ * button).
+ * @see SubstanceLookAndFeel#TABBED_PANE_CLOSE_CALLBACK
+ */
+ public static TabCloseCallback getTabCloseCallback(MouseEvent me,
+ JTabbedPane tabbedPane, int tabIndex) {
+ int tabCount = tabbedPane.getTabCount();
+ if ((tabIndex < 0) || (tabIndex >= tabCount))
+ return null;
+
+ // check property on tab component
+ Component tabComponent = tabbedPane.getComponentAt(tabIndex);
+ if (tabComponent instanceof JComponent) {
+ Object compProp = ((JComponent) tabComponent)
+ .getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_CALLBACK);
+ if (compProp instanceof TabCloseCallback)
+ return (TabCloseCallback) compProp;
+ }
+
+ // check property on tabbed pane
+ Object tabProp = tabbedPane
+ .getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_CALLBACK);
+ if (tabProp instanceof TabCloseCallback)
+ return (TabCloseCallback) tabProp;
+
+ Object globProp = UIManager
+ .get(SubstanceLookAndFeel.TABBED_PANE_CLOSE_CALLBACK);
+ if (globProp instanceof TabCloseCallback)
+ return (TabCloseCallback) globProp;
+
+ return null;
+ }
+
+ /**
+ * Blends two images along Y-axis.
+ *
+ * @param imageTop
+ * The left image.
+ * @param imageBottom
+ * The right image.
+ * @param start
+ * Relative start of the blend area (in 0.0-1.0 range).
+ * @param end
+ * Relative end of the blend area (in 0.0-1.0 range).
+ * @return Blended image.
+ */
+ public static BufferedImage blendImagesVertical(BufferedImage imageTop,
+ BufferedImage imageBottom, double start, double end) {
+ int width = imageTop.getWidth();
+ if (width != imageBottom.getWidth())
+ throw new IllegalArgumentException("Widths are not the same: "
+ + imageTop.getWidth() + " and " + imageBottom.getWidth());
+ int height = imageTop.getHeight();
+ if (height != imageBottom.getHeight())
+ throw new IllegalArgumentException("Heights are not the same: "
+ + imageTop.getHeight() + " and " + imageBottom.getHeight());
+
+ BufferedImage result = getBlankImage(width, height);
+ Graphics2D graphics = (Graphics2D) result.getGraphics().create();
+
+ int startY = (int) (start * height);
+ int endY = (int) (end * height);
+ int rampHeight = endY - startY;
+
+ if (rampHeight == 0) {
+ graphics.drawImage(imageTop, 0, 0, width, startY, 0, 0, width,
+ startY, null);
+ graphics.drawImage(imageBottom, 0, startY, width, height, 0,
+ startY, width, height, null);
+ } else {
+ BufferedImage rampBottom = getBlankImage(width, rampHeight);
+ Graphics2D rampBottomG = (Graphics2D) rampBottom.getGraphics();
+ rampBottomG.setPaint(new GradientPaint(new Point(0, 0), new Color(
+ 0, 0, 0, 255), new Point(0, rampHeight), new Color(0, 0, 0,
+ 0)));
+ rampBottomG.fillRect(0, 0, width, rampHeight);
+
+ BufferedImage tempBottom = getBlankImage(width, height - startY);
+ Graphics2D tempBottomG = (Graphics2D) tempBottom.getGraphics();
+ tempBottomG.drawImage(imageBottom, 0, 0, width, height - startY, 0,
+ startY, width, height, null);
+ tempBottomG.setComposite(AlphaComposite.DstOut);
+ tempBottomG.drawImage(rampBottom, 0, 0, null);
+
+ tempBottomG.setComposite(AlphaComposite.SrcOver);
+ graphics.drawImage(imageTop, 0, 0, null);
+ graphics.drawImage(tempBottom, 0, startY, null);
+ }
+ graphics.dispose();
+ return result;
+ }
+
+ /**
+ * Blends two images along X-axis.
+ *
+ * @param imageLeft
+ * The left image.
+ * @param imageRight
+ * The right image.
+ * @param start
+ * Relative start of the blend area (in 0.0-1.0 range).
+ * @param end
+ * Relative end of the blend area (in 0.0-1.0 range).
+ * @return Blended image.
+ */
+ public static BufferedImage blendImagesHorizontal(BufferedImage imageLeft,
+ BufferedImage imageRight, double start, double end) {
+ int width = imageLeft.getWidth();
+ if (width != imageRight.getWidth())
+ throw new IllegalArgumentException("Widths are not the same: "
+ + imageLeft.getWidth() + " and " + imageRight.getWidth());
+ int height = imageLeft.getHeight();
+ if (height != imageRight.getHeight())
+ throw new IllegalArgumentException("Heights are not the same: "
+ + imageLeft.getHeight() + " and " + imageRight.getHeight());
+
+ BufferedImage result = getBlankImage(width, height);
+ Graphics2D graphics = (Graphics2D) result.getGraphics().create();
+
+ int startX = (int) (start * width);
+ int endX = (int) (end * width);
+ int rampWidth = endX - startX;
+
+ if (rampWidth == 0) {
+ graphics.drawImage(imageLeft, 0, 0, startX, height, 0, 0, startX,
+ height, null);
+ graphics.drawImage(imageRight, startX, 0, width, height, startX, 0,
+ width, height, null);
+ } else {
+ BufferedImage rampRight = getBlankImage(rampWidth, height);
+ Graphics2D rampRightG = (Graphics2D) rampRight.getGraphics();
+ rampRightG
+ .setPaint(new GradientPaint(new Point(0, 0), new Color(0,
+ 0, 0, 255), new Point(rampWidth, 0), new Color(0,
+ 0, 0, 0)));
+ rampRightG.fillRect(0, 0, rampWidth, height);
+
+ BufferedImage tempRight = getBlankImage(width - startX, height);
+ Graphics2D tempRightG = (Graphics2D) tempRight.getGraphics();
+ tempRightG.drawImage(imageRight, 0, 0, width - startX, height,
+ startX, 0, width, height, null);
+ tempRightG.setComposite(AlphaComposite.DstOut);
+ tempRightG.drawImage(rampRight, 0, 0, null);
+
+ tempRightG.setComposite(AlphaComposite.SrcOver);
+ graphics.drawImage(imageLeft, 0, 0, null);
+ graphics.drawImage(tempRight, startX, 0, null);
+ }
+ graphics.dispose();
+ return result;
+ }
+
+ /**
+ * Returns the color scheme for the icon of option panes with the specified
+ * message type.
+ *
+ * @param messageType
+ * Option pane message type.
+ * @param mainScheme
+ * Main color scheme.
+ * @return Color scheme for the icon of option panes with the specified
+ * message type.
+ */
+ public static SubstanceColorScheme getOptionPaneColorScheme(
+ int messageType, SubstanceColorScheme mainScheme) {
+ if (!SubstanceLookAndFeel.isToUseConstantThemesOnDialogs())
+ return mainScheme;
+
+ switch (messageType) {
+ case JOptionPane.INFORMATION_MESSAGE:
+ return new BottleGreenColorScheme();
+ case JOptionPane.QUESTION_MESSAGE:
+ return new LightAquaColorScheme();
+ case JOptionPane.WARNING_MESSAGE:
+ return new SunsetColorScheme();
+ case JOptionPane.ERROR_MESSAGE:
+ return new SunfireRedColorScheme();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the popup prototype display value for the specified combo box.
+ * This value is used to compute the width of the combo popup.
+ *
+ * @param combo
+ * Combo box.
+ * @return The popup prototype display value for the specified combo box.
+ * @see SubstanceLookAndFeel#COMBO_POPUP_PROTOTYPE
+ */
+ public static Object getComboPopupPrototypeDisplayValue(JComboBox combo) {
+ Object objProp = combo
+ .getClientProperty(SubstanceLookAndFeel.COMBO_POPUP_PROTOTYPE);
+ if (objProp == null)
+ objProp = UIManager.get(SubstanceLookAndFeel.COMBO_POPUP_PROTOTYPE);
+ if (objProp == null)
+ return null;
+
+ if (objProp instanceof ComboPopupPrototypeCallback) {
+ ComboPopupPrototypeCallback callback = (ComboPopupPrototypeCallback) objProp;
+ return callback.getPopupPrototypeDisplayValue(combo);
+ }
+
+ // check if this object is in the model ???
+ return objProp;
+ }
+
+ /**
+ * Returns the scroll bar buttons kind of the specified scroll bar.
+ *
+ * @param scrollBar
+ * Scroll bar.
+ * @return The scroll bar buttons kind of the specified scroll bar.
+ * @see SubstanceLookAndFeel#SCROLL_PANE_BUTTONS_POLICY
+ */
+ public static ScrollPaneButtonPolicyKind getScrollPaneButtonsPolicyKind(
+ JScrollBar scrollBar) {
+ Component parent = scrollBar.getParent();
+ if (parent instanceof JScrollPane) {
+ Object jspKind = ((JScrollPane) parent)
+ .getClientProperty(SubstanceLookAndFeel.SCROLL_PANE_BUTTONS_POLICY);
+ if (jspKind instanceof ScrollPaneButtonPolicyKind)
+ return (ScrollPaneButtonPolicyKind) jspKind;
+ }
+ Object globalJspKind = UIManager
+ .get(SubstanceLookAndFeel.SCROLL_PANE_BUTTONS_POLICY);
+ if (globalJspKind instanceof ScrollPaneButtonPolicyKind)
+ return (ScrollPaneButtonPolicyKind) globalJspKind;
+ return ScrollPaneButtonPolicyKind.OPPOSITE;
+ }
+
+ /**
+ * Returns the set of sides registered on the specified button.
+ *
+ * @param component
+ * Button.
+ * @param propertyName
+ * Client property name for retrieving the registered sides.
+ * @return Set of sides registered on the specified button.
+ */
+ @SuppressWarnings("unchecked")
+ public static Set<Side> getSides(JComponent component, String propertyName) {
+ if (component == null) {
+ return null;
+ }
+
+ Object prop = component.getClientProperty(propertyName);
+ if (prop == null)
+ return null;
+
+ if (prop instanceof Set) {
+ return (Set<Side>) prop;
+ }
+
+ if (prop != null) {
+ if (prop instanceof Side) {
+ Set<Side> result = EnumSet.noneOf(Side.class);
+ result.add((Side) prop);
+ return result;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the corner radius of the specified toolbar button.
+ *
+ * @param button
+ * Toolbar button.
+ * @param insets
+ * Button insets.
+ * @return Corner radius of the specified toolbar button.
+ * @see SubstanceLookAndFeel#CORNER_RADIUS
+ */
+ public static float getToolbarButtonCornerRadius(JComponent button,
+ Insets insets) {
+
+ JToolBar toolbar = null;
+ Component c = button.getParent();
+ while (c != null) {
+ if (c instanceof JToolBar) {
+ toolbar = (JToolBar) c;
+ break;
+ }
+ c = c.getParent();
+ }
+ if (toolbar == null)
+ return 2.0f;
+
+ int width = button.getWidth();
+ int height = button.getHeight();
+
+ if (insets != null) {
+ width -= (insets.left + insets.right);
+ height -= (insets.top + insets.bottom);
+ }
+ float maxRadius = (width > height) ? (height) / 2.0f : (width) / 2.0f;
+
+ Object buttonProp = button
+ .getClientProperty(SubstanceLookAndFeel.CORNER_RADIUS);
+ if (buttonProp instanceof Float)
+ return Math.min(maxRadius, ((Float) buttonProp).floatValue());
+
+ Object toolbarProp = toolbar
+ .getClientProperty(SubstanceLookAndFeel.CORNER_RADIUS);
+ if (toolbarProp instanceof Float)
+ return Math.min(maxRadius, ((Float) toolbarProp).floatValue());
+
+ Object globalProp = UIManager.get(SubstanceLookAndFeel.CORNER_RADIUS);
+ if (globalProp instanceof Float)
+ return Math.min(maxRadius, ((Float) globalProp).floatValue());
+
+ return 2.0f;
+ }
+
+ /**
+ * Returns the number of echo characters per each password chanaracter.
+ *
+ * @param jpf
+ * Password field.
+ * @return The number of echo characters per each password chanaracter.
+ * @see SubstanceLookAndFeel#PASSWORD_ECHO_PER_CHAR
+ */
+ public static int getEchoPerChar(JPasswordField jpf) {
+ Object obj = jpf
+ .getClientProperty(SubstanceLookAndFeel.PASSWORD_ECHO_PER_CHAR);
+ if ((obj != null) && (obj instanceof Integer)) {
+ int result = (Integer) obj;
+ if (result >= 1)
+ return result;
+ }
+
+ obj = UIManager.get(SubstanceLookAndFeel.PASSWORD_ECHO_PER_CHAR);
+ if ((obj != null) && (obj instanceof Integer)) {
+ int result = (Integer) obj;
+ if (result >= 1)
+ return result;
+ }
+ return 1;
+ }
+
+ /**
+ * Creates a soft-clipped image. Code taken from <a href=
+ * "http://weblogs.java.net/blog/campbell/archive/2006/07/java_2d_tricker.html"
+ * >here</a>.
+ *
+ * @author Chris Campbell.
+ */
+ public static BufferedImage softClip(int width, int height,
+ BufferedImage source, Shape clipShape) {
+ // Create a translucent intermediate image in which we can perform
+ // the soft clipping
+ BufferedImage img = SubstanceCoreUtilities.getBlankImage(width, height);
+ Graphics2D g2 = img.createGraphics();
+
+ // Clear the image so all pixels have zero alpha
+ g2.setComposite(AlphaComposite.Clear);
+ g2.fillRect(0, 0, width, height);
+
+ // Render our clip shape into the image. Note that we enable
+ // antialiasing to achieve the soft clipping effect. Try
+ // commenting out the line that enables antialiasing, and
+ // you will see that you end up with the usual hard clipping.
+ g2.setComposite(AlphaComposite.Src);
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2.setColor(Color.WHITE);
+ g2.fill(clipShape);
+
+ // Here's the trick... We use SrcAtop, which effectively uses the
+ // alpha value as a coverage value for each pixel stored in the
+ // destination. For the areas outside our clip shape, the destination
+ // alpha will be zero, so nothing is rendered in those areas. For
+ // the areas inside our clip shape, the destination alpha will be fully
+ // opaque, so the full color is rendered. At the edges, the original
+ // antialiasing is carried over to give us the desired soft clipping
+ // effect.
+ g2.setComposite(AlphaComposite.SrcAtop);
+ g2.drawImage(source, 0, 0, null);
+ g2.dispose();
+
+ return img;
+ }
+
+ /**
+ * Checks whether the specified component has extra Substance-specific UI
+ * elements.
+ *
+ * @param component
+ * Component.
+ * @return <code>true</code> if the specified component has extra
+ * Substance-specific UI elements, <code>false</code> otherwise.
+ * @see SubstanceLookAndFeel#SHOW_EXTRA_WIDGETS
+ */
+ public static boolean toShowExtraWidgets(Component component) {
+ Component c = component;
+ while (c != null) {
+ if (c instanceof JComponent) {
+ JComponent jcomp = (JComponent) c;
+ Object componentProp = jcomp
+ .getClientProperty(SubstanceLookAndFeel.SHOW_EXTRA_WIDGETS);
+ if (componentProp != null) {
+ if (Boolean.TRUE.equals(componentProp))
+ return false;
+ if (Boolean.FALSE.equals(componentProp))
+ return true;
+ }
+ }
+ c = c.getParent();
+ }
+ return Boolean.TRUE.equals(UIManager
+ .get(SubstanceLookAndFeel.SHOW_EXTRA_WIDGETS));
+ }
+
+ public static Icon getThemedIcon(Component comp, Icon orig) {
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(comp, ComponentState.ENABLED);
+ float brightnessFactor = colorScheme.isDark() ? 0.2f : 0.8f;
+ return new ImageIcon(SubstanceImageCreator.getColorSchemeImage(comp,
+ orig, colorScheme, brightnessFactor));
+ }
+
+ public static Icon getThemedIcon(JTabbedPane tab, int tabIndex, Icon orig) {
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(tab, tabIndex, ColorSchemeAssociationKind.TAB,
+ ComponentState.ENABLED);
+ float brightnessFactor = colorScheme.isDark() ? 0.2f : 0.8f;
+ return new ImageIcon(SubstanceImageCreator.getColorSchemeImage(tab,
+ orig, colorScheme, brightnessFactor));
+ }
+
+ // /**
+ // * Returns the current icon for the specified button.
+ // *
+ // * @param b
+ // * Button.
+ // * @param defaultIcon
+ // * The default icon.
+ // * @param glowingIcon
+ // * The glowing icon.
+ // * @param ignoreRolloverSetting
+ // * If <code>true</code>, the rollover status of the specified
+ // * button is ignored.
+ // * @return Icon for the specified button.
+ // */
+ // public static Icon getIcon(AbstractButton b, Icon defaultIcon,
+ // Icon glowingIcon, boolean ignoreRolloverSetting) {
+ // ButtonModel model = b.getModel();
+ // Icon icon = getActiveIcon(b.getIcon() == null ? defaultIcon : b
+ // .getIcon(), b, glowingIcon, ignoreRolloverSetting);
+ // if (icon == null)
+ // return null;
+ //
+ // if (icon.getClass().isAnnotationPresent(TransitionAware.class))
+ // return icon;
+ //
+ // Icon tmpIcon = null;
+ //
+ // if (icon != null) {
+ // if (!model.isEnabled()) {
+ // if (model.isSelected()) {
+ // tmpIcon = b.getDisabledSelectedIcon();
+ // } else {
+ // tmpIcon = b.getDisabledIcon();
+ // }
+ // } else if (model.isPressed() && model.isArmed()) {
+ // tmpIcon = b.getPressedIcon();
+ // } else if (b.isRolloverEnabled() && model.isRollover()) {
+ // if (model.isSelected()) {
+ // tmpIcon = b.getRolloverSelectedIcon();
+ // } else {
+ // tmpIcon = b.getRolloverIcon();
+ // }
+ // } else if (model.isSelected()) {
+ // tmpIcon = b.getSelectedIcon();
+ // }
+ //
+ // if (tmpIcon != null) {
+ // icon = tmpIcon;
+ // }
+ // }
+ // return icon;
+ // }
+
+ public static Icon getOriginalIcon(AbstractButton b, Icon defaultIcon) {
+ ButtonModel model = b.getModel();
+ Icon icon = b.getIcon();
+ if (icon == null)
+ icon = defaultIcon;
+
+ if (icon.getClass().isAnnotationPresent(TransitionAware.class))
+ return icon;
+
+ Icon tmpIcon = null;
+
+ if (icon != null) {
+ if (!model.isEnabled()) {
+ if (model.isSelected()) {
+ tmpIcon = b.getDisabledSelectedIcon();
+ } else {
+ tmpIcon = b.getDisabledIcon();
+ }
+ } else if (model.isPressed() && model.isArmed()) {
+ tmpIcon = b.getPressedIcon();
+ } else if (b.isRolloverEnabled() && model.isRollover()) {
+ if (model.isSelected()) {
+ tmpIcon = b.getRolloverSelectedIcon();
+ if (tmpIcon == null) {
+ tmpIcon = b.getSelectedIcon();
+ }
+ } else {
+ tmpIcon = b.getRolloverIcon();
+ }
+ } else if (model.isSelected()) {
+ tmpIcon = b.getSelectedIcon();
+ }
+
+ if (tmpIcon != null) {
+ icon = tmpIcon;
+ }
+ }
+ return icon;
+ }
+
+ /**
+ * Returns the global menu gutter fill kind.
+ *
+ * @return The global menu gutter fill kind.
+ * @see SubstanceLookAndFeel#MENU_GUTTER_FILL_KIND
+ */
+ public static MenuGutterFillKind getMenuGutterFillKind() {
+ Object globalSetting = UIManager
+ .get(SubstanceLookAndFeel.MENU_GUTTER_FILL_KIND);
+ if (globalSetting instanceof MenuGutterFillKind)
+ return (MenuGutterFillKind) globalSetting;
+ return MenuGutterFillKind.HARD;
+ }
+
+ /**
+ * Given a component, returns the parent for computing the
+ * {@link SubstanceDecorationPainter}.
+ *
+ * @param c
+ * Component.
+ * @return The parent for computing the {@link SubstanceDecorationPainter}.
+ */
+ public static Container getHeaderParent(Component c) {
+ Component comp = c.getParent();
+ Container result = null;
+ while (comp != null) {
+ // the second part fixes the incorrect alignments on
+ // internal frames.
+ if ((comp instanceof JLayeredPane) && (result == null))
+ result = (Container) comp;
+ if ((result == null) && (comp instanceof Window))
+ result = (Container) comp;
+ comp = comp.getParent();
+ }
+ return result;
+ }
+
+ /**
+ * Paints the focus ring on the specified component.
+ *
+ * @param g
+ * Graphics context.
+ * @param mainComp
+ * The main component for the focus painting.
+ * @param focusedComp
+ * The actual component that has the focus. For example, the main
+ * component can be a {@link JSpinner}, while the focused
+ * component is a text field inside the the spinner editor.
+ * @param focusShape
+ * Focus shape. May be <code>null</code> - in this case, the
+ * bounds of <code>mainComp</code> will be used.
+ * @param textRect
+ * Text rectangle (if relevant).
+ * @param maxAlphaCoef
+ * Maximum alhpa coefficient for painting the focus. Values lower
+ * than 1.0 will result in a translucent focus ring (can be used
+ * to paint a focus ring that doesn't draw too much attention
+ * away from the content, for example on text components).
+ * @param extraPadding
+ * Extra padding between the component bounds and the focus ring
+ * painting.
+ */
+ public static void paintFocus(Graphics g, Component mainComp,
+ Component focusedComp, TransitionAwareUI transitionAwareUI,
+ Shape focusShape, Rectangle textRect, float maxAlphaCoef,
+ int extraPadding) {
+ float focusStrength = transitionAwareUI.getTransitionTracker()
+ .getFocusStrength(focusedComp.hasFocus());
+ if (focusStrength == 0.0f)
+ return;
+
+ FocusKind focusKind = SubstanceCoreUtilities.getFocusKind(mainComp);
+ if (focusKind == FocusKind.NONE)
+ return;
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ float alpha = maxAlphaCoef * focusStrength;
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(mainComp,
+ alpha, g));
+
+ Color color = SubstanceColorUtilities.getFocusColor(mainComp,
+ transitionAwareUI);
+ graphics.setColor(color);
+ focusKind.paintFocus(mainComp, focusedComp, transitionAwareUI,
+ graphics, focusShape, textRect, extraPadding);
+ graphics.dispose();
+ }
+
+ /**
+ * Returns indication whether the specified button is a close button on some
+ * title pane.
+ *
+ * @param ab
+ * Button.
+ * @return <code>true</code> if the specified button is a close button on
+ * some title pane, <code>false</code> otherwise.
+ */
+ public static boolean isTitleCloseButton(JComponent ab) {
+ if ((ab instanceof SubstanceTitleButton)
+ && Boolean.TRUE
+ .equals(ab
+ .getClientProperty(SubstanceButtonUI.IS_TITLE_CLOSE_BUTTON)))
+ return true;
+ return false;
+ }
+
+ /**
+ * Uninstalls the specified menu item.
+ *
+ * @param menuItem
+ * Menu item.
+ */
+ public static void uninstallMenu(JMenuItem menuItem) {
+ if (menuItem instanceof JMenu) {
+ JMenu menu = (JMenu) menuItem;
+ for (Component comp : menu.getMenuComponents())
+ if (comp instanceof JMenuItem)
+ SubstanceCoreUtilities.uninstallMenu((JMenuItem) comp);
+ }
+
+ ButtonUI menuItemUI = menuItem.getUI();
+ if (menuItemUI instanceof SubstanceMenu) {
+ SubstanceMenu sMenu = (SubstanceMenu) menuItemUI;
+ if (sMenu.getAssociatedMenuItem() != null) {
+ menuItemUI.uninstallUI(menuItem);
+ }
+ }
+
+ for (ActionListener actionListener : menuItem.getActionListeners())
+ menuItem.removeActionListener(actionListener);
+
+ menuItem.removeAll();
+ }
+
+ /**
+ * Returns an icon pointed to by the specified string.
+ *
+ * @param iconResource
+ * Resource location string.
+ * @return Icon.
+ */
+ public static Icon getIcon(String iconResource) {
+ ClassLoader cl = getClassLoaderForResources();
+ URL iconUrl = cl.getResource(iconResource);
+ if (iconUrl == null)
+ return null;
+ return new IconUIResource(new ImageIcon(iconUrl));
+ }
+
+ /**
+ * Returns the class loader for loading the resource files. It is a fix by
+ * Dag Joar and Christian Schlichtherle for application running with
+ * -Xbootclasspath VM flag. In this case, the using
+ * MyClass.class.getClassLoader() would return null, but the context class
+ * loader will function properly that classes will be properly loaded
+ * regardless of whether the lib is added to the system class path, the
+ * extension class path and regardless of the class loader architecture set
+ * up by some frameworks.
+ *
+ * @return The class loader for loading the resource files.
+ */
+ public static ClassLoader getClassLoaderForResources() {
+ ClassLoader cl = (ClassLoader) UIManager.get("ClassLoader");
+ if (cl == null)
+ cl = Thread.currentThread().getContextClassLoader();
+ return cl;
+ }
+
+ public static boolean isCoveredByLightweightPopups(final Component comp) {
+ JRootPane rootPane = SwingUtilities.getRootPane(comp);
+ if (rootPane == null)
+ return false;
+ Component[] popups = rootPane.getLayeredPane().getComponentsInLayer(
+ JLayeredPane.POPUP_LAYER);
+ if (popups == null)
+ return false;
+
+ // System.out.println("Has " + popups.length + " popups");
+
+ // Convert the component bounds to the layered pane.
+ // Can't use the SwingUtilities.convertRectangle() as that
+ // eventually will try to acquire the lock on the container
+ Rectangle compBoundsConverted = SwingUtilities.convertRectangle(comp
+ .getParent(), comp.getBounds(), rootPane.getLayeredPane());
+
+ // System.out.println("Bounds : \n\torig : " + comp.getBounds()
+ // + "\n\ttrans:" + compBoundsConverted);
+
+ int popupIndexToStartWith = getPopupParentIndexOf(comp, popups) - 1;
+ // String txt = (comp instanceof JMenuItem) ? ((JMenuItem) comp)
+ // .getText() : "";
+ // System.out.println("Starting scan from popup "
+ // + popupIndexToStartWith + " for " + txt);
+ for (int i = popupIndexToStartWith; i >= 0; i--) {
+ Component popup = popups[i];
+ // System.out.println("Popup " +
+ // popup.getClass().getName());
+ // System.out.println("Popup bounds " + popup.getBounds());
+ if (compBoundsConverted.intersects(popup.getBounds())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets a component and a list of popups and returns the index of the popup
+ * that is a parent of the specified component. Is used to track issue 297
+ * and prevent visual artifacts.
+ *
+ * @param comp
+ * Component.
+ * @param popups
+ * List of popups.
+ * @return Index of the popup which is component's parent if any, or the
+ * popup list length otherwise.
+ */
+ public static int getPopupParentIndexOf(Component comp, Component[] popups) {
+ for (int i = 0; i < popups.length; i++) {
+ Component popup = popups[i];
+
+ Component currComp = comp;
+ while (currComp != null) {
+ if (currComp == popup) {
+ return i;
+ }
+ currComp = currComp.getParent();
+ }
+ }
+ return popups.length;
+ }
+
+ /**
+ * Returns the resource bundle for the specified component.
+ *
+ * @param jcomp
+ * Component.
+ * @return Resource bundle for the specified component.
+ */
+ public static ResourceBundle getResourceBundle(JComponent jcomp) {
+ if (LafWidgetUtilities.toIgnoreGlobalLocale(jcomp)) {
+ return SubstanceLookAndFeel.getLabelBundle(jcomp.getLocale());
+ } else {
+ return SubstanceLookAndFeel.getLabelBundle();
+ }
+ }
+
+ /**
+ * Returns the border painter for the specified component.
+ *
+ * @param comp
+ * Component.
+ * @return Border painter for the specified component.
+ * @see SubstanceSkin#getBorderPainter()
+ */
+ public static SubstanceBorderPainter getBorderPainter(Component comp) {
+ return SubstanceCoreUtilities.getSkin(comp).getBorderPainter();
+ }
+
+ /**
+ * Returns the highlight border painter for the specified component.
+ *
+ * @param comp
+ * Component.
+ * @return Highlight border painter for the specified component.
+ * @see SubstanceSkin#getBorderPainter()
+ * @see SubstanceSkin#getHighlightBorderPainter()
+ */
+ public static SubstanceBorderPainter getHighlightBorderPainter(
+ Component comp) {
+ SubstanceBorderPainter result = SubstanceCoreUtilities.getSkin(comp)
+ .getHighlightBorderPainter();
+ if (result != null)
+ return result;
+ return getBorderPainter(comp);
+ }
+
+ /**
+ * Returns the component hierarchy.
+ *
+ * @param comp
+ * Component.
+ * @return Component hierarchy string.
+ */
+ public static String getHierarchy(Component comp) {
+ StringBuffer buffer = new StringBuffer();
+ getHierarchy(comp, buffer, 0);
+ while (true) {
+ if (comp instanceof Window) {
+ Window w = (Window) comp;
+ comp = w.getOwner();
+ if (comp != null) {
+ buffer.append("Owner --->\n");
+ getHierarchy(comp, buffer, 0);
+ }
+ } else {
+ break;
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Computes the component hierarchy.
+ *
+ * @param comp
+ * Component.
+ * @param buffer
+ * Hierarchy representation buffer.
+ * @param level
+ * Hierarchy level.
+ */
+ public static void getHierarchy(Component comp, StringBuffer buffer,
+ int level) {
+ for (int i = 0; i < level; i++)
+ buffer.append(" ");
+ String name = comp.getName();
+ if (comp instanceof Dialog)
+ name = ((Dialog) comp).getTitle();
+ if (comp instanceof Frame)
+ name = ((Frame) comp).getTitle();
+ buffer.append(comp.getClass().getName() + "[" + name + "]\n");
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++)
+ getHierarchy(cont.getComponent(i), buffer, level + 1);
+ }
+ }
+
+ /**
+ * Returns the title pane of the specified root pane.
+ *
+ * @param rootPane
+ * Root pane.
+ * @return The title pane of the specified root pane.
+ */
+ public static JComponent getTitlePane(JRootPane rootPane) {
+ JInternalFrame jif = (JInternalFrame) SwingUtilities
+ .getAncestorOfClass(JInternalFrame.class, rootPane);
+ if ((jif != null) && (jif.getUI() instanceof SubstanceInternalFrameUI)) {
+ SubstanceInternalFrameUI ui = (SubstanceInternalFrameUI) jif
+ .getUI();
+ return ui.getTitlePane();
+ }
+ SubstanceRootPaneUI ui = (SubstanceRootPaneUI) rootPane.getUI();
+ if (ui == null)
+ return null;
+ return ui.getTitlePane();
+ }
+
+ /**
+ * Returns the arrow icon.
+ *
+ * @param button
+ * Button.
+ * @param orientation
+ * Arrow orientation.
+ * @return Arrow icon.
+ */
+ public static Icon getArrowIcon(AbstractButton button, int orientation) {
+ Icon result = new ArrowButtonTransitionAwareIcon(button, orientation);
+ return result;
+ }
+
+ /**
+ * Returns the arrow icon.
+ *
+ * @param comp
+ * Component.
+ * @param orientation
+ * Arrow orientation.
+ * @return Arrow icon.
+ */
+ public static Icon getArrowIcon(
+ JComponent comp,
+ TransitionAwareIcon.TransitionAwareUIDelegate transitionAwareUIDelegate,
+ int orientation) {
+ Icon result = new ArrowButtonTransitionAwareIcon(comp,
+ transitionAwareUIDelegate, orientation);
+ return result;
+ }
+
+ /**
+ * Returns the colorization factor for the specified component.
+ *
+ * @param c
+ * Component.
+ * @return The colorization factor for the specified component.
+ * @see SubstanceLookAndFeel#COLORIZATION_FACTOR
+ */
+ public static double getColorizationFactor(Component c) {
+ JPopupMenu popupMenu = null;
+ while (c != null) {
+ if (c instanceof JComponent) {
+ JComponent jcomp = (JComponent) c;
+ Object compProp = jcomp
+ .getClientProperty(SubstanceLookAndFeel.COLORIZATION_FACTOR);
+ if (compProp instanceof Double)
+ return (Double) compProp;
+ }
+ if (c instanceof JPopupMenu) {
+ popupMenu = (JPopupMenu) c;
+ }
+ c = c.getParent();
+ }
+
+ if (popupMenu != null) {
+ Component invoker = popupMenu.getInvoker();
+ if (popupMenu != invoker)
+ return getColorizationFactor(popupMenu.getInvoker());
+ }
+
+ Object globalProp = UIManager
+ .get(SubstanceLookAndFeel.COLORIZATION_FACTOR);
+ if (globalProp instanceof Double) {
+ return (Double) globalProp;
+ }
+
+ return 0.5;
+ }
+
+ /**
+ * Returns the skin of the specified component.
+ *
+ * @param c
+ * Component.
+ * @return The skin of the specified component.
+ * @see SubstanceLookAndFeel#SKIN_PROPERTY
+ */
+ public static SubstanceSkin getSkin(Component c) {
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return null;
+
+ if (!SubstanceRootPaneUI.hasCustomSkinOnAtLeastOneRootPane())
+ return SubstanceLookAndFeel.getCurrentSkin();
+
+ SubstanceComboPopup comboPopup = (SubstanceComboPopup) SwingUtilities
+ .getAncestorOfClass(SubstanceComboPopup.class, c);
+ if (comboPopup != null) {
+ // special case for combobox popup - take the skin
+ // of the combobox itself - issue 439
+ return getSkin(comboPopup.getCombobox());
+ }
+
+ JRootPane rootPane = SwingUtilities.getRootPane(c);
+
+ if (c instanceof SubstanceInternalFrameTitlePane) {
+ // use correct root pane for painting the
+ // title panes of internal frames
+ Component frame = c.getParent();
+ if ((frame != null) && (frame instanceof JInternalFrame)) {
+ rootPane = ((JInternalFrame) frame).getRootPane();
+ }
+ }
+ if ((c != null)
+ && (c.getParent() instanceof SubstanceInternalFrameTitlePane)) {
+ // use correct root pane for painting the
+ // title buttons of internal frames
+ Component frame = c.getParent().getParent();
+ if ((frame != null) && (frame instanceof JInternalFrame)) {
+ rootPane = ((JInternalFrame) frame).getRootPane();
+ }
+ }
+ if (rootPane != null) {
+ Object skinProp = rootPane
+ .getClientProperty(SubstanceLookAndFeel.SKIN_PROPERTY);
+ if (skinProp instanceof SubstanceSkin)
+ return (SubstanceSkin) skinProp;
+ }
+ return SubstanceLookAndFeel.getCurrentSkin();
+ }
+
+ /**
+ * Returns a hash key for the specified parameters.
+ *
+ * @param objects
+ * Key components.
+ * @return Hash key.
+ */
+ public static HashMapKey getHashKey(Object... objects) {
+ return new HashMapKey(objects);
+ }
+
+ /**
+ * Stops all Substance threads. Improper use may result in UI artifacts and
+ * runtime exceptions.
+ */
+ public static void stopThreads() {
+ TrackableThread.requestStopAllThreads();
+ }
+
+ /**
+ * Retrieves a single parameter from the VM flags.
+ *
+ * @param parameterName
+ * Parameter name.
+ * @return Parameter value.
+ */
+ public static String getVmParameter(String parameterName) {
+ String paramValue;
+ try {
+ paramValue = System.getProperty(parameterName);
+ return paramValue;
+ } catch (Exception exc) {
+ // probably running in unsecure JNLP - ignore
+ return null;
+ }
+ }
+
+ public static boolean reallyPrintThreadingExceptions() {
+ return reallyPrint;
+ }
+
+ public static boolean reallyThrowThreadingExceptions() {
+ return reallyThrow;
+ }
+
+
+ /**
+ * Tests UI threading violations on creating the specified component.
+ *
+ * @param comp
+ * Component.
+ * @throws UiThreadingViolationException
+ * If the component is created off Event Dispatch Thread.
+ */
+ public static void testComponentCreationThreadingViolation(Component comp) {
+ if ((SubstanceCoreUtilities.reallyPrintThreadingExceptions() || SubstanceCoreUtilities.reallyThrowThreadingExceptions())
+ && !SwingUtilities.isEventDispatchThread())
+ {
+ UiThreadingViolationException uiThreadingViolationError = new UiThreadingViolationException(
+ "Component creation must be done on Event Dispatch Thread");
+ if (reallyPrintThreadingExceptions()) {
+ uiThreadingViolationError.printStackTrace(System.err);
+ }
+ if (reallyThrowThreadingExceptions()) {
+ throw uiThreadingViolationError;
+ }
+ }
+ }
+
+ /**
+ * Tests UI threading violations on changing the state the specified
+ * component.
+ *
+ * @param comp
+ * Component.
+ * @throws UiThreadingViolationException
+ * If the component is changing state off Event Dispatch Thread.
+ */
+ public static void testComponentStateChangeThreadingViolation(Component comp) {
+ if ((SubstanceCoreUtilities.reallyPrintThreadingExceptions() || SubstanceCoreUtilities.reallyThrowThreadingExceptions())
+ && !SwingUtilities.isEventDispatchThread())
+ {
+ UiThreadingViolationException uiThreadingViolationError = new UiThreadingViolationException(
+ "Component state change must be done on Event Dispatch Thread");
+ if (reallyPrintThreadingExceptions()) {
+ uiThreadingViolationError.printStackTrace(System.err);
+ }
+ if (reallyThrowThreadingExceptions()) {
+ throw uiThreadingViolationError;
+ }
+ }
+ }
+
+ /**
+ * Tests UI threading violations on closing the specified window.
+ *
+ * @param w
+ * Window.
+ * @throws UiThreadingViolationException
+ * If the window is closed off Event Dispatch Thread.
+ */
+ public static void testWindowCloseThreadingViolation(Window w) {
+ if ((SubstanceCoreUtilities.reallyPrintThreadingExceptions() || SubstanceCoreUtilities.reallyThrowThreadingExceptions())
+ && !SwingUtilities.isEventDispatchThread())
+ {
+ UiThreadingViolationException uiThreadingViolationError = new UiThreadingViolationException(
+ "Window close must be done on Event Dispatch Thread");
+ if (reallyPrintThreadingExceptions()) {
+ uiThreadingViolationError.printStackTrace(System.err);
+ }
+ if (reallyThrowThreadingExceptions()) {
+ throw uiThreadingViolationError;
+ }
+ }
+ }
+
+ public static void traceSubstanceApiUsage(Component comp, String message) {
+ Window w = SwingUtilities.getWindowAncestor(comp);
+ String wTitle = null;
+ if (w instanceof Frame) {
+ wTitle = ((Frame) w).getTitle();
+ }
+ if (w instanceof Dialog) {
+ wTitle = ((Dialog) w).getTitle();
+ }
+ throw new IllegalArgumentException(message + " [component "
+ + comp.getClass().getSimpleName() + " in window "
+ + w.getClass().getSimpleName() + ":'" + wTitle + "' under "
+ + UIManager.getLookAndFeel().getName() + "]");
+ }
+
+ /**
+ * Scans {@code imageList} for best-looking image of specified dimensions.
+ * Image can be scaled and/or padded with transparency.
+ */
+ public static BufferedImage getScaledIconImage(
+ java.util.List<Image> imageList, int width, int height) {
+ if (width == 0 || height == 0) {
+ return null;
+ }
+ Image bestImage = null;
+ int bestWidth = 0;
+ int bestHeight = 0;
+ double bestSimilarity = 3; // Impossibly high value
+ for (Image im : imageList) {
+ // Iterate imageList looking for best matching image.
+ // 'Similarity' measure is defined as good scale factor and small
+ // insets.
+ // best possible similarity is 0 (no scale, no insets).
+ // It's found while the experiments that good-looking result is
+ // achieved
+ // with scale factors x1, x3/4, x2/3, xN, x1/N.
+ if (im == null) {
+ continue;
+ }
+ int iw;
+ int ih;
+ try {
+ iw = im.getWidth(null);
+ ih = im.getHeight(null);
+ } catch (Exception e) {
+ continue;
+ }
+ if (iw > 0 && ih > 0) {
+ // Calc scale factor
+ double scaleFactor = Math.min((double) width / (double) iw,
+ (double) height / (double) ih);
+ // Calculate scaled image dimensions
+ // adjusting scale factor to nearest "good" value
+ int adjw;
+ int adjh;
+ double scaleMeasure; // 0 - best (no) scale, 1 - impossibly
+ // bad
+ if (scaleFactor >= 2) {
+ // Need to enlarge image more than twice
+ // Round down scale factor to multiply by integer value
+ scaleFactor = Math.floor(scaleFactor);
+ adjw = iw * (int) scaleFactor;
+ adjh = ih * (int) scaleFactor;
+ scaleMeasure = 1.0 - 0.5 / scaleFactor;
+ } else if (scaleFactor >= 1) {
+ // Don't scale
+ //scaleFactor = 1.0;
+ adjw = iw;
+ adjh = ih;
+ scaleMeasure = 0;
+ } else if (scaleFactor >= 0.75) {
+ // Multiply by 3/4
+ //scaleF`actor = 0.75;
+ adjw = iw * 3 / 4;
+ adjh = ih * 3 / 4;
+ scaleMeasure = 0.3;
+ } else if (scaleFactor >= 0.6666) {
+ // Multiply by 2/3
+ //scaleFactor = 0.6666;
+ adjw = iw * 2 / 3;
+ adjh = ih * 2 / 3;
+ scaleMeasure = 0.33;
+ } else {
+ // Multiply size by 1/scaleDivider
+ // where scaleDivider is minimum possible integer
+ // larger than 1/scaleFactor
+ double scaleDivider = Math.ceil(1.0 / scaleFactor);
+ //scaleFactor = 1.0 / scaleDivider;
+ adjw = (int) Math.round(iw / scaleDivider);
+ adjh = (int) Math.round(ih / scaleDivider);
+ scaleMeasure = 1.0 - 1.0 / scaleDivider;
+ }
+ double similarity = ((double) width - (double) adjw) / width
+ + ((double) height - (double) adjh) / height + // Large
+ // padding
+ // is
+ // bad
+ scaleMeasure; // Large rescale is bad
+ if (similarity < bestSimilarity) {
+ bestSimilarity = similarity;
+ bestImage = im;
+ bestWidth = adjw;
+ bestHeight = adjh;
+ }
+ if (similarity == 0)
+ break;
+ }
+ }
+ if (bestImage == null) {
+ // No images were found, possibly all are broken
+ return null;
+ }
+ BufferedImage bimage = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = bimage.createGraphics();
+ g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+
+ // BufferedImage bestBuffered = getBlankImage(bestImage.getWidth(null),
+ // bestImage.getHeight(null));
+ // bestBuffered.getGraphics().drawImage(bestImage, 0, 0, null);
+ // BufferedImage scaled =
+ // LafWidgetUtilities.createThumbnail(bestBuffered,
+ // bestWidth);
+
+ try {
+ int x = (width - bestWidth) / 2;
+ int y = (height - bestHeight) / 2;
+ g.drawImage(bestImage, x, y, bestWidth, bestHeight, null);
+ } finally {
+ g.dispose();
+ }
+ return bimage;
+ }
+
+ public static boolean canReplaceChildBackgroundColor(Color background) {
+ return (background instanceof UIResource)
+ || (background instanceof SubstanceColorResource);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static JTextComponent getTextComponentForTransitions(Component c) {
+ if (!(c instanceof JComponent))
+ return null;
+
+ TextComponentAware tcaui = (TextComponentAware) ((JComponent) c)
+ .getClientProperty(TEXT_COMPONENT_AWARE);
+ if (tcaui != null) {
+ return tcaui.getTextComponent(c);
+ }
+
+ if (c instanceof JTextComponent) {
+ return (JTextComponent) c;
+ }
+ return null;
+ }
+
+ public static SwingRepaintCallback getTextComponentRepaintCallback(
+ JTextComponent textComponent) {
+ Component c = textComponent;
+ while (c != null) {
+ if (c instanceof JComponent) {
+ TextComponentAware tcaui = (TextComponentAware) ((JComponent) c)
+ .getClientProperty(TEXT_COMPONENT_AWARE);
+ if (tcaui != null) {
+ return new SwingRepaintCallback(c);
+ }
+ }
+ c = c.getParent();
+ }
+ return new SwingRepaintCallback(textComponent);
+ }
+
+ public static boolean isOpaque(Component c) {
+ return c.isOpaque();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceDropDownButton.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceDropDownButton.java
new file mode 100644
index 0000000..495686f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceDropDownButton.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+
+/**
+ * Drop down button in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public final class SubstanceDropDownButton extends JButton implements
+ SubstanceInternalArrowButton {
+ static {
+ AnimationConfigurationManager.getInstance().disallowAnimations(
+ AnimationFacet.GHOSTING_BUTTON_PRESS,
+ SubstanceDropDownButton.class);
+ AnimationConfigurationManager.getInstance().disallowAnimations(
+ AnimationFacet.GHOSTING_ICON_ROLLOVER,
+ SubstanceDropDownButton.class);
+ }
+
+ /**
+ * Simple constructor.
+ *
+ * @param parent
+ * The parent component.
+ */
+ public SubstanceDropDownButton(JComponent parent) {
+ super("");
+ this.setModel(new DefaultButtonModel() {
+ @Override
+ public void setArmed(boolean armed) {
+ super.setArmed(this.isPressed() || armed);
+ }
+ });
+ this.setEnabled(parent.isEnabled());
+ this.setFocusable(false);
+ this.setRequestFocusEnabled(parent.isEnabled());
+
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(parent);
+ int tbInset = SubstanceSizeUtils.getAdjustedSize(fontSize, 1, 2, 1,
+ false);
+ int lrInset = 0;
+ this.setMargin(new Insets(tbInset, lrInset, tbInset, tbInset));
+
+ this.setBorderPainted(false);
+ this.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY, Boolean.TRUE);
+ this.setOpaque(false);
+ }
+
+ @Override
+ public void setBorder(Border border) {
+ }
+
+ @Override
+ protected void paintBorder(Graphics g) {
+ if (SubstanceCoreUtilities.isButtonNeverPainted(this)) {
+ return;
+ }
+
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) this.getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ ComponentState currState = modelStateInfo.getCurrModelState();
+ float extraAlpha = stateTransitionTracker.getActiveStrength();
+
+ if (currState == ComponentState.DISABLED_UNSELECTED)
+ extraAlpha = 0.0f;
+
+ if (extraAlpha == 0.0f)
+ return;
+
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(this);
+ int borderDelta = (int) Math.floor(1.5 * SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize));
+ float radius = Math.max(0, 2.0f
+ * SubstanceSizeUtils
+ .getClassicButtonCornerRadius(componentFontSize)
+ - borderDelta);
+
+ int width = getWidth();
+ int height = getHeight();
+
+ int offsetX = this.getX();
+ int offsetY = this.getY();
+ JComponent parent = (JComponent) this.getParent();
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this, ColorSchemeAssociationKind.BORDER,
+ currState);
+
+ BufferedImage offscreen = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D g2offscreen = offscreen.createGraphics();
+
+ SubstanceImageCreator.paintTextComponentBorder(this, g2offscreen, 0, 0,
+ width, height, radius, baseBorderScheme);
+ g2offscreen.translate(-offsetX, -offsetY);
+ SubstanceImageCreator.paintTextComponentBorder(parent, g2offscreen, 0,
+ 0, parent.getWidth(), parent.getHeight(), radius,
+ baseBorderScheme);
+ g2offscreen.translate(offsetX, offsetY);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ g2offscreen.setComposite(AlphaComposite.SrcOver
+ .derive(contribution));
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this, ColorSchemeAssociationKind.BORDER,
+ activeState);
+
+ SubstanceImageCreator.paintTextComponentBorder(this, g2offscreen,
+ 0, 0, width, height, radius, borderScheme);
+ g2offscreen.translate(-offsetX, -offsetY);
+ SubstanceImageCreator.paintTextComponentBorder(parent, g2offscreen,
+ 0, 0, parent.getWidth(), parent.getHeight(), radius,
+ borderScheme);
+ g2offscreen.translate(offsetX, offsetY);
+ }
+ g2offscreen.dispose();
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this, extraAlpha,
+ g));
+
+ g2d.drawImage(offscreen, 0, 0, null);
+ g2d.dispose();
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(this);
+ int width = getWidth();
+ int height = getHeight();
+ int clipDelta = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize);
+
+ if (this.getComponentOrientation().isLeftToRight()) {
+ g2d.clipRect(clipDelta, 0, width - clipDelta, height);
+ } else {
+ g2d.clipRect(0, 0, width - clipDelta, height);
+ }
+ super.paint(g2d);
+ g2d.dispose();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceImageCreator.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceImageCreator.java
new file mode 100644
index 0000000..2867ca4
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceImageCreator.java
@@ -0,0 +1,2337 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.*;
+import java.awt.MultipleGradientPaint.CycleMethod;
+import java.awt.geom.*;
+import java.awt.image.*;
+
+import javax.swing.*;
+import javax.swing.text.JTextComponent;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.SubstanceConstants.Side;
+import org.pushingpixels.substance.api.painter.border.FlatBorderPainter;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
+import org.pushingpixels.substance.internal.colorscheme.ShiftColorScheme;
+import org.pushingpixels.substance.internal.painter.SimplisticFillPainter;
+import org.pushingpixels.substance.internal.utils.filters.*;
+
+/**
+ * Provides utility functions for creating various images for <b>Substance </b>
+ * look and feel. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public final class SubstanceImageCreator {
+ /**
+ * Custom fill painter for filling the checkmarks of checkboxes and radio
+ * buttons.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class SimplisticSoftBorderReverseFillPainter extends
+ SimplisticFillPainter {
+ /**
+ * Singleton instance.
+ */
+ public static final SubstanceFillPainter INSTANCE = new SimplisticSoftBorderReverseFillPainter();
+
+ /**
+ * Private constructor.
+ */
+ private SimplisticSoftBorderReverseFillPainter() {
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Simplistic Soft Border Reverse";
+ }
+
+ @Override
+ public Color getTopFillColor(SubstanceColorScheme fillScheme) {
+ return super.getBottomFillColor(fillScheme);
+ }
+
+ @Override
+ public Color getBottomFillColor(SubstanceColorScheme fillScheme) {
+ return super.getTopFillColor(fillScheme);
+ }
+ }
+
+ /**
+ * Paints border instance of specified dimensions and status.
+ *
+ * @param c
+ * Component.
+ * @param graphics
+ * Graphics context.
+ * @param x
+ * Component left X (in graphics context).
+ * @param y
+ * Component top Y (in graphics context).
+ * @param width
+ * Border width.
+ * @param height
+ * Border height.
+ * @param radius
+ * Border radius.
+ * @param borderScheme
+ * border color scheme.
+ */
+ public static void paintBorder(Component c, Graphics2D graphics, int x,
+ int y, int width, int height, float radius,
+ SubstanceColorScheme borderScheme) {
+
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(c);
+ graphics.translate(x, y);
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(c);
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize) / 2.0);
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(width, height,
+ radius, null, borderDelta);
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize);
+ boolean skipInnerBorder = (c instanceof JTextComponent)
+ || ((SwingUtilities.getAncestorOfClass(CellRendererPane.class,
+ c) != null) && (SwingUtilities.getAncestorOfClass(
+ JFileChooser.class, c) != null));
+ GeneralPath contourInner = skipInnerBorder ? null
+ : SubstanceOutlineUtilities.getBaseOutline(width, height,
+ radius - borderThickness, null, borderThickness
+ + borderDelta);
+ borderPainter.paintBorder(graphics, c, width, height, contour,
+ contourInner, borderScheme);
+ graphics.translate(-x, -y);
+ }
+
+ /**
+ * Paints border instance of specified dimensions and status.
+ *
+ * @param c
+ * Component.
+ * @param graphics
+ * Graphics context.
+ * @param x
+ * Component left X (in graphics context).
+ * @param y
+ * Component top Y (in graphics context).
+ * @param width
+ * Border width.
+ * @param height
+ * Border height.
+ * @param radius
+ * Border radius.
+ * @param borderScheme
+ * Border color scheme.
+ */
+ public static void paintTextComponentBorder(JComponent c,
+ Graphics graphics, int x, int y, int width, int height,
+ float radius, SubstanceColorScheme borderScheme) {
+
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(c);
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize) / 2.0);
+ Shape contour = SubstanceOutlineUtilities
+ .getBaseOutline(width, height, radius,
+ SubstanceCoreUtilities.getSides(c,
+ SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY),
+ borderDelta);
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize);
+ GeneralPath contourInner = SubstanceOutlineUtilities.getBaseOutline(
+ width, height, radius - borderThickness, null, borderThickness
+ + borderDelta);
+
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.translate(x, y);
+
+ ComponentState stateForOuterBorder = c.isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED;
+ Color lightColor = SubstanceColorUtilities.getDefaultBackgroundColor(c,
+ stateForOuterBorder);
+
+ if (stateForOuterBorder.isDisabled()) {
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(c,
+ stateForOuterBorder);
+ lightColor = SubstanceColorUtilities.getAlphaColor(lightColor,
+ (int) (255 * alpha));
+ }
+
+ Color outerColor = SubstanceColorUtilities
+ .getOuterTextComponentBorderColor(lightColor);
+ float[] hsb = Color.RGBtoHSB(lightColor.getRed(),
+ lightColor.getGreen(), lightColor.getBlue(), null);
+ // hsb[1] = hsb[1] * 0.5f;
+ // System.out.println(lightColor + " -> " + hsb[1] + ":" + hsb[2]);
+ double bottomInnerBlend = 0.85;
+ double topInnerBlend = 0.8;
+ if (hsb[2] < 0.3f) {
+ bottomInnerBlend = 0.6;
+ topInnerBlend = 0.95;
+ } else if (hsb[2] < 0.5f) {
+ bottomInnerBlend = 0.8;
+ } else if (hsb[2] < 0.75f) {
+ bottomInnerBlend = 0.7;
+ } else {
+ }
+ Color darkColor = borderScheme.getDarkColor();
+
+ Color topInnerColor = SubstanceColorUtilities.getInterpolatedColor(
+ darkColor, lightColor, topInnerBlend);
+ Color bottomInnerColor = SubstanceColorUtilities.getInterpolatedColor(
+ lightColor, darkColor, bottomInnerBlend);
+
+ // darkColor = SubstanceColorUtilities.getAlphaColor(darkColor, 196);
+
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_NORMALIZE);
+
+ float strokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_ROUND));
+
+ g2d.setPaint(new GradientPaint(0, 0, outerColor, 0, height, outerColor));
+ // g2d.setColor(Color.red);
+ g2d.draw(contour);
+ g2d.setPaint(new GradientPaint(0, 0, topInnerColor, 0, height,
+ bottomInnerColor));
+ // g2d.setColor(Color.green);
+ g2d.draw(contourInner);
+
+ // System.out.println("Outer : " + outerColor + "[" + lightColor
+ // + "] from " + borderScheme.getDisplayName());
+
+ g2d.dispose();
+ }
+
+ /**
+ * Retrieves check mark image.
+ *
+ * @param dimension
+ * Check mark dimension.
+ * @param isEnabled
+ * Enabled status.
+ * @param scheme
+ * Color scheme for the check mark.
+ * @param checkMarkVisibility
+ * Checkmark visibility in 0.0-1.0 range.
+ * @return Check mark image.
+ */
+ private static BufferedImage getCheckMark(int dimension, boolean isEnabled,
+ SubstanceColorScheme scheme, float checkMarkVisibility) {
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(dimension,
+ dimension);
+
+ // get graphics and set hints
+ Graphics2D graphics = (Graphics2D) result.getGraphics();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ // create curved checkbox path
+ GeneralPath path = new GeneralPath();
+
+ path.moveTo(0.25f * dimension, 0.5f * dimension);
+ path.quadTo(0.37f * dimension, 0.6f * dimension, 0.47f * dimension,
+ 0.8f * dimension);
+ path.quadTo(0.55f * dimension, 0.5f * dimension, 0.85f * dimension, 0f);
+
+ // compute the x-based clip for the visibility
+ float xClipStart = 0.15f * dimension;
+ float xClipEnd = 0.95f * dimension;
+
+ float xClipRealEnd = xClipStart + (xClipEnd - xClipStart)
+ * checkMarkVisibility;
+
+ graphics.setClip(0, 0, (int) Math.ceil(xClipRealEnd), dimension);
+
+ graphics.setColor(SubstanceColorUtilities.getMarkColor(scheme,
+ isEnabled));
+ Stroke stroke = new BasicStroke((float) 0.15 * dimension,
+ BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+ graphics.setStroke(stroke);
+ graphics.draw(path);
+
+ return result;
+ }
+
+ /**
+ * Returns arrow icon for the specified parameters.
+ *
+ * @param fontSize
+ * Font size.
+ * @param direction
+ * Arrow direction.
+ * @param colorScheme
+ * Arrow icon color scheme.
+ * @return Arrow icon.
+ */
+ public static Icon getArrowIcon(int fontSize, int direction,
+ SubstanceColorScheme colorScheme) {
+ float width = SubstanceSizeUtils.getArrowIconWidth(fontSize);
+ float height = SubstanceSizeUtils.getArrowIconHeight(fontSize);
+ if (direction == SwingConstants.CENTER)
+ height *= 2;
+ float strokeWidth = SubstanceSizeUtils.getArrowStrokeWidth(fontSize);
+ return new ImageIcon(getArrow(width, height, strokeWidth, direction,
+ colorScheme));
+ }
+
+ /**
+ * Retrieves arrow icon.
+ *
+ * @param width
+ * Arrow width.
+ * @param height
+ * Arrow height.
+ * @param strokeWidth
+ * Stroke width.
+ * @param direction
+ * Arrow direction.
+ * @param scheme
+ * Color scheme for the arrow.
+ * @return Arrow image.
+ * @see SwingConstants#NORTH
+ * @see SwingConstants#WEST
+ * @see SwingConstants#SOUTH
+ * @see SwingConstants#EAST
+ */
+ public static Icon getArrowIcon(float width, float height,
+ float strokeWidth, int direction, SubstanceColorScheme scheme) {
+ return new ImageIcon(getArrow(width, height, strokeWidth, direction,
+ scheme));
+ }
+
+ /**
+ * Retrieves arrow image.
+ *
+ * @param width
+ * Arrow width.
+ * @param height
+ * Arrow height.
+ * @param strokeWidth
+ * Stroke width.
+ * @param direction
+ * Arrow direction.
+ * @param scheme
+ * Color scheme for the arrow.
+ * @return Arrow image.
+ * @see SwingConstants#NORTH
+ * @see SwingConstants#WEST
+ * @see SwingConstants#SOUTH
+ * @see SwingConstants#EAST
+ * @see SwingConstants#CENTER
+ */
+ public static BufferedImage getArrow(float width, float height,
+ float strokeWidth, int direction, SubstanceColorScheme scheme) {
+ BufferedImage arrowImage = SubstanceCoreUtilities.getBlankImage(
+ (int) width, (int) height);
+
+ // System.out.println(width + ":" + height + ":" + strokeWidth);
+
+ // get graphics and set hints
+ Graphics2D graphics = (Graphics2D) arrowImage.getGraphics();
+
+ // graphics.setColor(Color.red);
+ // graphics.fillRect(0, 0, width, height);
+
+ graphics.translate(1, 1);
+ width -= 2;
+ height -= 2;
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ Color arrowColor = SubstanceColorUtilities.getMarkColor(scheme, true);
+
+ graphics.setColor(arrowColor);
+ int cap = (width < 15) ? BasicStroke.CAP_BUTT : BasicStroke.CAP_ROUND;
+ Stroke stroke = new BasicStroke(strokeWidth, cap,
+ BasicStroke.JOIN_MITER);
+
+ graphics.setStroke(stroke);
+
+ int cushion = (int) strokeWidth / 2;
+ if (direction == SwingConstants.CENTER) {
+ BufferedImage top = getArrow(width, height / 2, strokeWidth,
+ SwingConstants.NORTH, scheme);
+ BufferedImage bottom = getArrow(width, height / 2, strokeWidth,
+ SwingConstants.SOUTH, scheme);
+ graphics.drawImage(top, 0, 1, null);
+ graphics.drawImage(bottom, 0, (int) height / 2 - 1, null);
+ return arrowImage;
+ } else {
+ GeneralPath gp = new GeneralPath();
+ gp.moveTo(cushion, strokeWidth);
+ gp.lineTo((float) 0.5 * (width - 1), height - 1 - cushion);
+ gp.lineTo(width - 1 - cushion, strokeWidth);
+ graphics.draw(gp);
+
+ // graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ // RenderingHints.VALUE_ANTIALIAS_OFF);
+ // graphics.setStroke(new BasicStroke(1.0f, cap,
+ // BasicStroke.JOIN_MITER));
+ // graphics.setColor(Color.red);
+ // graphics.drawRect(0, 0, (int)width - 1, (int)height - 1);
+
+ int quadrantCounterClockwise = 0;
+ switch (direction) {
+ case SwingConstants.NORTH:
+ quadrantCounterClockwise = 2;
+ break;
+ case SwingConstants.WEST:
+ quadrantCounterClockwise = 1;
+ break;
+ case SwingConstants.SOUTH:
+ quadrantCounterClockwise = 0;
+ break;
+ case SwingConstants.EAST:
+ quadrantCounterClockwise = 3;
+ break;
+ }
+ BufferedImage rotatedImage = SubstanceImageCreator.getRotated(
+ arrowImage, quadrantCounterClockwise);
+
+ return rotatedImage;
+ }
+ }
+
+ /**
+ * Returns double arrow icon for the specified parameters.
+ *
+ * @param fontSize
+ * Font size.
+ * @param deltaWidth
+ * Arrow width delta.
+ * @param deltaHeight
+ * Arrow height delta.
+ * @param deltaStrokeWidth
+ * Arrow stroke width delta.
+ * @param direction
+ * Arrow direction.
+ * @param colorScheme
+ * Color scheme for the arrow.
+ * @return Double arrow icon.
+ */
+ public static Icon getDoubleArrowIconDelta(int fontSize, float deltaWidth,
+ float deltaHeight, float deltaStrokeWidth, int direction,
+ SubstanceColorScheme colorScheme) {
+ float arrowWidth = SubstanceSizeUtils.getArrowIconWidth(fontSize)
+ + deltaWidth;
+ float arrowHeight = SubstanceSizeUtils.getArrowIconHeight(fontSize)
+ + deltaHeight;
+ float arrowStrokeWidth = SubstanceSizeUtils
+ .getDoubleArrowStrokeWidth(fontSize) + deltaStrokeWidth;
+ return getDoubleArrowIcon(fontSize, arrowWidth, arrowHeight,
+ arrowStrokeWidth, direction, colorScheme);
+ }
+
+ /**
+ * Retrieves arrow icon.
+ *
+ * @param width
+ * Arrow width.
+ * @param height
+ * Arrow height.
+ * @param strokeWidth
+ * Stroke width.
+ * @param direction
+ * Arrow direction.
+ * @param colorScheme
+ * Color scheme for the arrow.
+ * @return Arrow image.
+ * @see SwingConstants#NORTH
+ * @see SwingConstants#WEST
+ * @see SwingConstants#SOUTH
+ * @see SwingConstants#EAST
+ */
+ public static Icon getDoubleArrowIcon(int fontSize, float width,
+ float height, float strokeWidth, int direction,
+ SubstanceColorScheme colorScheme) {
+ int delta = 3 + 2 * SubstanceSizeUtils.getExtraPadding(fontSize) / 3;
+ boolean toggle = (direction == SwingConstants.WEST)
+ || (direction == SwingConstants.EAST);
+ if (toggle) {
+ float temp = width;
+ width = height;
+ height = temp;
+ }
+ BufferedImage downArrowImage = SubstanceCoreUtilities.getBlankImage(
+ (int) width, (int) height);
+
+ BufferedImage singleArrow = getArrow(width,
+ Math.max(1, height - delta), strokeWidth, SwingConstants.SOUTH,
+ colorScheme);
+
+ // get graphics and set hints
+ Graphics2D graphics = (Graphics2D) downArrowImage.getGraphics();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ int arrowHeight = singleArrow.getHeight();
+ int arrowWidth = singleArrow.getWidth();
+ int offset = toggle ? (int) (height - arrowHeight - delta) / 2
+ : (int) (width - arrowWidth - delta) / 2;
+ graphics.drawImage(singleArrow, 0, offset, null);
+ graphics.drawImage(singleArrow, 0, offset + delta, null);
+
+ int quadrantCounterClockwise = 0;
+ switch (direction) {
+ case SwingConstants.NORTH:
+ quadrantCounterClockwise = 2;
+ break;
+ case SwingConstants.WEST:
+ quadrantCounterClockwise = 1;
+ break;
+ case SwingConstants.SOUTH:
+ quadrantCounterClockwise = 0;
+ break;
+ case SwingConstants.EAST:
+ quadrantCounterClockwise = 3;
+ break;
+ }
+ BufferedImage arrowImage = SubstanceImageCreator.getRotated(
+ downArrowImage, quadrantCounterClockwise);
+
+ return new ImageIcon(arrowImage);
+ }
+
+ /**
+ * Returns rotated image.
+ *
+ * @param bi
+ * Image to rotate.
+ * @param quadrantClockwise
+ * Amount of quadrants to rotate in clockwise directio. The
+ * rotation angle is 90 times this value.
+ * @return Rotated image.
+ */
+ public static BufferedImage getRotated(BufferedImage bi,
+ int quadrantClockwise) {
+ quadrantClockwise = quadrantClockwise % 4;
+ int width = bi.getWidth();
+ int height = bi.getHeight();
+ if ((quadrantClockwise == 1) || (quadrantClockwise == 3)) {
+ width = bi.getHeight();
+ height = bi.getWidth();
+ }
+ BufferedImage biRot = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ AffineTransform at = null;
+ switch (quadrantClockwise) {
+ case 1:
+ at = AffineTransform.getTranslateInstance(width, 0);
+ at.rotate(Math.PI / 2);
+ break;
+ case 2:
+ at = AffineTransform.getTranslateInstance(width, height);
+ at.rotate(Math.PI);
+ break;
+ case 3:
+ at = AffineTransform.getTranslateInstance(0, height);
+ at.rotate(-Math.PI / 2);
+ }
+ Graphics2D rotg = biRot.createGraphics();
+ if (at != null)
+ rotg.setTransform(at);
+ rotg.drawImage(bi, 0, 0, null);
+ rotg.dispose();
+ return biRot;
+ }
+
+ /**
+ * Returns rotated image.
+ *
+ * @param bi
+ * Image to rotate.
+ * @param quadrantClockwise
+ * Amount of quadrants to rotate in clockwise directio. The
+ * rotation angle is 90 times this value.
+ * @return Rotated image.
+ */
+ public static VolatileImage getRotated(final VolatileImage bi,
+ int quadrantClockwise) {
+ quadrantClockwise = quadrantClockwise % 4;
+ int width = bi.getWidth();
+ int height = bi.getHeight();
+ if ((quadrantClockwise == 1) || (quadrantClockwise == 3)) {
+ width = bi.getHeight();
+ height = bi.getWidth();
+ }
+ VolatileImage biRot = SubstanceCoreUtilities.getBlankVolatileImage(
+ width, height);
+ AffineTransform at = null;
+ switch (quadrantClockwise) {
+ case 1:
+ at = AffineTransform.getTranslateInstance(width, 0);
+ at.rotate(Math.PI / 2);
+ break;
+ case 2:
+ at = AffineTransform.getTranslateInstance(width, height);
+ at.rotate(Math.PI);
+ break;
+ case 3:
+ at = AffineTransform.getTranslateInstance(0, height);
+ at.rotate(-Math.PI / 2);
+ }
+ Graphics2D rotg = biRot.createGraphics();
+ if (at != null)
+ rotg.setTransform(at);
+ rotg.drawImage(bi, 0, 0, null);
+ rotg.dispose();
+ return biRot;
+ }
+
+ /**
+ * Translated the specified icon to grey scale.
+ *
+ * @param icon
+ * Icon.
+ * @return Greyscale version of the specified icon.
+ */
+ public static Icon toGreyscale(Icon icon) {
+ if (icon == null) {
+ return null;
+ }
+
+ int width = icon.getIconWidth();
+ int height = icon.getIconHeight();
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+
+ icon.paintIcon(null, result.getGraphics(), 0, 0);
+ return new ImageIcon(new GrayscaleFilter().filter(result, null));
+ }
+
+ /**
+ * Makes the specified icon transparent.
+ *
+ * @param c
+ * Component.
+ * @param icon
+ * Icon.
+ * @param alpha
+ * The alpha of the resulting image. The closer this value is to
+ * 0.0, the more transparent resulting image will be.
+ * @return Transparent version of the specified icon.
+ */
+ public static Icon makeTransparent(Component c, Icon icon, double alpha) {
+ if (icon == null) {
+ return null;
+ }
+
+ int width = icon.getIconWidth();
+ int height = icon.getIconHeight();
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ icon.paintIcon(c, result.getGraphics(), 0, 0);
+ return new ImageIcon(new TranslucentFilter(alpha).filter(result, null));
+ }
+
+ // /**
+ // * Retrieves radio button of the specified size that matches the specified
+ // * parameters.
+ // *
+ // * @param component
+ // * Component.
+ // * @param dimension
+ // * Radio button dimension.
+ // * @param componentState
+ // * Component state.
+ // * @param prevState
+ // * Previous component state.
+ // * @param offsetX
+ // * Offset on X axis - should be positive in order to see the
+ // * entire radio button.
+ // * @param scheme1
+ // * First color scheme.
+ // * @param scheme2
+ // * Second color scheme.
+ // * @param interpolationCyclePos
+ // * Interpolation cycle.
+ // * @param checkMarkVisibility
+ // * Checkmark visibility in 0.0-1.0 range.
+ // * @return Radio button of the specified size that matches the specified
+ // * parameters.
+ // */
+ // public static BufferedImage getRadioButton(JComponent component,
+ // SubstanceFillPainter fillPainter,
+ // SubstanceBorderPainter borderPainter, int dimension,
+ // ComponentState componentState, ComponentState prevState,
+ // int offsetX, SubstanceColorScheme currFillColorScheme,
+ // SubstanceColorScheme prevFillColorScheme,
+ // SubstanceColorScheme currMarkColorScheme,
+ // SubstanceColorScheme prevMarkColorScheme,
+ // SubstanceColorScheme currBorderColorScheme,
+ // SubstanceColorScheme prevBorderColorScheme,
+ // float interpolationCyclePos, float checkMarkVisibility) {
+ //
+ // // ComponentState.ColorSchemeKind kind = componentState
+ // // .getColorSchemeKind();
+ //
+ // float cyclePos = (currFillColorScheme != prevFillColorScheme) ?
+ // interpolationCyclePos
+ // : componentState.getCyclePosition();
+ // float borderCyclePos = (currBorderColorScheme != prevBorderColorScheme) ?
+ // interpolationCyclePos
+ // : componentState.getCyclePosition();
+ // float markCyclePos = (currMarkColorScheme != prevMarkColorScheme) ?
+ // interpolationCyclePos
+ // : componentState.getCyclePosition();
+ //
+ // if (componentState.getColorSchemeKind() != ColorSchemeKind.CURRENT) {
+ // fillPainter = SimplisticSoftBorderReverseFillPainter.INSTANCE;
+ // }
+ //
+ // float borderThickness = SubstanceSizeUtils
+ // .getBorderStrokeWidth(dimension);
+ // int delta = (int) (borderThickness - 0.6);
+ // // System.out.println(dimension + ":" + borderThickness + ":" + delta);
+ //
+ // // float fDelta = borderThickness / 2.0f;
+ // Shape contourBorder = new Ellipse2D.Float(delta, delta, dimension - 2
+ // * delta - 1, dimension - 2 * delta - 1);
+ //
+ // BufferedImage offBackground = SubstanceCoreUtilities.getBlankImage(
+ // dimension + offsetX, dimension);
+ // Graphics2D graphics = (Graphics2D) offBackground.getGraphics();
+ // float alpha = SubstanceColorSchemeUtilities.getAlpha(component,
+ // componentState);
+ // graphics.setComposite(AlphaComposite.getInstance(
+ // AlphaComposite.SRC_OVER, alpha));
+ //
+ // graphics.translate(offsetX, 0);
+ // fillPainter.paintContourBackground(graphics, component, dimension,
+ // dimension, contourBorder, false, currFillColorScheme,
+ // prevFillColorScheme, cyclePos, true,
+ // currFillColorScheme != prevFillColorScheme);
+ //
+ // Shape contourInner = new Ellipse2D.Float(delta + borderThickness, delta
+ // + borderThickness, dimension - 2 * delta - 2 * borderThickness,
+ // dimension - 2 * delta - 2 * borderThickness);
+ //
+ // borderPainter.paintBorder(graphics, component, dimension, dimension,
+ // contourBorder, contourInner, currBorderColorScheme,
+ // prevBorderColorScheme, borderCyclePos,
+ // currBorderColorScheme != prevBorderColorScheme);
+ // graphics.setComposite(AlphaComposite.SrcOver);
+ // // graphics.translate(-offsetX, 0);
+ //
+ // float rc = dimension / 2.0f;
+ // float radius = dimension / 4.5f;
+ // // graphics.translate(offsetX, 0);
+ //
+ // graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ // RenderingHints.VALUE_ANTIALIAS_ON);
+ //
+ // if (componentState.isFacetActive(AnimationFacet.SELECTION)
+ // || (checkMarkVisibility > 0.0)) {
+ // // mark
+ // Shape markOval = new Ellipse2D.Double(rc - radius, rc - radius,
+ // 2 * radius, 2 * radius);
+ //
+ // graphics.setComposite(AlphaComposite.getInstance(
+ // AlphaComposite.SRC_OVER, alpha * checkMarkVisibility));
+ // drawRadioMark(graphics, SubstanceColorUtilities
+ // .getMarkColor(currMarkColorScheme, componentState
+ // .getColorSchemeKind() != ColorSchemeKind.DISABLED),
+ // markOval);
+ // graphics.setComposite(AlphaComposite.getInstance(
+ // AlphaComposite.SRC_OVER, alpha * checkMarkVisibility
+ // * markCyclePos));
+ // drawRadioMark(graphics, SubstanceColorUtilities
+ // .getMarkColor(prevMarkColorScheme, componentState
+ // .getColorSchemeKind() != ColorSchemeKind.DISABLED),
+ // markOval);
+ // } else {
+ // // draw ghost mark holder
+ // graphics.setPaint(new GradientPaint(rc + radius, rc - radius,
+ // currFillColorScheme.getDarkColor(), rc - radius, rc
+ // + radius, currFillColorScheme.getLightColor()));
+ // Shape markOval = new Ellipse2D.Double(rc - radius, rc - radius,
+ // 2 * radius, 2 * radius);
+ // graphics.setComposite(AlphaComposite.getInstance(
+ // AlphaComposite.SRC_OVER, alpha * 0.3f));
+ // graphics.fill(markOval);
+ // }
+ // graphics.translate(-offsetX, 0);
+ //
+ // return offBackground;
+ // }
+
+ /**
+ * Retrieves radio button of the specified size that matches the specified
+ * parameters.
+ *
+ * @param component
+ * Component.
+ * @param dimension
+ * Radio button dimension.
+ * @param componentState
+ * Component state.
+ * @param offsetX
+ * Offset on X axis - should be positive in order to see the
+ * entire radio button.
+ * @param fillColorScheme
+ * Color scheme for the inner fill.
+ * @param markColorScheme
+ * Color scheme for the check mark.
+ * @param borderColorScheme
+ * Color scheme for the border.
+ * @param checkMarkVisibility
+ * Check mark visibility in 0.0-1.0 range.
+ * @return Radio button of the specified size that matches the specified
+ * parameters.
+ */
+ public static BufferedImage getRadioButton(JComponent component,
+ SubstanceFillPainter fillPainter,
+ SubstanceBorderPainter borderPainter, int dimension,
+ ComponentState componentState, int offsetX,
+ SubstanceColorScheme fillColorScheme,
+ SubstanceColorScheme markColorScheme,
+ SubstanceColorScheme borderColorScheme, float checkMarkVisibility) {
+
+ // ComponentState.ColorSchemeKind kind = componentState
+ // .getColorSchemeKind();
+
+ if (!componentState.isActive()) {
+ fillPainter = SimplisticSoftBorderReverseFillPainter.INSTANCE;
+ }
+
+ float borderThickness = SubstanceSizeUtils
+ .getBorderStrokeWidth(dimension);
+ int delta = (int) (borderThickness - 0.6);
+ // System.out.println(dimension + ":" + borderThickness + ":" + delta);
+
+ // float fDelta = borderThickness / 2.0f;
+ Shape contourBorder = new Ellipse2D.Float(delta, delta, dimension - 2
+ * delta - 1, dimension - 2 * delta - 1);
+
+ BufferedImage offBackground = SubstanceCoreUtilities.getBlankImage(
+ dimension + offsetX, dimension);
+ Graphics2D graphics = (Graphics2D) offBackground.getGraphics();
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(component,
+ componentState);
+ graphics.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, alpha));
+
+ graphics.translate(offsetX, 0);
+ fillPainter.paintContourBackground(graphics, component, dimension,
+ dimension, contourBorder, false, fillColorScheme, true);
+
+ Shape contourInner = new Ellipse2D.Float(delta + borderThickness, delta
+ + borderThickness, dimension - 2 * delta - 2 * borderThickness,
+ dimension - 2 * delta - 2 * borderThickness);
+
+ borderPainter.paintBorder(graphics, component, dimension, dimension,
+ contourBorder, contourInner, borderColorScheme);
+ graphics.setComposite(AlphaComposite.SrcOver);
+ // graphics.translate(-offsetX, 0);
+
+ float rc = dimension / 2.0f;
+ float radius = dimension / 4.5f;
+ // graphics.translate(offsetX, 0);
+
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ if (checkMarkVisibility > 0.0) {
+ // mark
+ Shape markOval = new Ellipse2D.Double(rc - radius, rc - radius,
+ 2 * radius, 2 * radius);
+
+ graphics.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, alpha * checkMarkVisibility));
+ drawRadioMark(graphics, SubstanceColorUtilities.getMarkColor(
+ markColorScheme, !componentState.isDisabled()), markOval);
+ } else {
+ // draw ghost mark holder
+ graphics.setPaint(new GradientPaint(rc + radius, rc - radius,
+ fillColorScheme.getDarkColor(), rc - radius, rc + radius,
+ fillColorScheme.getLightColor()));
+ Shape markOval = new Ellipse2D.Double(rc - radius, rc - radius,
+ 2 * radius, 2 * radius);
+ graphics.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, alpha * 0.3f));
+ graphics.fill(markOval);
+ }
+ graphics.translate(-offsetX, 0);
+
+ return offBackground;
+ }
+
+ /**
+ * Draws radio mark.
+ *
+ * @param graphics
+ * Graphics context.
+ * @param color
+ * Radio mark color.
+ * @param markOval
+ * Radio mark shape.
+ */
+ private static void drawRadioMark(Graphics2D graphics, Color color,
+ Shape markOval) {
+ graphics.setColor(color);
+ graphics.fill(markOval);
+ }
+
+ /**
+ * Retrieves check box of the specified size that matches the specified
+ * component state.
+ *
+ * @param button
+ * Button for the check mark.
+ * @param dimension
+ * Check box size.
+ * @param componentState
+ * Component state.
+ * @param fillColorScheme
+ * Color scheme for the inner fill.
+ * @param markColorScheme
+ * Color scheme for the check mark.
+ * @param borderColorScheme
+ * Color scheme for the border.
+ * @param checkMarkVisibility
+ * Check mark visibility in 0.0-1.0 range.
+ * @param isCheckMarkFadingOut
+ * if <code>true</code>, the value of
+ * <code>interpolationCyclePos10</code> is used as the alpha
+ * channel.
+ * @return Check box of the specified size that matches the specified
+ * component state.
+ */
+ public static BufferedImage getCheckBox(AbstractButton button,
+ SubstanceFillPainter fillPainter,
+ SubstanceBorderPainter borderPainter, int dimension,
+ ComponentState componentState,
+ SubstanceColorScheme fillColorScheme,
+ SubstanceColorScheme markColorScheme,
+ SubstanceColorScheme borderColorScheme, float checkMarkVisibility,
+ boolean isCheckMarkFadingOut) {
+
+ // int checkMarkSize = SubstanceSizeUtils
+ // .getCheckBoxMarkSize(SubstanceSizeUtils
+ // .getComponentFontSize(button));
+ int offset = SubstanceSizeUtils
+ .getAdjustedSize(
+ SubstanceSizeUtils.getComponentFontSize(button), 3, 9,
+ 1, false);
+ int delta = offset;
+ float cornerRadius = SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(button));
+ if (dimension <= 10) {
+ offset = 2;
+ cornerRadius = 2;
+ }
+
+ int contourDim = dimension - delta;
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(button)) / 2.0);
+ GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(
+ contourDim, contourDim, cornerRadius, null, borderDelta);
+
+ if (!componentState.isActive()) {
+ fillPainter = SimplisticSoftBorderReverseFillPainter.INSTANCE;
+ }
+
+ BufferedImage offBackground = SubstanceCoreUtilities.getBlankImage(
+ dimension, dimension);
+ Graphics2D graphics = (Graphics2D) offBackground.getGraphics();
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(button,
+ componentState);
+ graphics.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, alpha));
+
+ graphics.translate(delta - 1, delta - 1);
+ fillPainter.paintContourBackground(graphics, button, contourDim,
+ contourDim, contour, false, fillColorScheme, true);
+
+ // graphics.drawImage(background, 0, 0, null);
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(dimension);
+ GeneralPath contourInner = SubstanceOutlineUtilities.getBaseOutline(
+ contourDim, contourDim, cornerRadius - borderThickness, null,
+ borderThickness + borderDelta);
+ borderPainter.paintBorder(graphics, button, contourDim, contourDim,
+ contour, contourInner, borderColorScheme);
+ graphics.translate(-delta, 1 - delta);
+ if (checkMarkVisibility > 0.0) {
+ if (isCheckMarkFadingOut) {
+ graphics.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, alpha * checkMarkVisibility));
+ checkMarkVisibility = 1.0f;
+ }
+
+ BufferedImage checkMark = SubstanceImageCreator.getCheckMark(
+ dimension - 2 * offset / 3, !componentState.isDisabled(),
+ markColorScheme, checkMarkVisibility);
+ graphics.drawImage(checkMark, 1 + 2 * offset / 3,
+ (dimension < 14) ? 0 : -1, null);
+ }
+
+ return offBackground;
+ }
+
+ /**
+ * Retrieves composite background for the specified parameters. The
+ * composite background consists of three layers:
+ * <ol>
+ * <li>Layer that matches the <code>increased</code> state.
+ * <li>Layer that matches the <code>decreased</code> state.
+ * <li>Regular layer with rounded background.
+ * </ol>
+ * The layers are drawn in the following order:
+ * <ol>
+ * <li>The left half of the first layer
+ * <li>The right half of the second layer
+ * <li>The third layer
+ * </ol>
+ * Combined together, the layers create the image for scrollbar track with
+ * continuation of the arrow increase and decrease buttons.
+ *
+ * @param component
+ * Component.
+ * @param width
+ * Image width.
+ * @param height
+ * Image height.
+ * @param cornerRadius
+ * Corner radius.
+ * @param decrButton
+ * The <code>decrease</code> button.
+ * @param incrButton
+ * The <code>increase</code> button.
+ * @param flipSides
+ * If <code>true</code>, the drawn halves of the first and the
+ * second layers above will be swapped.
+ */
+ public static void paintCompositeRoundedBackground(JComponent component,
+ Graphics g, int width, int height, int cornerRadius,
+ AbstractButton decrButton, AbstractButton incrButton,
+ boolean flipSides) {
+
+ int delta = 3;
+
+ if (decrButton != null) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ if (!flipSides) {
+ graphics.clip(new Rectangle(-delta, 0, width / 2, height));
+ graphics.translate(-delta, 0);
+ } else {
+ graphics.clip(new Rectangle(width / 2, 0, width / 2 + 1, height));
+ }
+ PairwiseButtonBackgroundDelegate.updatePairwiseBackground(graphics,
+ decrButton, width + 2 * delta, height,
+ flipSides ? Side.RIGHT : Side.LEFT, true);
+ graphics.dispose();
+ }
+
+ if (incrButton != null) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ if (!flipSides) {
+ graphics.clip(new Rectangle(width / 2, 0, width / 2 + 1, height));
+ } else {
+ graphics.clip(new Rectangle(-delta, 0, width / 2, height));
+ graphics.translate(-delta, 0);
+ }
+ PairwiseButtonBackgroundDelegate.updatePairwiseBackground(graphics,
+ incrButton, width + 2 * delta, height,
+ flipSides ? Side.LEFT : Side.RIGHT, true);
+ graphics.dispose();
+ }
+ }
+
+ /**
+ * Overlays light-colored echo below the specified image.
+ *
+ * @param image
+ * The input image.
+ * @param echoAlpha
+ * Alpha channel for the echo image.
+ * @param offsetX
+ * X offset of the echo.
+ * @param offsetY
+ * Y offset of the echo.
+ * @return Image with overlayed echo.
+ */
+ private static BufferedImage overlayEcho(BufferedImage image,
+ float echoAlpha, int offsetX, int offsetY) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ // blur the original image
+ // ConvolveOp convolve = new ConvolveOp(new Kernel(3, 3, new float[] {
+ // .4f, .4f, .4f, .4f, .0f, .4f, .4f, .4f, .4f }),
+ // ConvolveOp.EDGE_NO_OP, null);
+ offsetX = offsetY = 0;
+ BufferedImage negated = getNegated(image);
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D graphics = (Graphics2D) result.getGraphics().create();
+ graphics.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, 0.2f * echoAlpha * echoAlpha
+ * echoAlpha));
+ graphics.drawImage(negated, offsetX - 1, offsetY - 1, null);
+ graphics.drawImage(negated, offsetX + 1, offsetY - 1, null);
+ graphics.drawImage(negated, offsetX - 1, offsetY + 1, null);
+ graphics.drawImage(negated, offsetX + 1, offsetY + 1, null);
+ graphics.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, 0.7f * echoAlpha * echoAlpha
+ * echoAlpha));
+ graphics.drawImage(negated, offsetX, offsetY - 1, null);
+ graphics.drawImage(negated, offsetX, offsetY + 1, null);
+ graphics.drawImage(negated, offsetX - 1, offsetY, null);
+ graphics.drawImage(negated, offsetX + 1, offsetY, null);
+
+ graphics.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, 1.0f));
+ graphics.drawImage(image, 0, 0, null);
+
+ graphics.dispose();
+ return result;
+ }
+
+ /**
+ * Returns <code>minimize</code> icon.
+ *
+ * @param scheme
+ * Color scheme for the icon.
+ * @return <code>Minimize</code> icon.
+ */
+ public static Icon getMinimizeIcon(SubstanceColorScheme scheme,
+ SubstanceColorScheme backgroundScheme) {
+ int iSize = SubstanceSizeUtils.getTitlePaneIconSize();
+ return getMinimizeIcon(iSize, scheme, backgroundScheme);
+ }
+
+ /**
+ * Returns <code>minimize</code> icon.
+ *
+ * @param iSize
+ * Icon dimension.
+ * @param scheme
+ * Color scheme for the icon.
+ * @return <code>Minimize</code> icon.
+ */
+ public static Icon getMinimizeIcon(int iSize, SubstanceColorScheme scheme,
+ SubstanceColorScheme backgroundScheme) {
+ BufferedImage image = SubstanceCoreUtilities
+ .getBlankImage(iSize, iSize);
+ Graphics2D graphics = (Graphics2D) image.getGraphics();
+ int start = (iSize / 4) - 2;
+ int end = (3 * iSize / 4);// - 1;
+ int size = end - start - 3;
+ Color color = SubstanceColorUtilities.getMarkColor(scheme, true);
+ graphics.setColor(color);
+ graphics.fillRect(start + 2, end - 2, size, 3);
+
+ int fgStrength = SubstanceColorUtilities.getColorBrightness(color
+ .getRGB());
+ int fgNegativeStrength = SubstanceColorUtilities
+ .getColorBrightness(SubstanceColorUtilities
+ .getNegativeColor(color.getRGB()));
+ int bgStrength = SubstanceColorUtilities
+ .getColorBrightness(backgroundScheme.getLightColor().getRGB());
+ boolean noEcho = (fgStrength > fgNegativeStrength)
+ && (fgStrength < bgStrength);
+
+ return new ImageIcon(SubstanceImageCreator.overlayEcho(image,
+ noEcho ? 0 : SubstanceColorUtilities.getColorStrength(color),
+ 1, 1));
+ }
+
+ /**
+ * Returns <code>restore</code> icon.
+ *
+ * @param scheme
+ * Color scheme for the icon.
+ * @return <code>Restore</code> icon.
+ */
+ public static Icon getRestoreIcon(SubstanceColorScheme scheme,
+ SubstanceColorScheme backgroundScheme) {
+ int iSize = SubstanceSizeUtils.getTitlePaneIconSize();
+ BufferedImage image = SubstanceCoreUtilities
+ .getBlankImage(iSize, iSize);
+ Graphics2D graphics = (Graphics2D) image.getGraphics();
+ int start = (iSize / 4) - 2;
+ int end = (3 * iSize / 4) - 1;
+ int size = end - start - 3;
+ Color color = SubstanceColorUtilities.getMarkColor(scheme, true);
+ graphics.setColor(color);
+ graphics.drawRect(start, end - size + 1, size, size);
+ graphics.drawLine(start, end - size + 2, start + size, end - size + 2);
+ graphics.fillRect(end - size, start + 1, size + 1, 2);
+ graphics.drawLine(end, start + 1, end, start + size + 1);
+ graphics.drawLine(start + size + 2, start + size + 1, end, start + size
+ + 1);
+
+ int fgStrength = SubstanceColorUtilities.getColorBrightness(color
+ .getRGB());
+ int fgNegativeStrength = SubstanceColorUtilities
+ .getColorBrightness(SubstanceColorUtilities
+ .getNegativeColor(color.getRGB()));
+ int bgStrength = SubstanceColorUtilities
+ .getColorBrightness(backgroundScheme.getLightColor().getRGB());
+ boolean noEcho = (fgStrength > fgNegativeStrength)
+ && (fgStrength < bgStrength);
+
+ return new ImageIcon(SubstanceImageCreator.overlayEcho(image,
+ noEcho ? 0 : SubstanceColorUtilities.getColorStrength(color),
+ 1, 1));
+ }
+
+ /**
+ * Returns <code>maximize</code> icon.
+ *
+ * @param scheme
+ * Color scheme for the icon.
+ * @return <code>Maximize</code> icon.
+ */
+ public static Icon getMaximizeIcon(SubstanceColorScheme scheme,
+ SubstanceColorScheme backgroundScheme) {
+ int iSize = SubstanceSizeUtils.getTitlePaneIconSize();
+ return getMaximizeIcon(iSize, scheme, backgroundScheme);
+ }
+
+ /**
+ * Returns <code>maximize</code> icon.
+ *
+ * @param iSize
+ * Icon dimension.
+ * @param scheme
+ * Color scheme for the icon.
+ * @return <code>Maximize</code> icon.
+ */
+ public static Icon getMaximizeIcon(int iSize, SubstanceColorScheme scheme,
+ SubstanceColorScheme backgroundScheme) {
+ BufferedImage image = SubstanceCoreUtilities
+ .getBlankImage(iSize, iSize);
+ Graphics2D graphics = (Graphics2D) image.getGraphics();
+ int start = (iSize / 4) - 1;
+ int end = iSize - start - 1;// (3 * iSize / 4);
+ Color color = SubstanceColorUtilities.getMarkColor(scheme, true);
+ graphics.setColor(color);
+ graphics.drawRect(start, start, end - start, end - start);
+ graphics.drawLine(start, start + 1, end, start + 1);
+
+ int fgStrength = SubstanceColorUtilities.getColorBrightness(color
+ .getRGB());
+ int fgNegativeStrength = SubstanceColorUtilities
+ .getColorBrightness(SubstanceColorUtilities
+ .getNegativeColor(color.getRGB()));
+ int bgStrength = SubstanceColorUtilities
+ .getColorBrightness(backgroundScheme.getLightColor().getRGB());
+ boolean noEcho = (fgStrength > fgNegativeStrength)
+ && (fgStrength < bgStrength);
+
+ return new ImageIcon(SubstanceImageCreator.overlayEcho(image,
+ noEcho ? 0 : SubstanceColorUtilities.getColorStrength(color),
+ 1, 1));
+ }
+
+ /**
+ * Returns <code>close</code> icon.
+ *
+ * @param scheme
+ * Color scheme for the icon.
+ * @return <code>Close</code> icon.
+ */
+ public static Icon getCloseIcon(SubstanceColorScheme scheme,
+ SubstanceColorScheme backgroundScheme) {
+ return SubstanceImageCreator.getCloseIcon(
+ SubstanceSizeUtils.getTitlePaneIconSize(), scheme,
+ backgroundScheme);
+ }
+
+ /**
+ * Returns <code>close</code> icon.
+ *
+ * @param iSize
+ * Icon dimension.
+ * @param colorScheme
+ * Color scheme for the icon.
+ * @return <code>Close</code> icon.
+ */
+ public static Icon getCloseIcon(int iSize,
+ SubstanceColorScheme colorScheme,
+ SubstanceColorScheme backgroundScheme) {
+ BufferedImage image = SubstanceCoreUtilities
+ .getBlankImage(iSize, iSize);
+ Graphics2D graphics = (Graphics2D) image.getGraphics();
+ if (iSize < 15) {
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ } else {
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+ }
+ int start = (iSize / 4);// - 1;
+ int end = (3 * iSize / 4);
+
+ // System.out.println(iSize + ":" + start + ":" + end);
+
+ Stroke stroke = new BasicStroke(
+ SubstanceSizeUtils.getFocusStrokeWidth(iSize),
+ BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
+
+ graphics.setStroke(stroke);
+ Color color = SubstanceColorUtilities.getMarkColor(colorScheme, true);
+ graphics.setColor(color);
+ graphics.drawLine(start, start, end, end);
+ graphics.drawLine(start, end, end, start);
+
+ int fgStrength = SubstanceColorUtilities.getColorBrightness(color
+ .getRGB());
+ int fgNegativeStrength = SubstanceColorUtilities
+ .getColorBrightness(SubstanceColorUtilities
+ .getNegativeColor(color.getRGB()));
+ int bgStrength = SubstanceColorUtilities
+ .getColorBrightness(backgroundScheme.getLightColor().getRGB());
+ boolean noEcho = (fgStrength > fgNegativeStrength)
+ && (fgStrength < bgStrength);
+ // System.out.println(SubstanceColorUtilities.getColorBrightness(color
+ // .getRGB())
+ // + ":"
+ // + SubstanceColorUtilities
+ // .getColorBrightness(SubstanceColorUtilities
+ // .getNegativeColor(color.getRGB()))
+ // + ":"
+ // + SubstanceColorUtilities.getColorBrightness(backgroundScheme
+ // .getLightColor().getRGB()));
+
+ return new ImageIcon(SubstanceImageCreator.overlayEcho(image,
+ noEcho ? 0 : SubstanceColorUtilities.getColorStrength(color),
+ 1, 1));
+ }
+
+ /**
+ * Paints rectangular gradient background.
+ *
+ * @param g
+ * Graphic context.
+ * @param startX
+ * Background starting X coord.
+ * @param startY
+ * Background starting Y coord.
+ * @param width
+ * Background width.
+ * @param height
+ * Background height.
+ * @param colorScheme
+ * Color scheme for the background.
+ * @param borderAlpha
+ * Border alpha.
+ * @param isVertical
+ * if <code>true</code>, the gradient will be vertical, if
+ * <code>false</code>, the gradient will be horizontal.
+ */
+ public static void paintRectangularBackground(Component c, Graphics g,
+ int startX, int startY, int width, int height,
+ SubstanceColorScheme colorScheme, float borderAlpha,
+ boolean isVertical) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.translate(startX, startY);
+
+ if (!isVertical) {
+ LinearGradientPaint paint = new LinearGradientPaint(0, 0, 0,
+ height, new float[] { 0.0f, 0.4f, 0.5f, 1.0f },
+ new Color[] { colorScheme.getUltraLightColor(),
+ colorScheme.getLightColor(),
+ colorScheme.getMidColor(),
+ colorScheme.getUltraLightColor() },
+ CycleMethod.REPEAT);
+ graphics.setPaint(paint);
+ graphics.fillRect(0, 0, width, height);
+ } else {
+ LinearGradientPaint paint = new LinearGradientPaint(0, 0, width, 0,
+ new float[] { 0.0f, 0.4f, 0.5f, 1.0f }, new Color[] {
+ colorScheme.getUltraLightColor(),
+ colorScheme.getLightColor(),
+ colorScheme.getMidColor(),
+ colorScheme.getUltraLightColor() },
+ CycleMethod.REPEAT);
+ graphics.setPaint(paint);
+ graphics.fillRect(0, 0, width, height);
+ }
+
+ if (borderAlpha > 0.0f) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(null,
+ borderAlpha, graphics));
+
+ paintSimpleBorderAliased(c, g2d, width, height, colorScheme);
+
+ g2d.dispose();
+ }
+ graphics.dispose();
+ }
+
+ /**
+ * Paints simple border.
+ *
+ * @param g2d
+ * Graphics context.
+ * @param width
+ * Border width.
+ * @param height
+ * Border height.
+ * @param borderColorScheme
+ * Border color scheme.
+ */
+ public static void paintSimpleBorder(Component c, Graphics2D g2d,
+ int width, int height, SubstanceColorScheme borderColorScheme) {
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(c);
+ // int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ // .getBorderStrokeWidth(componentFontSize) / 2.0);
+ // int borderDelta2 = (int) Math.floor(SubstanceSizeUtils
+ // .getBorderStrokeWidth(componentFontSize));
+ float borderThickness = (float) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize));
+
+ g2d.setColor(SubstanceColorUtilities
+ .getMidBorderColor(borderColorScheme));
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_NORMALIZE);
+ int joinKind = BasicStroke.JOIN_ROUND;
+ int capKind = BasicStroke.CAP_BUTT;
+ g2d.setStroke(new BasicStroke(borderThickness, capKind, joinKind));
+ g2d.draw(new Rectangle2D.Float((borderThickness - 1.0f) / 2.0f,
+ (borderThickness - 1.0f) / 2.0f, width - 1
+ - (borderThickness - 1.5f), height - 1
+ - (borderThickness - 1.5f)));
+ // g2d.drawRect(borderDelta, borderDelta, width - 1 - borderDelta2,
+ // height
+ // - 1 - borderDelta2);
+ }
+
+ public static void paintSimpleBorderAliased(Component c, Graphics2D g2d,
+ int width, int height, SubstanceColorScheme colorScheme) {
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(c);
+ // int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ // .getBorderStrokeWidth(componentFontSize) / 2.0);
+ // int borderDelta2 = (int) Math.floor(SubstanceSizeUtils
+ // .getBorderStrokeWidth(componentFontSize));
+ float borderThickness = (float) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize));
+
+ g2d.setColor(SubstanceColorUtilities.getMidBorderColor(colorScheme));
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+ g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_NORMALIZE);
+ int joinKind = BasicStroke.JOIN_MITER;
+ int capKind = BasicStroke.CAP_SQUARE;
+ g2d.setStroke(new BasicStroke(borderThickness, capKind, joinKind));
+ g2d.draw(new Rectangle2D.Float(borderThickness / 2.0f,
+ borderThickness / 2.0f, width - borderThickness, height
+ - borderThickness));
+ }
+
+ /**
+ * Paints rectangular gradient background with spots and optional replicated
+ * stripe image.
+ *
+ * @param g
+ * Graphics context.
+ * @param startX
+ * X start coordinate.
+ * @param startY
+ * Y start coordinate.
+ * @param width
+ * Background width.
+ * @param height
+ * Background height.
+ * @param colorScheme
+ * Color scheme for the background.
+ * @param stripeImage
+ * Stripe image to replicate.
+ * @param stripeOffset
+ * Offset of the first stripe replication.
+ * @param borderAlpha
+ * Border alpha.
+ * @param isVertical
+ * Indication of horizontal / vertical orientation.
+ */
+ public static void paintRectangularStripedBackground(Component c,
+ Graphics g, int startX, int startY, int width, int height,
+ SubstanceColorScheme colorScheme, BufferedImage stripeImage,
+ int stripeOffset, float borderAlpha, boolean isVertical) {
+ Graphics2D graphics = (Graphics2D) g.create(startX, startY, width,
+ height);
+ if (!isVertical) {
+ LinearGradientPaint paint = new LinearGradientPaint(0, 0, 0,
+ height, new float[] { 0.0f, 0.2f, 0.5f, 0.8f, 1.0f },
+ new Color[] { colorScheme.getDarkColor(),
+ colorScheme.getLightColor(),
+ colorScheme.getMidColor(),
+ colorScheme.getLightColor(),
+ colorScheme.getDarkColor() }, CycleMethod.REPEAT);
+ graphics.setPaint(paint);
+ graphics.fillRect(0, 0, width, height);
+
+ if (stripeImage != null) {
+ int stripeSize = stripeImage.getHeight();
+ int stripeCount = width / stripeSize;
+ stripeOffset = stripeOffset % (2 * stripeSize);
+ for (int stripe = -2; stripe <= stripeCount; stripe += 2) {
+ int stripePos = stripe * stripeSize + stripeOffset;
+
+ graphics.drawImage(stripeImage, stripePos, 0, null);
+ }
+ }
+ } else {
+ LinearGradientPaint paint = new LinearGradientPaint(0, 0, width, 0,
+ new float[] { 0.0f, 0.2f, 0.5f, 0.8f, 1.0f }, new Color[] {
+ colorScheme.getDarkColor(),
+ colorScheme.getLightColor(),
+ colorScheme.getMidColor(),
+ colorScheme.getLightColor(),
+ colorScheme.getDarkColor() }, CycleMethod.REPEAT);
+ graphics.setPaint(paint);
+ graphics.fillRect(0, 0, width, height);
+
+ if (stripeImage != null) {
+ int stripeSize = stripeImage.getWidth();
+ int stripeCount = height / stripeSize;
+ stripeOffset = stripeOffset % (2 * stripeSize);
+ for (int stripe = -2; stripe <= stripeCount; stripe += 2) {
+ int stripePos = stripe * stripeSize + stripeOffset;
+
+ graphics.drawImage(stripeImage, 0, stripePos, null);
+ }
+ }
+ }
+
+ if (borderAlpha > 0.0f) {
+ Graphics2D g2d = (Graphics2D) graphics.create();
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(null,
+ borderAlpha, graphics));
+
+ paintSimpleBorderAliased(c, g2d, width, height, colorScheme);
+ g2d.dispose();
+ }
+ graphics.dispose();
+ }
+
+ /**
+ * Returns diagonal stripe image.
+ *
+ * @param baseSize
+ * Stripe base in pixels.
+ * @param color
+ * Stripe color.
+ * @return Diagonal stripe image.
+ */
+ public static BufferedImage getStripe(int baseSize, Color color) {
+ int width = (int) (1.8 * baseSize);
+ int height = baseSize;
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D graphics = (Graphics2D) result.getGraphics();
+
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ Polygon polygon = new Polygon();
+ polygon.addPoint(0, 0);
+ polygon.addPoint(width - 1 - baseSize, 0);
+ polygon.addPoint(width - 1, height - 1);
+ polygon.addPoint(baseSize, height - 1);
+
+ graphics.setColor(color);
+ graphics.fillPolygon(polygon);
+ graphics.drawPolygon(polygon);
+
+ float[] BLUR = { 0.10f, 0.10f, 0.10f, 0.10f, 0.30f, 0.10f, 0.10f,
+ 0.10f, 0.10f };
+ ConvolveOp vBlurOp = new ConvolveOp(new Kernel(3, 3, BLUR));
+ BufferedImage blurred = vBlurOp.filter(result, null);
+
+ return blurred;
+ }
+
+ /**
+ * Returns drag bumps image.
+ *
+ * @param c
+ * Component.
+ * @param colorScheme
+ * Color scheme.
+ * @param width
+ * Drag bumps width.
+ * @param height
+ * Drag bumps height.
+ * @param maxNumberOfStripes
+ * The maximum number of bump stripes (rows or columns).
+ * @return Drag bumps image.
+ */
+ public static BufferedImage getDragImage(Component c,
+ SubstanceColorScheme colorScheme, int width, int height,
+ int maxNumberOfStripes) {
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D graphics = (Graphics2D) result.getGraphics();
+
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ boolean isDark = colorScheme.isDark();
+ Color back1 = isDark ? colorScheme.getLightColor()
+ : SubstanceColorUtilities.getInterpolatedColor(
+ colorScheme.getLightColor(),
+ colorScheme.getDarkColor(), 0.8);
+ Color back2 = isDark ? colorScheme.getExtraLightColor()
+ : SubstanceColorUtilities.getInterpolatedColor(
+ colorScheme.getMidColor(), colorScheme.getDarkColor(),
+ 0.4);
+ Color fore = isDark ? colorScheme.getDarkColor() : colorScheme
+ .getUltraLightColor();
+
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(c);
+ int bumpDotDiameter = SubstanceSizeUtils
+ .getDragBumpDiameter(componentFontSize);
+ int bumpCellSize = (int) (1.5 * bumpDotDiameter + 1);
+ int bumpRows = Math.max(1, height / bumpCellSize - 1);
+ int bumpColumns = Math.max(1, (width - 2) / bumpCellSize);
+ if (maxNumberOfStripes > 0) {
+ if (height > width)
+ bumpColumns = Math.min(bumpColumns, maxNumberOfStripes);
+ else
+ bumpRows = Math.min(bumpRows, maxNumberOfStripes);
+ }
+
+ int bumpRowOffset = (height - bumpCellSize * bumpRows) / 2;
+ int bumpColOffset = 1 + (width - bumpCellSize * bumpColumns) / 2;
+
+ for (int col = 0; col < bumpColumns; col++) {
+ int cx = bumpColOffset + col * bumpCellSize;
+ boolean isEvenCol = (col % 2 == 0);
+ int offsetY = isEvenCol ? 0 : bumpDotDiameter;
+ for (int row = 0; row < bumpRows; row++) {
+ int cy = offsetY + bumpRowOffset + row * bumpCellSize;
+ graphics.setColor(fore);
+ graphics.fillOval(cx + 1, cy + 1, bumpDotDiameter,
+ bumpDotDiameter);
+ // graphics.setColor(back1);
+ graphics.setPaint(new GradientPaint(cx, cy, back1, cx
+ + bumpDotDiameter - 1, cy + bumpDotDiameter - 1, back2));
+ graphics.fillOval(cx, cy, bumpDotDiameter, bumpDotDiameter);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Paints the bump dots on the split pane dividers.
+ *
+ * @param g
+ * Graphics context.
+ * @param divider
+ * Split pane divider.
+ * @param x
+ * X coordinate of the bump dots.
+ * @param y
+ * Y coordinate of the bump dots.
+ * @param width
+ * Width of the bump dots area.
+ * @param height
+ * Height of the bump dots area.
+ * @param isHorizontal
+ * Indicates whether the dots are horizontal.
+ * @param colorScheme
+ * First color scheme.
+ */
+ public static void paintSplitDividerBumpImage(Graphics g,
+ SubstanceSplitPaneDivider divider, int x, int y, int width,
+ int height, boolean isHorizontal, SubstanceColorScheme colorScheme) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.translate(x, y);
+
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ int componentFontSize = SubstanceSizeUtils
+ .getComponentFontSize(divider);
+ int bumpDotDiameter = SubstanceSizeUtils
+ .getBigDragBumpDiameter(componentFontSize);
+ int bumpCellSize = (int) (1.5 * bumpDotDiameter + 1);
+ int bumpRows = isHorizontal ? 1 : Math
+ .max(1, height / bumpCellSize - 1);
+ int bumpColumns = isHorizontal ? Math
+ .max(1, (width - 2) / bumpCellSize) : 1;
+
+ int bumpRowOffset = (height - bumpCellSize * bumpRows) / 2;
+ int bumpColOffset = 1 + (width - bumpCellSize * bumpColumns) / 2;
+
+ BufferedImage singleDot = SubstanceCoreUtilities.getBlankImage(
+ bumpDotDiameter, bumpDotDiameter);
+ Graphics2D dotGraphics = (Graphics2D) singleDot.getGraphics();
+ dotGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ Color markColor = SubstanceColorUtilities.getMarkColor(colorScheme,
+ divider.isEnabled());
+ dotGraphics.setColor(markColor);
+ dotGraphics.fillOval(0, 0, bumpDotDiameter, bumpDotDiameter);
+
+ dotGraphics.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, 0.4f));
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(divider);
+ borderPainter.paintBorder(dotGraphics, divider, width, height,
+ new Ellipse2D.Float(0, 0, bumpDotDiameter - 1,
+ bumpDotDiameter - 1), null, colorScheme);
+
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(divider,
+ 0.8f, g));
+ for (int col = 0; col < bumpColumns; col++) {
+ int cx = bumpColOffset + col * bumpCellSize;
+ for (int row = 0; row < bumpRows; row++) {
+ int cy = bumpRowOffset + row * bumpCellSize
+ + (bumpCellSize - bumpDotDiameter) / 2;
+ graphics.drawImage(singleDot, cx, cy, null);
+ }
+ }
+ graphics.dispose();
+ }
+
+ /**
+ * Returns resize grip image.
+ *
+ * @param c
+ * Component.
+ * @param colorScheme
+ * Color scheme.
+ * @param dimension
+ * Resize grip width.
+ * @param isCrowded
+ * Indicates whether the grips should be painted closely.
+ * @return Resize grip image.
+ */
+ public static BufferedImage getResizeGripImage(Component c,
+ SubstanceColorScheme colorScheme, int dimension, boolean isCrowded) {
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(dimension,
+ dimension);
+ Graphics2D graphics = (Graphics2D) result.getGraphics();
+
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ boolean isDark = colorScheme.isDark();
+ // SubstanceCoreUtilities
+ // .getActiveScheme(null) : SubstanceCoreUtilities
+ // .getDefaultScheme(null);
+ Color back1 = isDark ? colorScheme.getLightColor()
+ : SubstanceColorUtilities.getInterpolatedColor(
+ colorScheme.getLightColor(),
+ colorScheme.getDarkColor(), 0.8);
+ Color back2 = isDark ? colorScheme.getExtraLightColor()
+ : SubstanceColorUtilities.getInterpolatedColor(
+ colorScheme.getMidColor(), colorScheme.getDarkColor(),
+ 0.4);
+ Color fore = isDark ? colorScheme.getDarkColor() : colorScheme
+ .getUltraLightColor();
+
+ int bumpDotDiameter = SubstanceSizeUtils
+ .getDragBumpDiameter(SubstanceSizeUtils.getComponentFontSize(c));
+ int bumpCellSize = (int) (1.5 * bumpDotDiameter + 1);
+ if (isCrowded)
+ bumpCellSize--;
+ int bumpLines = dimension / bumpCellSize;
+
+ int bumpOffset = (dimension - bumpCellSize * bumpLines) / 2;
+
+ for (int col = 0; col < bumpLines; col++) {
+ int cx = bumpOffset + col * bumpCellSize;
+ for (int row = (bumpLines - col - 1); row < bumpLines; row++) {
+ int cy = bumpOffset + row * bumpCellSize;
+ graphics.setColor(fore);
+ graphics.fillOval(cx + 1, cy + 1, bumpDotDiameter,
+ bumpDotDiameter);
+ // graphics.setColor(back1);
+ graphics.setPaint(new GradientPaint(cx, cy, back1, cx
+ + bumpDotDiameter - 1, cy + bumpDotDiameter - 1, back2));
+ graphics.fillOval(cx, cy, bumpDotDiameter, bumpDotDiameter);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves tree icon.
+ *
+ * @param tree
+ * Tree.
+ * @param fillScheme
+ * Icon fill color scheme.
+ * @param borderScheme
+ * Icon border color scheme.
+ * @param isCollapsed
+ * Collapsed state.
+ * @return Tree icon.
+ */
+ public static BufferedImage getTreeIcon(JTree tree,
+ SubstanceColorScheme fillScheme, SubstanceColorScheme borderScheme,
+ boolean isCollapsed) {
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(tree);
+ int dim = SubstanceSizeUtils.getTreeIconSize(fontSize);
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(dim + 2,
+ dim);
+ Graphics2D graphics = (Graphics2D) result.getGraphics();
+
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+
+ Graphics2D g2 = (Graphics2D) graphics.create();
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+ SimplisticFillPainter gradPainter = new SimplisticSoftBorderReverseFillPainter();
+ FlatBorderPainter fbp = new FlatBorderPainter();
+
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(fontSize) / 2.0);
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(dim - 1,
+ dim - 1, SubstanceSizeUtils.getClassicButtonCornerRadius(dim),
+ null, borderDelta);
+
+ g2.translate(0, 1);
+
+ boolean isDark = fillScheme.isDark();
+
+ fillScheme = new ShiftColorScheme(fillScheme,
+ fillScheme.getExtraLightColor(), 0.7);
+
+ gradPainter.paintContourBackground(g2, tree, dim - 1, dim - 1, contour,
+ false, fillScheme, false);
+ borderScheme = new ShiftColorScheme(borderScheme,
+ isDark ? borderScheme.getUltraLightColor()
+ : borderScheme.getLightColor(), 0.5);
+ fbp.paintBorder(g2, tree, dim - 1, dim - 1, contour, null, borderScheme);
+
+ g2.translate(-1, -1);
+
+ Color signColor = isDark ? borderScheme.getUltraLightColor().brighter()
+ .brighter() : borderScheme.getUltraDarkColor();
+ if ((tree != null) && !tree.isEnabled())
+ signColor = borderScheme.getForegroundColor();
+ g2.setColor(signColor);
+ int mid = dim / 2;
+ int length = 5 * dim / 12;
+ g2.drawLine(mid - length / 2, dim / 2, mid + length / 2, dim / 2);
+ if (isCollapsed) {
+ g2.drawLine(dim / 2, mid - length / 2, dim / 2, mid + length / 2);
+ }
+ g2.dispose();
+
+ return result;
+ }
+
+ /**
+ * Retrieves a single crayon of the specified color and dimensions for the
+ * crayon panel in color chooser.
+ *
+ * @param mainColor
+ * Crayon main color.
+ * @param width
+ * Crayon width.
+ * @param height
+ * Crayon height.
+ * @return Crayon image.
+ */
+ public static BufferedImage getSingleCrayon(Color mainColor, int width,
+ int height) {
+ BufferedImage image = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+
+ int baseTop = (int) (0.2 * height);
+
+ Graphics2D graphics = (Graphics2D) image.getGraphics().create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ int r = mainColor.getRed();
+ int g = mainColor.getGreen();
+ int b = mainColor.getBlue();
+ // light coefficient
+ double lc = 0.8;
+ int lr = (int) (r + (255 - r) * lc);
+ int lg = (int) (g + (255 - g) * lc);
+ int lb = (int) (b + (255 - b) * lc);
+ // dark coefficient
+ double dc = 0.05;
+ int dr = (int) ((1.0 - dc) * r);
+ int dg = (int) ((1.0 - dc) * g);
+ int db = (int) ((1.0 - dc) * b);
+
+ Color lightColor = new Color(lr, lg, lb);
+ Color darkColor = new Color(dr, dg, db);
+
+ LinearGradientPaint paint = new LinearGradientPaint(0, 0, width, 0,
+ new float[] { 0.0f, 0.3f, 0.5f, 0.9f, 1.0f }, new Color[] {
+ lightColor, darkColor, darkColor, lightColor,
+ lightColor }, CycleMethod.REPEAT);
+ graphics.setPaint(paint);
+ graphics.fillRect(0, baseTop, width, height);
+
+ int dbwr = lr;
+ int dbwg = lg;
+ int dbwb = lb;
+ int lbwr = 128 + dr / 4;
+ int lbwg = 128 + dg / 4;
+ int lbwb = 128 + db / 4;
+
+ Color lightStripeColor = new Color(lbwr, lbwg, lbwb);
+ Color darkStripeColor = new Color(dbwr, dbwg, dbwb);
+
+ int stripeTop = (int) (0.35 * height);
+ int stripeHeight = (int) (0.04 * height);
+ LinearGradientPaint stripePaint = new LinearGradientPaint(0, 0, width,
+ 0, new float[] { 0.0f, 0.3f, 0.5f, 0.9f, 1.0f }, new Color[] {
+ lightStripeColor, darkStripeColor, darkStripeColor,
+ lightStripeColor, lightStripeColor },
+ CycleMethod.REPEAT);
+ graphics.setPaint(stripePaint);
+ graphics.fillRect(0, stripeTop, width, stripeHeight);
+
+ graphics.setColor(lightStripeColor);
+ graphics.drawRect(0, stripeTop, width - 1, stripeHeight);
+
+ // create cap path
+ GeneralPath capPath = new GeneralPath();
+ capPath.moveTo(0.5f * width - 3, 4);
+ capPath.quadTo(0.5f * width, 0, 0.5f * width + 3, 4);
+ capPath.lineTo(width - 3, baseTop);
+ capPath.lineTo(2, baseTop);
+ capPath.lineTo(0.5f * width - 3, 4);
+
+ graphics.setClip(capPath);
+
+ graphics.setPaint(new GradientPaint(0, baseTop / 2, lightColor,
+ (int) (0.6 * width), baseTop, mainColor));
+ graphics.fillRect(0, 0, width / 2, baseTop);
+ graphics.setPaint(new GradientPaint(width, baseTop / 2, lightColor,
+ (int) (0.4 * width), baseTop, mainColor));
+ graphics.fillRect(width / 2, 0, width / 2, baseTop);
+
+ graphics.setStroke(new BasicStroke((float) 1.3, BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_ROUND));
+
+ graphics.setClip(null);
+ graphics.setColor(new Color(64 + dr / 2, 64 + dg / 2, 64 + db / 2, 200));
+ graphics.drawRect(0, baseTop, width - 1, height - baseTop - 1);
+ graphics.draw(capPath);
+
+ graphics.dispose();
+
+ return image;
+ }
+
+ /**
+ * Crayon colors.
+ */
+ private final static int[] crayonColors = { 0x800000, // Cayenne
+ 0x808000, // Asparagus
+ 0x008000, // Clover
+ 0x008080, // Teal
+ 0x000080, // Midnight
+ 0x800080, // Plum
+ 0x7f7f7f, // Tin
+ 0x808080, // Nickel
+
+ 0x804000, // Mocha
+ 0x408000, // Fern
+ 0x008040, // Moss
+ 0x004080, // Ocean
+ 0x400080, // Eggplant
+ 0x800040, // Maroon
+ 0x666666, // Steel
+ 0x999999, // Aluminium
+
+ 0xff0000, // Maraschino
+ 0xffff00, // Lemon
+ 0x00ff00, // Spring
+ 0x00ffff, // Turquoise
+ 0x0000ff, // Blueberry
+ 0xff00ff, // Magenta
+ 0x4c4c4c, // Iron
+ 0xb3b3b3, // Magnesium
+
+ 0xff8000, // Tangerine
+ 0x80ff00, // Lime
+ 0x00ff80, // Sea Foam
+ 0x0080ff, // Aqua
+ 0x8000ff, // Grape
+ 0xff0080, // Strawberry
+ 0x333333, // Tungsten
+ 0xcccccc, // Silver
+
+ 0xff6666, // Salmon
+ 0xffff66, // Banana
+ 0x66ff66, // Flora
+ 0x66ffff, // Ice
+ 0x6666ff, // Orchid
+ 0xff66ff, // Bubblegum
+ 0x191919, // Lead
+ 0xe6e6e6, // Mercury
+
+ 0xffcc66, // Cantaloupe
+ 0xccff66, // Honeydew
+ 0x66ffcc, // Spindrift
+ 0x66ccff, // Sky
+ 0xcc66ff, // Lavender
+ 0xff6fcf, // Carnation
+ 0x000000, // Licorice
+ 0xffffff, // Snow
+ };
+
+ /**
+ * Retrieves crayon X offset.
+ *
+ * @param i
+ * Crayon index.
+ * @return Crayon X offset.
+ */
+ private static int crayonX(int i) {
+ return (i % 8) * 22 + 4 + ((i / 8) % 2) * 11;
+ }
+
+ /**
+ * Retrieves crayon Y offset.
+ *
+ * @param i
+ * Crayon index.
+ * @return Crayon Y offset.
+ */
+ private static int crayonY(int i) {
+ return (i / 8) * 20 + 23;
+ }
+
+ /**
+ * Retrieves crayons image for the crayon panel of color chooser.
+ *
+ * @return Crayons image.
+ */
+ public static Image getCrayonsImage() {
+ int iw = 195;
+ int ih = 208;
+ Image image = SubstanceCoreUtilities.getBlankImage(iw, ih);
+ Graphics2D graphics = (Graphics2D) image.getGraphics().create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ graphics.setColor(new Color(240, 240, 240));
+ graphics.fillRect(0, 0, iw, ih);
+
+ for (int i = 0; i < SubstanceImageCreator.crayonColors.length; i++) {
+ Color crayonColor = new Color(
+ 0xff000000 | SubstanceImageCreator.crayonColors[i]);
+ Image crayonImage = SubstanceImageCreator.getSingleCrayon(
+ crayonColor, 22, 120);
+ graphics.drawImage(crayonImage, SubstanceImageCreator.crayonX(i),
+ SubstanceImageCreator.crayonY(i), null);
+ }
+
+ graphics.setColor(new Color(190, 190, 190));
+ graphics.drawRoundRect(0, 1, iw - 1, ih - 2, 4, 4);
+
+ graphics.dispose();
+ return image;
+ }
+
+ /**
+ * Returns small icon representation of the specified integer value. The
+ * remainder of dividing the integer by 16 is translated to four circles
+ * arranged in 2*2 grid.
+ *
+ * @param value
+ * Integer value to represent.
+ * @param colorScheme
+ * Icon color scheme.
+ * @return Icon representation of the specified integer value.
+ */
+ public static Icon getHexaMarker(int value, SubstanceColorScheme colorScheme) {
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(9, 9);
+
+ value %= 16;
+ Color offColor = null;
+ Color onColor = null;
+ if (colorScheme == null) {
+ return new ImageIcon(result);
+ }
+ boolean isDark = colorScheme.isDark();
+ offColor = isDark ? colorScheme.getMidColor() : colorScheme
+ .getMidColor().darker();
+ onColor = isDark ? SubstanceColorUtilities.getInterpolatedColor(
+ colorScheme.getUltraLightColor(), Color.white, 0.2)
+ : colorScheme.getUltraDarkColor().darker();
+
+ boolean bit1 = ((value & 0x1) != 0);
+ boolean bit2 = ((value & 0x2) != 0);
+ boolean bit3 = ((value & 0x4) != 0);
+ boolean bit4 = ((value & 0x8) != 0);
+
+ Graphics2D graphics = (Graphics2D) result.getGraphics().create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ graphics.setColor(bit1 ? onColor : offColor);
+ graphics.fillOval(5, 5, 4, 4);
+ graphics.setColor(bit2 ? onColor : offColor);
+ graphics.fillOval(5, 0, 4, 4);
+ graphics.setColor(bit3 ? onColor : offColor);
+ graphics.fillOval(0, 5, 4, 4);
+ graphics.setColor(bit4 ? onColor : offColor);
+ graphics.fillOval(0, 0, 4, 4);
+
+ graphics.dispose();
+ return new ImageIcon(result);
+ }
+
+ /**
+ * Returns search icon.
+ *
+ * @param dimension
+ * Icon dimension.
+ * @param colorScheme
+ * Icon color scheme.
+ * @param leftToRight
+ * LTR indication of the resulting icon.
+ * @return Search icon.
+ */
+ public static Icon getSearchIcon(int dimension,
+ SubstanceColorScheme colorScheme, boolean leftToRight) {
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(dimension,
+ dimension);
+
+ Graphics2D graphics = (Graphics2D) result.getGraphics().create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ Color color = SubstanceColorUtilities.getMarkColor(colorScheme, true);
+ graphics.setColor(color);
+
+ graphics.setStroke(new BasicStroke(1.5f));
+ if (leftToRight) {
+ int xc = (int) (0.6 * dimension);
+ int yc = (int) (0.45 * dimension);
+ int r = (int) (0.3 * dimension);
+
+ graphics.drawOval(xc - r, yc - r, 2 * r, 2 * r);
+
+ graphics.setStroke(new BasicStroke(3.0f));
+ GeneralPath handle = new GeneralPath();
+ handle.moveTo((float) (xc - r / Math.sqrt(2.0)), (float) (yc + r
+ / Math.sqrt(2.0)));
+ handle.lineTo(1.8f, dimension - 2.2f);
+ graphics.draw(handle);
+ } else {
+ int xc = (int) (0.4 * dimension);
+ int yc = (int) (0.45 * dimension);
+ int r = (int) (0.3 * dimension);
+
+ graphics.drawOval(xc - r, yc - r, 2 * r, 2 * r);
+
+ graphics.setStroke(new BasicStroke(3.0f));
+ GeneralPath handle = new GeneralPath();
+ handle.moveTo((float) (xc + r / Math.sqrt(2.0)), (float) (yc + r
+ / Math.sqrt(2.0)));
+ handle.lineTo(dimension - 2.5f, dimension - 2.2f);
+ graphics.draw(handle);
+ }
+
+ graphics.dispose();
+ return new ImageIcon(result);
+ }
+
+ /**
+ * Returns an icon that matches the specified watermark.
+ *
+ * @param watermark
+ * Watermark instance.
+ * @return Icon that matches the specified watermark.
+ */
+ public static Icon getWatermarkIcon(SubstanceWatermark watermark) {
+ int iSize = SubstanceSizeUtils.getTitlePaneIconSize();
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(iSize,
+ iSize);
+ Graphics2D graphics = (Graphics2D) result.getGraphics().create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ watermark
+ .previewWatermark(graphics,
+ SubstanceColorSchemeUtilities.METALLIC_SKIN, 0, 0,
+ iSize, iSize);
+ graphics.dispose();
+ return new ImageIcon(result);
+ }
+
+ /**
+ * Returns a lock icon that matches the specified scheme.
+ *
+ * @param scheme
+ * Scheme instance.
+ * @return Lock icon that matches the specified scheme.
+ */
+ public static Icon getSmallLockIcon(SubstanceColorScheme scheme, Component c) {
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(c);
+ int extraPadding = SubstanceSizeUtils
+ .getExtraPadding(componentFontSize);
+ int width = 6 + 2 * extraPadding;
+ int height = 9 + 2 * extraPadding;
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+
+ Color fore = scheme.getForegroundColor();
+ Color fill = new Color(208, 208, 48);
+
+ Graphics2D graphics = (Graphics2D) result.getGraphics().create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ float borderStrokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize) / 1.2f;
+ float extraInsets = borderStrokeWidth / 2.0f;
+ graphics.setStroke(new BasicStroke(borderStrokeWidth,
+ BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND));
+
+ float lockPadTop = height / 3.0f;
+ float lockPadBottom = height - extraInsets;
+ float lockPadLeft = extraInsets;
+ float lockPadRight = width - extraInsets;
+
+ // lock pad fill
+ graphics.setColor(fill);
+ graphics.fill(new Rectangle2D.Float(lockPadLeft, lockPadTop,
+ lockPadRight - lockPadLeft, lockPadBottom - lockPadTop));
+ // lock pad outline
+ graphics.setColor(fore);
+ graphics.draw(new Rectangle2D.Float(lockPadLeft, lockPadTop,
+ lockPadRight - lockPadLeft, lockPadBottom - lockPadTop));
+
+ // lock handle
+ graphics.setColor(fore);
+ float lockHandleLeft = width / 4.0f;
+ float lockHandleRight = width - width / 4.0f;
+
+ GeneralPath handle = new GeneralPath();
+ handle.moveTo(lockHandleLeft, lockPadTop);
+ // up to top-left
+ handle.lineTo(lockHandleLeft, extraInsets);
+ // right to top-right
+ handle.lineTo(lockHandleRight, extraInsets);
+ // down
+ handle.lineTo(lockHandleRight, lockPadTop);
+
+ graphics.draw(handle);
+
+ // lock keyhole
+ graphics.setColor(fore);
+ float lockKeyholeTop = lockPadTop + 2 * borderStrokeWidth;
+ float lockKeyholeBottom = lockPadBottom - 2 * borderStrokeWidth + 1;
+ float lockKeyholeLeft = lockHandleLeft + 1;
+ float lockKeyholeRight = lockHandleRight;
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+ graphics.setStroke(new BasicStroke(1.0f));
+ graphics.fill(new Rectangle2D.Float(lockKeyholeLeft, lockKeyholeTop,
+ lockKeyholeRight - lockKeyholeLeft, lockKeyholeBottom
+ - lockKeyholeTop));
+
+ graphics.dispose();
+ return new ImageIcon(result);
+ }
+
+ /**
+ * Returns the negative of the specified image.
+ *
+ * @param bi
+ * Image.
+ * @return The negative of the specified image.
+ */
+ public static BufferedImage getNegated(BufferedImage bi) {
+ return new NegatedFilter().filter(bi, null);
+ }
+
+ /**
+ * Creates a new version of the specified icon that is rendered in the
+ * colors of the specified color scheme.
+ *
+ * @param comp
+ * Component.
+ * @param original
+ * The original icon.
+ * @param colorScheme
+ * Color scheme.
+ * @return Scheme-based version of the original icon.
+ */
+ public static BufferedImage getColorSchemeImage(Component comp,
+ Icon original, SubstanceColorScheme colorScheme,
+ float originalBrightnessFactor) {
+ int w = original.getIconWidth();
+ int h = original.getIconHeight();
+ BufferedImage origImage = SubstanceCoreUtilities.getBlankImage(w, h);
+ original.paintIcon(comp, origImage.getGraphics(), 0, 0);
+
+ return getColorSchemeImage(origImage, colorScheme,
+ originalBrightnessFactor);
+ }
+
+ /**
+ * Creates a new version of the specified image that is rendered in the
+ * colors of the specified color scheme.
+ *
+ * @param original
+ * The original image.
+ * @param colorScheme
+ * Color scheme.
+ * @return Scheme-based version of the original icon.
+ */
+ public static BufferedImage getColorSchemeImage(BufferedImage original,
+ SubstanceColorScheme colorScheme, float originalBrightnessFactor) {
+ return ColorSchemeFilter.getColorSchemeFilter(colorScheme,
+ originalBrightnessFactor).filter(original, null);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceInternalArrowButton.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceInternalArrowButton.java
new file mode 100644
index 0000000..427e5de
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceInternalArrowButton.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+/**
+ * Interface to mark arrow buttons used in various UI delegates.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SubstanceInternalArrowButton extends SubstanceInternalButton {
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceInternalButton.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceInternalButton.java
new file mode 100644
index 0000000..d8856fd
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceInternalButton.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+/**
+ * Interface to mark buttons used in various UI delegates.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SubstanceInternalButton {
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceInternalFrameTitlePane.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceInternalFrameTitlePane.java
new file mode 100755
index 0000000..b51a96a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceInternalFrameTitlePane.java
@@ -0,0 +1,940 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.internal.colorscheme.ShiftColorScheme;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.ui.SubstanceButtonUI;
+import org.pushingpixels.substance.internal.ui.SubstanceMenuBarUI;
+import org.pushingpixels.substance.internal.utils.icon.SubstanceIconFactory;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
+
+import javax.swing.*;
+import javax.swing.JInternalFrame.JDesktopIcon;
+import javax.swing.plaf.MenuBarUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicInternalFrameTitlePane;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+/**
+ * UI for internal frame title pane in <b>Substance </b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceInternalFrameTitlePane extends
+ BasicInternalFrameTitlePane {
+ /**
+ * Listens on the changes to the internal frame title.
+ */
+ protected PropertyChangeListener substancePropertyListener;
+
+ /**
+ * Listens to the changes to the
+ * {@link SubstanceLookAndFeel#WINDOW_MODIFIED
+ * } property on the internal
+ * frame and its root pane.
+ */
+ protected PropertyChangeListener substanceWinModifiedListener;
+
+ /**
+ * Client property to mark an internal frame as being iconified.
+ */
+ protected static final String ICONIFYING = "substance.internal.internalTitleFramePane.iconifying";
+
+ /**
+ * Client property to mark a title pane as uninstalled.
+ */
+ protected static final String UNINSTALLED = "substance.internal.internalTitleFramePane.uninstalled";
+
+ // protected boolean wasClosable;
+
+ /**
+ * Simple constructor.
+ *
+ * @param f
+ * Associated internal frame.
+ */
+ public SubstanceInternalFrameTitlePane(JInternalFrame f) {
+ super(f);
+ this.setToolTipText(f.getTitle());
+ SubstanceLookAndFeel.setDecorationType(this,DecorationAreaType.SECONDARY_TITLE_PANE);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicInternalFrameTitlePane#installDefaults()
+ */
+ @Override
+ protected void installDefaults() {
+ super.installDefaults();
+ if (SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ this.setForeground(SubstanceColorUtilities
+ .getForegroundColor(SubstanceCoreUtilities.getSkin(
+ this.frame).getActiveColorScheme(
+ getThisDecorationType())));
+ }
+ // this.wasClosable = this.frame.isClosable();
+ }
+
+ // /*
+ // * (non-Javadoc)
+ // *
+ // * @see
+ // * javax.swing.plaf.basic.BasicInternalFrameTitlePane#uninstallDefaults()
+ // */
+ // @Override
+ // protected void uninstallDefaults() {
+ // super.uninstallDefaults();
+ // if (this.wasClosable != this.frame.isClosable()) {
+ // this.frame.setClosable(this.wasClosable);
+ // }
+ // }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicInternalFrameTitlePane#installListeners()
+ */
+ @Override
+ protected void installListeners() {
+ super.installListeners();
+ this.substancePropertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (JInternalFrame.TITLE_PROPERTY.equals(evt.getPropertyName())) {
+ SubstanceInternalFrameTitlePane.this
+ .setToolTipText((String) evt.getNewValue());
+ }
+ if ("JInternalFrame.messageType".equals(evt.getPropertyName())) {
+ updateOptionPaneState();
+ frame.repaint();
+ }
+ if ("closed".equals(evt.getPropertyName())) {
+ windowMenu.setPopupMenuVisible(false);
+ }
+ }
+ };
+ this.frame.addPropertyChangeListener(this.substancePropertyListener);
+
+ // Property change listener for pulsating close button
+ // when window has been marked as changed.
+ this.substanceWinModifiedListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (SubstanceLookAndFeel.WINDOW_MODIFIED.equals(evt
+ .getPropertyName())) {
+ syncCloseButtonTooltip();
+ }
+ }
+ };
+ // Wire it on the root pane.
+ this.frame.getRootPane().addPropertyChangeListener(
+ this.substanceWinModifiedListener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicInternalFrameTitlePane#uninstallListeners()
+ */
+ @Override
+ public void uninstallListeners() {
+ this.frame.removePropertyChangeListener(this.substancePropertyListener);
+ this.substancePropertyListener = null;
+
+ this.frame.getRootPane().removePropertyChangeListener(
+ this.substanceWinModifiedListener);
+ this.substanceWinModifiedListener = null;
+
+ super.uninstallListeners();
+ }
+
+ /**
+ * Uninstalls <code>this</code> title pane.
+ */
+ public void uninstall() {
+ if ((this.menuBar != null) && (this.menuBar.getMenuCount() > 0)) {
+ MenuBarUI menuBarUI = this.menuBar.getUI();
+ if (menuBarUI instanceof SubstanceMenuBarUI) {
+ SubstanceMenuBarUI ui = (SubstanceMenuBarUI) menuBarUI;
+ if (ui.getMenuBar() == this.menuBar)
+ menuBarUI.uninstallUI(this.menuBar);
+ }
+ SubstanceCoreUtilities.uninstallMenu(this.menuBar.getMenu(0));
+ this.remove(menuBar);
+ // fix for issue 362 - remove the buttons so that we don't
+ // have duplicate buttons on internal frames in reparented
+ // desktop panes
+ this.remove(maxButton);
+ this.remove(closeButton);
+ this.remove(iconButton);
+ }
+ this.uninstallListeners();
+ this.putClientProperty(UNINSTALLED, Boolean.TRUE);
+ }
+ /**
+ * Updates state dependant upon the Window's active state.
+ *
+ * @param isActive
+ * if <code>true</code>, the window is in active state.
+ */
+ public void setActive(boolean isActive) {
+ if (getRootPane() != null) {
+ this.getRootPane().repaint();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicInternalFrameTitlePane#enableActions()
+ */
+ @Override
+ protected void enableActions() {
+ super.enableActions();
+
+ if (!this.frame.isIcon()) {
+ if (this.maxButton != null)
+ this.maxButton.setEnabled(this.maximizeAction.isEnabled()
+ || this.restoreAction.isEnabled());
+ if (this.iconButton != null)
+ this.iconButton.setEnabled(this.iconifyAction.isEnabled());
+ }
+ }
+
+ public DecorationAreaType getThisDecorationType() {
+ DecorationAreaType dat = SubstanceLookAndFeel.getDecorationType(this);
+ if (dat == DecorationAreaType.PRIMARY_TITLE_PANE) {
+ return SubstanceCoreUtilities.isPaintRootPaneActivated(frame.getRootPane())
+ ? DecorationAreaType.PRIMARY_TITLE_PANE
+ : DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE;
+ } else if (dat == DecorationAreaType.SECONDARY_TITLE_PANE) {
+ return SubstanceCoreUtilities.isPaintRootPaneActivated(frame.getRootPane())
+ ? DecorationAreaType.SECONDARY_TITLE_PANE
+ : DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE;
+
+ } else {
+ return dat;
+ }
+
+ }
+
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
+ */
+ @Override
+ public void paintComponent(Graphics g) {
+ // if (this.isPalette) {
+ // this.paintPalette(g);
+ // return;
+ // }
+
+ DecorationAreaType decorationType = getThisDecorationType();
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ // Desktop icon is translucent.
+ final float coef = (this.getParent() instanceof JDesktopIcon) ? 0.6f
+ : 1.0f;
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(this.frame,
+ coef, g));
+
+ boolean leftToRight = this.frame.getComponentOrientation()
+ .isLeftToRight();
+
+ int width = this.getWidth();
+ int height = this.getHeight() + 2;
+
+ SubstanceColorScheme scheme = SubstanceCoreUtilities
+ .getSkin(this.frame).getEnabledColorScheme(decorationType);
+ JInternalFrame hostFrame = (JInternalFrame) SwingUtilities
+ .getAncestorOfClass(JInternalFrame.class, this);
+ JComponent hostForColorization = hostFrame;
+ if (hostFrame == null) {
+ // try desktop icon
+ JDesktopIcon desktopIcon = (JDesktopIcon) SwingUtilities
+ .getAncestorOfClass(JDesktopIcon.class, this);
+ if (desktopIcon != null)
+ hostFrame = desktopIcon.getInternalFrame();
+ hostForColorization = desktopIcon;
+ }
+ // if ((hostFrame != null) && SubstanceCoreUtilities.hasColorization(
+ // this)) {
+ Color backgr = hostFrame.getBackground();
+ if (!(backgr instanceof UIResource)) {
+ double colorization = SubstanceCoreUtilities
+ .getColorizationFactor(hostForColorization);
+ scheme = ShiftColorScheme.getShiftedScheme(scheme, backgr,
+ colorization, null, 0.0);
+ }
+ // }
+ String theTitle = this.frame.getTitle();
+
+ // offset of border
+ int xOffset;
+ int leftEnd;
+ int rightEnd;
+
+ if (leftToRight) {
+ xOffset = 5;
+ Icon icon = this.frame.getFrameIcon();
+ int iconWidth = 0;
+ int menuWidth = 0;
+ if (icon != null) {
+ iconWidth = icon.getIconWidth() + 5;
+ }
+
+ menuWidth = (this.menuBar == null) ? 0
+ : (this.menuBar.getWidth() + 5);
+ leftEnd = Math.max(iconWidth, menuWidth);
+ xOffset += leftEnd;
+
+ rightEnd = width - 5;
+
+ // find the leftmost button for the right end
+ AbstractButton leftmostButton = null;
+ if (this.frame.isIconifiable()) {
+ leftmostButton = this.iconButton;
+ } else {
+ if (this.frame.isMaximizable()) {
+ leftmostButton = this.maxButton;
+ } else {
+ if (this.frame.isClosable()) {
+ leftmostButton = this.closeButton;
+ }
+ }
+ }
+
+ if (leftmostButton != null) {
+ Rectangle rect = leftmostButton.getBounds();
+ rightEnd = rect.getBounds().x - 5;
+ }
+ if (theTitle != null) {
+ FontMetrics fm = this.frame.getFontMetrics(graphics.getFont());
+ int titleWidth = rightEnd - leftEnd;
+ String clippedTitle = SubstanceCoreUtilities.clipString(fm,
+ titleWidth, theTitle);
+ // show tooltip with full title only if necessary
+ if (theTitle.equals(clippedTitle))
+ this.setToolTipText(null);
+ else
+ this.setToolTipText(theTitle);
+ theTitle = clippedTitle;
+ }
+ } else {
+ int iconWidth = 0;
+ int menuWidth = 0;
+
+ Icon icon = this.frame.getFrameIcon();
+ if (icon != null) {
+ iconWidth = (icon.getIconWidth() + 5);
+ }
+
+ menuWidth = (this.menuBar == null) ? 0 : this.menuBar.getWidth() + 5;
+ rightEnd = width - Math.max(iconWidth, menuWidth);
+ xOffset = rightEnd - 5;
+
+ // find the rightmost button for the left end
+ AbstractButton rightmostButton = null;
+ if (this.frame.isIconifiable()) {
+ rightmostButton = this.iconButton;
+ } else {
+ if (this.frame.isMaximizable()) {
+ rightmostButton = this.maxButton;
+ } else {
+ if (this.frame.isClosable()) {
+ rightmostButton = this.closeButton;
+ }
+ }
+ }
+
+ leftEnd = 5;
+ if (rightmostButton != null) {
+ Rectangle rect = rightmostButton.getBounds();
+ leftEnd = rect.getBounds().x + 5;
+ }
+ if (theTitle != null) {
+ FontMetrics fm = this.frame.getFontMetrics(graphics.getFont());
+ int titleWidth = rightEnd - leftEnd;
+ String clippedTitle = SubstanceCoreUtilities.clipString(fm,
+ titleWidth, theTitle);
+ // show tooltip with full title only if necessary
+ if (theTitle.equals(clippedTitle)) {
+ this.setToolTipText(null);
+ } else {
+ this.setToolTipText(theTitle);
+ }
+ theTitle = clippedTitle;
+ xOffset = rightEnd - fm.stringWidth(theTitle);
+ }
+ }
+
+ BackgroundPaintingUtils.update(graphics,
+ SubstanceInternalFrameTitlePane.this, false, decorationType);
+ // DecorationPainterUtils.paintDecorationBackground(graphics,
+ // SubstanceInternalFrameTitlePane.this, false);
+
+ // draw the title (if needed)
+ if (theTitle != null) {
+ JRootPane rootPane = this.getRootPane();
+ FontMetrics fm = rootPane.getFontMetrics(graphics.getFont());
+ int yOffset = ((height - fm.getHeight()) / 2) + fm.getAscent();
+
+ SubstanceTextUtilities.paintTextWithDropShadow(this, graphics,
+ SubstanceColorUtilities.getForegroundColor(scheme),
+ theTitle, width, height, xOffset, yOffset);
+ }
+
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicInternalFrameTitlePane#setButtonIcons()
+ */
+ @Override
+ protected void setButtonIcons() {
+ super.setButtonIcons();
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return;
+
+ Icon restoreIcon = new TransitionAwareIcon(this.maxButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ return SubstanceIconFactory
+ .getTitlePaneIcon(
+ SubstanceIconFactory.IconKind.RESTORE,
+ scheme,
+ SubstanceCoreUtilities
+ .getSkin(
+ SubstanceInternalFrameTitlePane.this)
+ .getBackgroundColorScheme(getThisDecorationType()));
+ }
+ }, "substance.internalFrame.restoreIcon");
+ Icon maximizeIcon = new TransitionAwareIcon(this.maxButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ return SubstanceIconFactory
+ .getTitlePaneIcon(
+ SubstanceIconFactory.IconKind.MAXIMIZE,
+ scheme,
+ SubstanceCoreUtilities
+ .getSkin(
+ SubstanceInternalFrameTitlePane.this)
+ .getBackgroundColorScheme(getThisDecorationType()));
+ }
+ }, "substance.internalFrame.maxIcon");
+ Icon minimizeIcon = new TransitionAwareIcon(this.iconButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ return SubstanceIconFactory
+ .getTitlePaneIcon(
+ SubstanceIconFactory.IconKind.MINIMIZE,
+ scheme,
+ SubstanceCoreUtilities
+ .getSkin(
+ SubstanceInternalFrameTitlePane.this)
+ .getBackgroundColorScheme(getThisDecorationType()));
+ }
+ }, "substance.internalFrame.minIcon");
+ Icon closeIcon = new TransitionAwareIcon(this.closeButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ return SubstanceIconFactory
+ .getTitlePaneIcon(
+ SubstanceIconFactory.IconKind.CLOSE,
+ scheme,
+ SubstanceCoreUtilities
+ .getSkin(
+ SubstanceInternalFrameTitlePane.this)
+ .getBackgroundColorScheme(getThisDecorationType()));
+ }
+ }, "substance.internalFrame.closeIcon");
+ if (this.frame.isIcon()) {
+ this.iconButton.setIcon(restoreIcon);
+ this.iconButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(frame).getString("SystemMenu.restore"));
+ this.maxButton.setIcon(maximizeIcon);
+ this.maxButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(frame).getString("SystemMenu.maximize"));
+ } else {
+ this.iconButton.setIcon(minimizeIcon);
+ this.iconButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(frame).getString("SystemMenu.iconify"));
+ if (this.frame.isMaximum()) {
+ this.maxButton.setIcon(restoreIcon);
+ this.maxButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(frame).getString(
+ "SystemMenu.restore"));
+ } else {
+ this.maxButton.setIcon(maximizeIcon);
+ this.maxButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(frame).getString(
+ "SystemMenu.maximize"));
+ }
+ }
+ if (closeIcon != null) {
+ this.closeButton.setIcon(closeIcon);
+ syncCloseButtonTooltip();
+ }
+ }
+
+ /**
+ * Click correction listener that resets models of minimize and restore
+ * buttons on click (so that the rollover behaviour will be preserved
+ * correctly).
+ *
+ * @author Kirill Grouchnikov.
+ */
+ public static class ClickListener implements ActionListener {
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent
+ * )
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ AbstractButton src = (AbstractButton) e.getSource();
+ ButtonModel model = src.getModel();
+ model.setArmed(false);
+ model.setPressed(false);
+ model.setRollover(false);
+ model.setSelected(false);
+ }
+ }
+
+ /**
+ * Returns the <code>JMenuBar</code> displaying the appropriate system menu
+ * items.
+ *
+ * @return <code>JMenuBar</code> displaying the appropriate system menu
+ * items.
+ */
+ @Override
+ protected JMenuBar createSystemMenuBar() {
+ this.menuBar = new SubstanceMenuBar();
+ this.menuBar.setFocusable(false);
+ this.menuBar.setBorderPainted(true);
+ this.menuBar.add(this.createSystemMenu());
+ this.menuBar.setOpaque(false);
+ // support for RTL
+ this.menuBar.applyComponentOrientation(this.getComponentOrientation());
+
+ return this.menuBar;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicInternalFrameTitlePane#createActions()
+ */
+ @Override
+ protected void createActions() {
+ super.createActions();
+ this.iconifyAction = new SubstanceIconifyAction();
+ }
+
+ /**
+ * Returns the <code>JMenu</code> displaying the appropriate menu items for
+ * manipulating the Frame.
+ *
+ * @return <code>JMenu</code> displaying the appropriate menu items for
+ * manipulating the Frame.
+ */
+ @Override
+ protected JMenu createSystemMenu() {
+ JMenu menu = super.createSystemMenu();
+
+ menu.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() > 1) {
+ closeAction.actionPerformed(new ActionEvent(e.getSource(),
+ ActionEvent.ACTION_PERFORMED, null,
+ EventQueue.getMostRecentEventTime(), e.getModifiers()));
+ }
+ }
+ });
+ return menu;
+ }
+
+ /**
+ * Adds the necessary <code>JMenuItem</code>s to the specified menu.
+ *
+ * @param menu
+ * Menu.
+ */
+ @Override
+ protected void addSystemMenuItems(JMenu menu) {
+ menu.add(this.restoreAction);
+
+ menu.add(this.iconifyAction);
+
+ if (Toolkit.getDefaultToolkit().isFrameStateSupported(
+ Frame.MAXIMIZED_BOTH)) {
+ menu.add(this.maximizeAction);
+ }
+
+ menu.addSeparator();
+
+ menu.add(this.closeAction);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicInternalFrameTitlePane#createButtons()
+ */
+ @Override
+ protected void createButtons() {
+ iconButton = new SubstanceTitleButton(
+ "InternalFrameTitlePane.iconifyButtonAccessibleName");
+ iconButton.addActionListener(iconifyAction);
+
+ maxButton = new SubstanceTitleButton(
+ "InternalFrameTitlePane.maximizeButtonAccessibleName");
+ maxButton.addActionListener(maximizeAction);
+
+ closeButton = new SubstanceTitleButton(
+ "InternalFrameTitlePane.closeButtonAccessibleName");
+ closeButton.addActionListener(closeAction);
+
+ setButtonIcons();
+
+ for (ActionListener listener : this.iconButton.getActionListeners())
+ if (listener instanceof ClickListener)
+ return;
+ this.iconButton.addActionListener(new ClickListener());
+ for (ActionListener listener : this.maxButton.getActionListeners())
+ if (listener instanceof ClickListener)
+ return;
+ this.maxButton.addActionListener(new ClickListener());
+ this.iconButton.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY,
+ Boolean.TRUE);
+
+ this.maxButton.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY,
+ Boolean.TRUE);
+
+ this.closeButton.putClientProperty(
+ SubstanceButtonUI.IS_TITLE_CLOSE_BUTTON, Boolean.TRUE);
+ this.closeButton.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY,
+ Boolean.TRUE);
+
+ this.enableActions();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicInternalFrameTitlePane#createLayout()
+ */
+ @Override
+ protected LayoutManager createLayout() {
+ return new SubstanceTitlePaneLayout();
+ }
+
+ /**
+ * Synchronizes the tooltip of the close button.
+ */
+ protected void syncCloseButtonTooltip() {
+ if (SubstanceCoreUtilities.isInternalFrameModified(this.frame)) {
+ this.closeButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(frame).getString("SystemMenu.close")
+ + " ["
+ + SubstanceCoreUtilities.getResourceBundle(frame)
+ .getString("Tooltip.contentsNotSaved") + "]");
+ } else {
+ this.closeButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(frame).getString("SystemMenu.close"));
+ }
+ this.closeButton.repaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#removeNotify()
+ */
+ @Override
+ public void removeNotify() {
+ super.removeNotify();
+
+ // fix for defect 211 - internal frames that are iconified
+ // programmatically should not uninstall the title panes.
+ boolean isAlive = ((this.frame.isIcon() && !this.frame.isClosed()) || Boolean.TRUE
+ .equals(frame.getClientProperty(ICONIFYING)));
+ if (!isAlive) {
+ this.uninstall();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#addNotify()
+ */
+ @Override
+ public void addNotify() {
+ super.addNotify();
+ if (Boolean.TRUE.equals(this.getClientProperty(UNINSTALLED))) {
+ this.installTitlePane();
+ // this.installListeners();
+ this.putClientProperty(UNINSTALLED, null);
+ }
+ }
+
+ /**
+ * Class responsible for drawing the system menu. Looks up the image to draw
+ * from the Frame associated with the <code>JRootPane</code>.
+ */
+ public class SubstanceMenuBar extends JMenuBar {
+ @Override
+ public void paint(Graphics g) {
+ if (frame.getFrameIcon() != null) {
+ frame.getFrameIcon().paintIcon(this, g, 0, 0);
+ } else {
+ Icon icon = UIManager.getIcon("InternalFrame.icon");
+ if (icon != null) {
+ icon.paintIcon(this, g, 0, 0);
+ }
+ }
+ }
+
+ @Override
+ public Dimension getMinimumSize() {
+ return this.getPreferredSize();
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size = super.getPreferredSize();
+
+ int iSize = SubstanceSizeUtils.getTitlePaneIconSize();
+ return new Dimension(Math.max(iSize, size.width), Math.max(
+ size.height, iSize));
+ }
+ }
+
+ /**
+ * Layout manager for this title pane.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class SubstanceTitlePaneLayout extends TitlePaneLayout {
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ return minimumLayoutSize(c);
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ // Compute width.
+ int width = 30;
+ if (frame.isClosable()) {
+ width += 21;
+ }
+ if (frame.isMaximizable()) {
+ width += 16 + (frame.isClosable() ? 10 : 4);
+ }
+ if (frame.isIconifiable()) {
+ width += 16 + (frame.isMaximizable() ? 2
+ : (frame.isClosable() ? 10 : 4));
+ }
+ FontMetrics fm = frame.getFontMetrics(getFont());
+ String frameTitle = frame.getTitle();
+ int title_w = frameTitle != null ? fm.stringWidth(frameTitle) : 0;
+ int title_length = frameTitle != null ? frameTitle.length() : 0;
+
+ if (title_length > 2) {
+ int subtitle_w = fm.stringWidth(frame.getTitle()
+ .substring(0, 2)
+ + "...");
+ width += (title_w < subtitle_w) ? title_w : subtitle_w;
+ } else {
+ width += title_w;
+ }
+
+ // Compute height.
+ int height;
+ // if (isPalette) {
+ // height = paletteTitleHeight;
+ // } else {
+ int fontHeight = fm.getHeight();
+ fontHeight += 7;
+ Icon icon = frame.getFrameIcon();
+ int iconHeight = 0;
+ if (icon != null) {
+ // SystemMenuBar forces the icon to be 16x16 or less.
+ iconHeight = Math.min(icon.getIconHeight(), 16);
+ }
+ iconHeight += 5;
+ height = Math.max(fontHeight, iconHeight);
+ // }
+
+ return new Dimension(width, height);
+ }
+
+ @Override
+ public void layoutContainer(Container c) {
+ boolean leftToRight = frame.getComponentOrientation()
+ .isLeftToRight();
+
+ int w = getWidth();
+ int x = leftToRight ? w : 0;
+ int y;
+ int spacing;
+
+ // assumes all buttons have the same dimensions
+ // these dimensions include the borders
+ int buttonHeight = closeButton.getIcon().getIconHeight();
+ int buttonWidth = closeButton.getIcon().getIconWidth();
+
+ y = (getHeight() - buttonHeight) / 2;
+
+ Icon icon = frame.getFrameIcon();
+ int iconHeight = 0;
+ int iconWidth = 0;
+ if (icon != null) {
+ iconHeight = icon.getIconHeight();
+ iconWidth = icon.getIconWidth();
+ }
+ int xMenuBar = (leftToRight) ? 5 : w - 16 - 5;
+ menuBar.setBounds(xMenuBar, (getHeight() - iconHeight) / 2, iconWidth, iconHeight);
+
+ if (frame.isClosable()) {
+ // if (isPalette) {
+ // spacing = 3;
+ // x += leftToRight ? -spacing - (buttonWidth + 2) : spacing;
+ // closeButton.setBounds(x, y, buttonWidth + 2,
+ // getHeight() - 4);
+ // if (!leftToRight)
+ // x += (buttonWidth + 2);
+ // } else {
+ spacing = 4;
+ x += leftToRight ? -spacing - buttonWidth : spacing;
+ closeButton.setBounds(x, y, buttonWidth, buttonHeight);
+ if (!leftToRight)
+ x += buttonWidth;
+ // }
+ }
+
+ if (frame.isMaximizable()) {// && !isPalette) {
+ spacing = frame.isClosable() ? 10 : 4;
+ x += leftToRight ? -spacing - buttonWidth : spacing;
+ maxButton.setBounds(x, y, buttonWidth, buttonHeight);
+ if (!leftToRight)
+ x += buttonWidth;
+ }
+
+ if (frame.isIconifiable()) {// && !isPalette) {
+ spacing = frame.isMaximizable() ? 2 : (frame.isClosable() ? 10
+ : 4);
+ x += leftToRight ? -spacing - buttonWidth : spacing;
+ iconButton.setBounds(x, y, buttonWidth, buttonHeight);
+ if (!leftToRight)
+ x += buttonWidth;
+ }
+ //
+ // buttonsWidth = leftToRight ? w - x : x;
+ }
+ }
+
+ /**
+ * Custom iconifying action.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public class SubstanceIconifyAction extends IconifyAction {
+ /**
+ * Creates an iconifying action.
+ */
+ public SubstanceIconifyAction() {
+ super();
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ frame.putClientProperty(ICONIFYING, Boolean.TRUE);
+ super.actionPerformed(e);
+ frame.putClientProperty(ICONIFYING, null);
+ }
+ }
+
+ /**
+ * Updates the state of internal frames used in {@link JOptionPane}s.
+ */
+ private void updateOptionPaneState() {
+ Object obj = frame.getClientProperty("JInternalFrame.messageType");
+
+ if (obj == null) {
+ // Don't change the closable state unless in an JOptionPane.
+ return;
+ }
+ if (frame.isClosable()) {
+ frame.setClosable(false);
+ }
+ }
+
+ public AbstractButton getCloseButton() {
+ return this.closeButton;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceOutlineUtilities.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceOutlineUtilities.java
new file mode 100644
index 0000000..714a682
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceOutlineUtilities.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.Component;
+import java.awt.Insets;
+import java.awt.geom.Arc2D;
+import java.awt.geom.GeneralPath;
+import java.util.Set;
+
+import org.pushingpixels.substance.api.SubstanceConstants.Side;
+
+/**
+ * Provides common functionality that can be used by button shapers. This class
+ * is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceOutlineUtilities {
+ /**
+ * Returns basic outline for the specified component. The basic outline is a
+ * rectangle with rounded corners. Some corners may not be rounded based on
+ * the contents of <code>straightSide</code> parameter.
+ *
+ * @param comp
+ * Component.
+ * @param radius
+ * Corner radius.
+ * @param straightSides
+ * Contains all sides which are straight.
+ * @return The basic outline for the specified parameters.
+ */
+ public static GeneralPath getBaseOutline(Component comp, float radius,
+ Set<Side> straightSides) {
+ int width = comp.getWidth();
+ int height = comp.getHeight();
+
+ return getBaseOutline(width, height, radius, straightSides);
+ }
+
+ /**
+ * Returns basic outline for the specified parameters. The basic outline is
+ * a rectangle with rounded corners. Some corners may not be rounded based
+ * on the contents of <code>straightSide</code> parameter.
+ *
+ * @param width
+ * Width of some UI component.
+ * @param height
+ * Height of some UI component.
+ * @param radius
+ * Corner radius.
+ * @param straightSides
+ * Contains all sides which are straight.
+ * @return The basic outline for the specified parameters.
+ */
+ public static GeneralPath getBaseOutline(int width, int height,
+ float radius, Set<Side> straightSides) {
+ return getBaseOutline(width, height, radius, straightSides, null);
+ }
+
+ /**
+ * Returns basic outline for the specified parameters. The basic outline is
+ * a rectangle with rounded corners. Some corners may not be rounded based
+ * on the contents of <code>straightSides</code> parameter.
+ *
+ * @param width
+ * Width of some UI component.
+ * @param height
+ * Height of some UI component.
+ * @param radius
+ * Corner radius.
+ * @param straightSides
+ * Contains all sides which are straight.
+ * @param insets
+ * Shape insets.
+ * @return The basic outline for the specified parameters.
+ */
+ public static GeneralPath getBaseOutline(int width, int height,
+ float radius, Set<Side> straightSides, int insets) {
+ return getBaseOutline(width, height, radius, straightSides, new Insets(
+ insets, insets, insets, insets));
+ }
+
+ /**
+ * Returns basic outline for the specified parameters. The basic outline is
+ * a rectangle with rounded corners. Some corners may not be rounded based
+ * on the contents of <code>straightSides</code> parameter.
+ *
+ * @param width
+ * Width of some UI component.
+ * @param height
+ * Height of some UI component.
+ * @param radius
+ * Corner radius.
+ * @param straightSides
+ * Contains all sides which are straight.
+ * @param insets
+ * Shape insets.
+ * @return The basic outline for the specified parameters.
+ */
+ public static GeneralPath getBaseOutline(int width, int height,
+ float radius, Set<Side> straightSides, Insets insets) {
+ boolean isTopLeftCorner = (straightSides != null)
+ && (straightSides.contains(Side.LEFT) || straightSides
+ .contains(Side.TOP));
+ boolean isTopRightCorner = (straightSides != null)
+ && (straightSides.contains(Side.RIGHT) || straightSides
+ .contains(Side.TOP));
+ boolean isBottomRightCorner = (straightSides != null)
+ && (straightSides.contains(Side.RIGHT) || straightSides
+ .contains(Side.BOTTOM));
+ boolean isBottomLeftCorner = (straightSides != null)
+ && (straightSides.contains(Side.LEFT) || straightSides
+ .contains(Side.BOTTOM));
+
+ int xs = (insets == null) ? 0 : insets.left;
+ int ys = (insets == null) ? 0 : insets.top;
+ if (insets != null) {
+ width -= (insets.right + insets.left);
+ }
+ if (insets != null) {
+ height -= (insets.top + insets.bottom);
+ }
+
+ GeneralPath result = new GeneralPath();
+ // float radius3 = (float) (radius / (1.5 * Math.pow(height, 0.5)));
+ // if (Math.max(width, height) < 15)
+ // radius3 /= 2;
+
+ if (isTopLeftCorner) {
+ result.moveTo(xs, ys);
+ } else {
+ result.moveTo(xs + radius, ys);
+ }
+
+ if (isTopRightCorner) {
+ result.lineTo(xs + width - 1, ys);
+ } else {
+ if (isTopLeftCorner || ((xs + width - radius - 1) >= radius)) {
+ result.lineTo(xs + width - radius - 1, ys);
+ }
+ result.append(new Arc2D.Double(xs + width - 1 - 2 * radius, ys,
+ 2 * radius, 2 * radius, 90, -90, Arc2D.OPEN), true);
+
+ // result.quadTo(xs + width - 1 - radius3, ys + radius3, xs + width
+ // - 1, ys + radius);
+ }
+
+ if (isBottomRightCorner) {
+ result.lineTo(xs + width - 1, ys + height - 1);
+ } else {
+ if (isTopRightCorner || ((ys + height - radius - 1) >= radius)) {
+ result.lineTo(xs + width - 1, ys + height - radius - 1);
+ }
+
+ result.append(new Arc2D.Double(xs + width - 2 * radius - 1, ys
+ + height - 1 - 2 * radius, 2 * radius, 2 * radius, 0, -90,
+ Arc2D.OPEN), true);
+
+ // result.quadTo(xs + width - 1 - radius3, ys + height - 1 -
+ // radius3,
+ // xs + width - radius - 1, ys + height - 1);
+ }
+
+ if (isBottomLeftCorner) {
+ result.lineTo(xs, ys + height - 1);
+ } else {
+ if (isBottomRightCorner || ((xs + width - radius - 1) >= radius)) {
+ result.lineTo(xs + radius, ys + height - 1);
+ }
+ result.append(new Arc2D.Double(xs, ys + height - 2 * radius - 1,
+ 2 * radius, 2 * radius, 270, -90, Arc2D.OPEN), true);
+ // result.quadTo(xs + radius3, ys + height - 1 - radius3, xs, ys
+ // + height - radius - 1);
+ }
+
+ if (isTopLeftCorner) {
+ result.lineTo(xs, ys);
+ } else {
+ if (isBottomLeftCorner || ((ys + height - radius - 1) >= radius)) {
+ result.lineTo(xs, ys + radius);
+ }
+ result.append(new Arc2D.Double(xs, ys, 2 * radius, 2 * radius, 180,
+ -90, Arc2D.OPEN), true);
+ // result.quadTo(xs + radius3, ys + radius3, xs + radius, ys);
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns outline that has a triangle poiting downwards. The top two
+ * corners in the outline are rounded. This function can be used to draw
+ * slider thumbs.
+ *
+ * @param width
+ * Width of some UI component.
+ * @param height
+ * Height of some UI component.
+ * @param radius
+ * Corner radius for the top two corners.
+ * @param insets
+ * Insets to compute the outline.
+ * @return Outline that has a triangle poiting downwards.
+ */
+ public static GeneralPath getTriangleButtonOutline(int width, int height,
+ float radius, int insets) {
+ return getTriangleButtonOutline(width, height, radius, new Insets(
+ insets, insets, insets, insets));
+
+ }
+
+ /**
+ * Returns outline that has a triangle poiting downwards. The top two
+ * corners in the outline are rounded. This function can be used to draw
+ * slider thumbs.
+ *
+ * @param width
+ * Width of some UI component.
+ * @param height
+ * Height of some UI component.
+ * @param radius
+ * Corner radius for the top two corners.
+ * @param insets
+ * Insets to compute the outline.
+ * @return Outline that has a triangle poiting downwards.
+ */
+ public static GeneralPath getTriangleButtonOutline(int width, int height,
+ float radius, Insets insets) {
+
+ int xs = insets.left;
+ int ys = insets.top;
+ int xe = width - insets.right;
+ xe--;
+ int ye = height - insets.bottom;
+ width -= (insets.right + insets.left);
+ height -= (insets.top + insets.bottom);
+
+ GeneralPath result = new GeneralPath();
+ float radius3 = (float) (radius / (1.5 * Math.pow(height, 0.5)));
+ if (Math.max(width, height) < 15)
+ radius3 /= 2;
+
+ result.moveTo(radius + xs, ys);
+
+ if ((xe - radius) >= radius) {
+ result.lineTo(xe - radius, ys);
+ }
+ result.quadTo(xe - radius3, xs + radius3, xe, xs + radius);
+
+ float h2 = (ye - 1.0f) / 2.0f;
+ if (h2 >= radius) {
+ result.lineTo(xe, h2);
+ }
+
+ result.lineTo((xe + insets.right) / 2.0f, ye - 1);
+ result.lineTo(xs, h2);
+
+ if (h2 >= radius) {
+ result.lineTo(xs, h2);
+ }
+
+ if ((height - radius - 1) >= radius) {
+ result.lineTo(xs, radius + ys);
+ }
+ result.quadTo(xs + radius3, ys + radius3, xs + radius, ys);
+
+ return result;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceSizeUtils.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceSizeUtils.java
new file mode 100644
index 0000000..b295d55
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceSizeUtils.java
@@ -0,0 +1,984 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.*;
+
+import javax.swing.border.Border;
+import javax.swing.plaf.BorderUIResource;
+
+import org.pushingpixels.substance.api.SubstanceConstants;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.fonts.FontPolicy;
+import org.pushingpixels.substance.api.fonts.FontSet;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.internal.fonts.DefaultGnomeFontPolicy;
+
+/**
+ * This class is responsible for computing DPI-aware insets, stroke widths,
+ * paddings, icon sizes etc. This class if for internal use only.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceSizeUtils {
+ /**
+ * Cached control font size.
+ */
+ private static int controlFontSize = -1;
+
+ /**
+ * The points to pixels ratio of the current font policy.
+ */
+ private static double pointsToPixelsRatio = 1.0;
+
+ /**
+ * Gets the current control font size.
+ *
+ * @return Control font size.
+ */
+ public static int getControlFontSize() {
+ if (controlFontSize > 0)
+ return controlFontSize;
+ FontPolicy fPolicy = SubstanceLookAndFeel.getFontPolicy();
+ FontSet fSet = fPolicy.getFontSet("Substance", null);
+ controlFontSize = fSet.getControlFont().getSize();
+ return controlFontSize;
+ }
+
+ /**
+ * Sets the new value for the control font size.
+ *
+ * @param size
+ * Control font size.
+ */
+ public static void setControlFontSize(int size) {
+ controlFontSize = size;
+ }
+
+ /**
+ * Computes the font size for the specified component. If the component is
+ * <code>null</code> or doesn't have font set ({@link Component#getFont()}
+ * returns <code>null</code>), this method returns the default control font
+ * size from {@link #getControlFontSize()}.
+ *
+ * @param c
+ * Component.
+ * @return Font size for the specified component
+ */
+ public static int getComponentFontSize(Component c) {
+ return ((c == null) || (c.getFont() == null)) ? getControlFontSize()
+ : c.getFont().getSize();
+ }
+
+ /**
+ * Gets the adjusted size. The basic functionality of this method is as
+ * follows:
+ *
+ * <ul>
+ * <li>The <code>baseSize</code> parameter specifies the base value</li>
+ * <li>The <code>forEachBase</code> and <code>toAdjustBy</code> specify how
+ * to adjust the resulting value based on the passed <code>fontSize</code>.</li>
+ * </ul>
+ *
+ * For example, if you want base value to be 1.2 pixels, and have it grow by
+ * 0.1 pixel for every additional pixel in the font size, call this method
+ * with the following values:
+ *
+ * <ul>
+ * <li><code>baseSize</code> = 1.2</li>
+ * <li><code>forEachBase</code> = 1</li>
+ * <li><code>toAdjustBy</code> = 0.1</li>
+ * </ul>
+ *
+ * @param fontSize
+ * Font size.
+ * @param baseSize
+ * The base value.
+ * @param forEachBase
+ * Base units for computing the adjustment.
+ * @param toAdjustBy
+ * Adjustment amount for computing the adjustment.
+ * @return Adjusted size.
+ */
+ public static float getAdjustedSize(int fontSize, float baseSize,
+ int forEachBase, float toAdjustBy) {
+ int delta = fontSize - 11;
+ if (delta <= 0)
+ return baseSize;
+ float result = baseSize + delta * toAdjustBy / forEachBase;
+ return result;
+ }
+
+ /**
+ * Gets the adjusted size. The basic functionality of this method is as
+ * follows:
+ *
+ * <ul>
+ * <li>The <code>baseSize</code> parameter specifies the base value</li>
+ * <li>The <code>forEachBase</code> and <code>toAdjustBy</code> specify how
+ * to adjust the resulting value based on the passed <code>fontSize</code>.</li>
+ * </ul>
+ *
+ * For example, if you want base value to be 4 pixels, and have it grow by 1
+ * pixel for every 3 additional pixels in the font size, call this method
+ * with the following values:
+ *
+ * <ul>
+ * <li><code>baseSize</code> = 4</li>
+ * <li><code>forEachBase</code> = 3</li>
+ * <li><code>toAdjustBy</code> = 1</li>
+ * </ul>
+ *
+ * @param fontSize
+ * Font size.
+ * @param baseSize
+ * The base value.
+ * @param forEachBase
+ * Base units for computing the adjustment.
+ * @param toAdjustBy
+ * Adjustment amount for computing the adjustment.
+ * @param toRoundAsEven
+ * If <code>true</code>, the final value will be rounded down to
+ * the closest even value.
+ * @return Adjusted size.
+ */
+ public static int getAdjustedSize(int fontSize, int baseSize,
+ int forEachBase, int toAdjustBy, boolean toRoundAsEven) {
+ int delta = fontSize - 11;
+ if (delta <= 0)
+ return baseSize;
+ int result = baseSize + delta * toAdjustBy / forEachBase;
+ if (toRoundAsEven && (result % 2 != 0))
+ result--;
+ return result;
+ }
+
+ /**
+ * Returns the height of arrow icons under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Height of arrow icons under the specified font size.
+ */
+ public static float getArrowIconHeight(int fontSize) {
+ if (fontSize < 12)
+ return 2.5f + fontSize * 0.5f;
+ return 3.0f + fontSize * 0.6f;
+ }
+
+ /**
+ * Returns the width of arrow icons under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Width of arrow icons under the specified font size.
+ */
+ public static float getArrowIconWidth(int fontSize) {
+ int result = 2 * fontSize / 3;
+ if (result % 2 == 0)
+ result++;
+ return result + 4;
+ }
+
+ /**
+ * Returns the stroke width of arrow icons under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Stroke width of arrow icons under the specified font size.
+ */
+ public static float getArrowStrokeWidth(int fontSize) {
+ return fontSize / 6.0f;
+ }
+
+ /**
+ * Returns the stroke width of borders under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Stroke width of borders under the specified font size.
+ */
+ public static float getBorderStrokeWidth(int fontSize) {
+ return fontSize / 10.0f;
+ }
+
+ /**
+ * Returns the list cell renderer insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return List cell renderer insets under the specified font size.
+ */
+ public static Insets getButtonInsets(int fontSize) {
+ // Special handling to make buttons
+ // have the same height as text components.
+ // We subtract the border stroke width - since the new
+ // text component border appearance has a lighter "halo"
+ // around the darker inner border.
+ Insets textInsets = getTextBorderInsets(fontSize);
+ int borderStroke = (int) getBorderStrokeWidth(fontSize);
+ int topDelta = textInsets.top - borderStroke;
+ int bottomDelta = textInsets.bottom - borderStroke;
+
+ int lrInset = SubstanceSizeUtils.getAdjustedSize(fontSize, 4, 4, 1,
+ false);
+ return new Insets(topDelta, lrInset, bottomDelta, lrInset);
+ }
+
+ /**
+ * Returns the border for check boxes under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Border for check boxes under the specified font size.
+ */
+ public static Border getCheckBoxBorder(int fontSize, boolean ltr) {
+ // The base insets are 2,3,3,5. We add one pixel for
+ // each 3 extra points in base control size.
+ int tInset = getAdjustedSize(fontSize, 2, 3, 1, false);
+ int bInset = getAdjustedSize(fontSize, 3, 3, 1, false);
+ if (fontSize == 11) {
+ tInset = 2;
+ bInset = 2;
+ }
+ int leadingInset = getAdjustedSize(fontSize, 3, 3, 1, false);
+ int trailingInset = getAdjustedSize(fontSize, 5, 3, 1, false);
+
+ return new BorderUIResource.EmptyBorderUIResource(tInset,
+ ltr ? leadingInset : trailingInset, bInset, ltr ? trailingInset
+ : leadingInset);
+ }
+
+ /**
+ * Returns the check mark size for check boxes under the specified font
+ * size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Check mark size for check boxes under the specified font size.
+ */
+ public static int getCheckBoxMarkSize(int fontSize) {
+ return 5 + fontSize;
+ }
+
+ /**
+ * Returns the corner radius for {@link ClassicButtonShaper} under the
+ * specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Corner radius for {@link ClassicButtonShaper} under the specified
+ * font size.
+ */
+ public static float getClassicButtonCornerRadius(int fontSize) {
+ return getAdjustedSize(fontSize, 2, 6, 1, false);
+ }
+
+ /**
+ * Returns the combo box border insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Combo box border insets under the specified font size.
+ */
+ public static Insets getComboBorderInsets(int fontSize) {
+ // The base insets are 1,2,1,2. We add one pixel for
+ // each 3 extra points in base control size.
+ int tbInset = getAdjustedSize(fontSize, 1, 3, 1, false);
+ int lrInset = getAdjustedSize(fontSize, 2, 3, 1, false);
+ return new Insets(tbInset, lrInset, tbInset, lrInset);
+ }
+
+ /**
+ * Returns the combo box border insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Combo box border insets under the specified font size.
+ */
+ public static Insets getComboLayoutInsets(int fontSize) {
+ // The base insets are 2,2,2,2. We add one pixel for
+ // each 4 extra points in base control size.
+ int tbInset = getAdjustedSize(fontSize, 2, 4, 1, false);
+ int lrInset = getAdjustedSize(fontSize, 2, 4, 1, false);
+ return new Insets(tbInset, lrInset, tbInset, lrInset);
+ }
+
+ // /**
+ // * Returns the combo box border insets under the specified font size.
+ // *
+ // * @param fontSize
+ // * Font size.
+ // * @return Combo box border insets under the specified font size.
+ // */
+ // public static Insets getEditableComboBorderInsets(int fontSize) {
+ // // The base insets are 2,2,2,2. We add one pixel for
+ // // each 4 extra points in base control size.
+ // int tbInset = getAdjustedSize(fontSize, 2, 4, 1, false);
+ // int lrInset = getAdjustedSize(fontSize, 2, 4, 1, false);
+ // return new Insets(tbInset, lrInset, tbInset, lrInset);
+ // }
+
+ /**
+ * Returns the combo box text border insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Combo box text border insets under the specified font size.
+ */
+ public static Insets getComboTextBorderInsets(int fontSize) {
+ // the following makes sure that the text components
+ // and combos have the same height and text alignment
+ // under all font sizes.
+ Insets textInsets = getTextBorderInsets(fontSize);
+ Insets comboInsets = getComboBorderInsets(fontSize);
+ int topDelta = textInsets.top - comboInsets.top;// - 1;
+ int bottomDelta = textInsets.bottom - comboInsets.bottom;// - 1;
+
+ int lrInset = getAdjustedSize(fontSize, 3, 4, 1, false);
+ return new Insets(topDelta, lrInset, bottomDelta, lrInset);
+ }
+
+ /**
+ * Returns the default border insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Default border insets under the specified font size.
+ */
+ public static Insets getDefaultBorderInsets(int fontSize) {
+ // The base insets are 2,2,2,2. We add one pixel for
+ // each 3 extra points in base control size.
+ int inset = getAdjustedSize(fontSize, 2, 3, 1, false);
+ return new Insets(inset, inset, inset, inset);
+ }
+
+ /**
+ * Returns the stroke width of double arrow icons under the specified font
+ * size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Stroke width of double arrow icons under the specified font size.
+ */
+ public static float getDoubleArrowStrokeWidth(int fontSize) {
+ return fontSize / 8.0f;
+ }
+
+ /**
+ * Returns the diameter of a drag bump dot under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Diameter of a drag bump dot under the specified font size.
+ */
+ public static int getDragBumpDiameter(int fontSize) {
+ return getAdjustedSize(fontSize, 2, 4, 1, false);
+ }
+
+ /**
+ * Returns the diameter of a big drag bump dot under the specified font
+ * size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Diameter of a big drag bump dot under the specified font size.
+ */
+ public static int getBigDragBumpDiameter(int fontSize) {
+ int result = getAdjustedSize(fontSize, 3, 3, 1, false);
+ if (result % 2 != 0)
+ result++;
+ return result;
+ }
+
+ /**
+ * Returns the extra padding amount under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Extra padding amount under the specified font size.
+ */
+ public static int getExtraPadding(int fontSize) {
+ if (fontSize < 14)
+ return 0;
+ return (int) SubstanceSizeUtils.getAdjustedSize(fontSize, 0, 3, 1.2f);
+ }
+
+ /**
+ * Returns the focus ring padding amount under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Focus ring padding amount under the specified font size.
+ */
+ public static int getFocusRingPadding(int fontSize) {
+ if (fontSize < 14)
+ return 2;
+ return 3 + (int) SubstanceSizeUtils.getAdjustedSize(fontSize, 0, 3,
+ 0.8f);
+ }
+
+ /**
+ * Returns the stroke width of focus rings under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Stroke width of focus rings under the specified font size.
+ */
+ public static float getFocusStrokeWidth(int fontSize) {
+ return Math.max(1.0f, fontSize / 10.0f);
+ }
+
+ /**
+ * Returns the list cell renderer insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return List cell renderer insets under the specified font size.
+ */
+ public static Insets getListCellRendererInsets(int fontSize) {
+ // Special handling to make non-editable combo boxes
+ // have the same height as text components. The combo box
+ // uses list cell renderer, so to compute the top and
+ // bottom insets of a list cell renderer, we subtract the
+ // insets of combo box from the insets of text component.
+ // We also subtract the border stroke width - since the new
+ // text component border appearance has a lighter "halo"
+ // around the darker inner border.
+ Insets textInsets = getTextBorderInsets(fontSize);
+ Insets comboInsets = getComboBorderInsets(fontSize);
+ int borderStroke = (int) getBorderStrokeWidth(fontSize);
+ int topDelta = textInsets.top - comboInsets.top - borderStroke;
+ int bottomDelta = textInsets.bottom - comboInsets.bottom - borderStroke;
+
+ int lrInset = SubstanceSizeUtils.getAdjustedSize(fontSize, 4, 4, 1,
+ false);
+ return new Insets(topDelta, lrInset, bottomDelta, lrInset);
+ }
+
+ /**
+ * Returns the check mark size of check box menu items and radio button menu
+ * items under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Check mark size of check box menu items and radio button menu
+ * items under the specified font size.
+ */
+ public static int getMenuCheckMarkSize(int fontSize) {
+ int result = fontSize - 2;
+ if (result % 2 == 0)
+ result--;
+ return result;
+ }
+
+ /**
+ * Returns the margin for menu items under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Margin for menu items under the specified font size.
+ */
+ public static int getMenuItemMargin(int fontSize) {
+ return getAdjustedSize(fontSize, 2, 4, 1, false);
+ }
+
+ /**
+ * Returns the gap between text and icon in buttons and menu items under the
+ * specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Gap between text and icon in button menu items under the
+ * specified font size.
+ */
+ public static int getTextIconGap(int fontSize) {
+ return getAdjustedSize(fontSize, 4, 3, 1, false);
+ }
+
+ /**
+ * Returns the maximum button height under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Maximum button height under the specified font size.
+ */
+ public static int getMinButtonWidth(int fontSize) {
+ return 5 * fontSize + 12;
+ }
+
+ /**
+ * Returns the password dot diameter for password fields under the specified
+ * font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Password dot diameter for password fields under the specified
+ * font size.
+ */
+ public static int getPasswordDotDiameter(int fontSize) {
+ return getAdjustedSize(fontSize, 7, 2, 1, false);
+ }
+
+ /**
+ * Returns the password dot gap for password fields under the specified font
+ * size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Password dot gap for password fields under the specified font
+ * size.
+ */
+ public static int getPasswordDotGap(int fontSize) {
+ return (fontSize - 6) / 3;
+ }
+
+ /**
+ * Returns the border for radio buttons under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Border for radio buttons under the specified font size.
+ */
+ public static Border getRadioButtonBorder(int fontSize, boolean ltr) {
+ Border checkBoxBorder = getCheckBoxBorder(fontSize, ltr);
+ Insets checkBoxInsets = checkBoxBorder.getBorderInsets(null);
+ return new BorderUIResource.EmptyBorderUIResource(checkBoxInsets.top,
+ checkBoxInsets.left - (ltr ? 0 : 2), checkBoxInsets.bottom,
+ checkBoxInsets.right - (ltr ? 2 : 0));
+ }
+
+ /**
+ * Returns the check mark size for radio buttons under the specified font
+ * size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Check mark size for radio buttons under the specified font size.
+ */
+ public static int getRadioButtonMarkSize(int fontSize) {
+ int result = fontSize;
+ if (result % 2 == 0)
+ result--;
+ return result;
+ }
+
+ /**
+ * Returns the width of scroll bars under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Width of scroll bars under the specified font size.
+ */
+ public static int getScrollBarWidth(int fontSize) {
+ int result = (int) (getArrowIconWidth(fontSize) * 3 / 2);
+ if (result % 2 == 0)
+ result++;
+ return result;
+ }
+
+ /**
+ * Returns the slider thumb icon size under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Slider thumb icon size under the specified font size.
+ */
+ public static int getSliderIconSize(int fontSize) {
+ int result = fontSize + 5;
+ if (result % 2 != 0)
+ result--;
+ return result;
+ }
+
+ /**
+ * Returns the slider tick size under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Slider tick size under the specified font size.
+ */
+ public static int getSliderTickSize(int fontSize) {
+ return Math.max(7, fontSize - 3);
+ }
+
+ /**
+ * Returns the slider track size under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Slider track size under the specified font size.
+ */
+ public static int getSliderTrackSize(int fontSize) {
+ return SubstanceSizeUtils.getAdjustedSize(fontSize, 5, 4, 1, false);
+ }
+
+ /**
+ * Returns the height of small arrow icons under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Height of small arrow icons under the specified font size.
+ */
+ public static float getSmallArrowIconHeight(int fontSize) {
+ return getArrowIconHeight(fontSize) - 1;
+ }
+
+ /**
+ * Returns the width of small arrow icons under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Width of small arrow icons under the specified font size.
+ */
+ public static float getSmallArrowIconWidth(int fontSize) {
+ return getArrowIconWidth(fontSize) - 2;
+ }
+
+ /**
+ * Returns the height of spinner arrow icons under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Height of spinner arrow icons under the specified font size.
+ */
+ public static float getSpinnerArrowIconHeight(int fontSize) {
+ float result = SubstanceSizeUtils.getArrowIconHeight(fontSize)
+ + SubstanceSizeUtils
+ .getAdjustedSize(fontSize + 1, 0, 1, -0.25f);
+ return result;
+ }
+
+ /**
+ * Returns the width of spinner arrow icons under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Width of spinner arrow icons under the specified font size.
+ */
+ public static float getSpinnerArrowIconWidth(int fontSize) {
+ int result = (int) (SubstanceSizeUtils.getArrowIconWidth(fontSize) + SubstanceSizeUtils
+ .getAdjustedSize(fontSize, 1, 1, -0.15f));
+ if (result % 2 == 0)
+ result--;
+ return result;
+ }
+
+ /**
+ * Returns the spinner border insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Spinner border insets under the specified font size.
+ */
+ public static Insets getSpinnerBorderInsets(int fontSize) {
+ // make sure that spinners and combos have the same height and text
+ // alignment under all font sizes.
+ Insets comboInsets = getComboBorderInsets(fontSize);
+ return new Insets(comboInsets.top + 1, comboInsets.left,
+ comboInsets.bottom + 1, comboInsets.right);
+ }
+
+ /**
+ * Returns the spinner arrow button insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Spinner arrow button insets under the specified font size.
+ */
+ public static Insets getSpinnerArrowButtonInsets(int fontSize) {
+ int borderStrokeWidth = (int) Math
+ .floor(getBorderStrokeWidth(fontSize));
+ return new Insets(borderStrokeWidth, borderStrokeWidth,
+ borderStrokeWidth, borderStrokeWidth);
+ }
+
+ // /**
+ // * Returns the spinner button width under the specified font size.
+ // *
+ // * @param fontSize
+ // * Font size.
+ // * @return Spinner button width under the specified font size.
+ // */
+ // public static int getSpinnerButtonWidth(int fontSize) {
+ // return (int)(getArrowIconWidth(fontSize) * 3 / 2);
+ // }
+
+ /**
+ * Returns the spinner text border insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Spinner text border insets under the specified font size.
+ */
+ public static Insets getSpinnerTextBorderInsets(int fontSize) {
+ Insets textInsets = getComboTextBorderInsets(fontSize);
+ return new Insets(textInsets.top - 1, textInsets.left,
+ textInsets.bottom - 1, textInsets.right);
+ }
+
+ /**
+ * Returns the height of split pane divider arrow icons under the specified
+ * font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Height of split pane divider arrow icons under the specified font
+ * size.
+ */
+ public static float getSplitPaneArrowIconHeight(int fontSize) {
+ float result = SubstanceSizeUtils.getArrowIconHeight(fontSize)
+ + SubstanceSizeUtils.getAdjustedSize(fontSize, -1, 1, -0.3f);
+ return result;
+ }
+
+ /**
+ * Returns the width of split pane divider arrow icons under the specified
+ * font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Width of split pane divider arrow icons under the specified font
+ * size.
+ */
+ public static float getSplitPaneArrowIconWidth(int fontSize) {
+ float result = SubstanceSizeUtils.getArrowIconWidth(fontSize)
+ + SubstanceSizeUtils.getAdjustedSize(fontSize, -2, 1, -0.25f);
+ // if (result % 2 == 0)
+ // result--;
+ return result;
+ }
+
+ /**
+ * Returns the offset of the first split pane divider button under the
+ * specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Offset of the first split pane divider button under the specified
+ * font size.
+ */
+ public static int getSplitPaneButtonOffset(int fontSize) {
+ return getAdjustedSize(fontSize, 2, 3, 1, false);
+ }
+
+ /**
+ * Returns the tabbed pane content insets under the specified size. The
+ * {@link org.pushingpixels.substance.api.SubstanceConstants.TabContentPaneBorderKind#SINGLE_FULL} is
+ * assumed.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Tabbed pane content insets under the specified size.
+ */
+ public static Insets getTabbedPaneContentInsets(int fontSize) {
+ float borderStrokeWidth = getBorderStrokeWidth(fontSize);
+ int tbIns = (int) (Math.ceil(2.5 * borderStrokeWidth));
+ int lrIns = (int) (Math.ceil(3.0 * borderStrokeWidth));
+ return new Insets(tbIns, lrIns, tbIns, lrIns);
+ }
+
+ /**
+ * Returns the stroke width of tab close buttons under the specified size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Stroke width of tab close buttons under the specified size.
+ */
+ public static float getTabCloseButtonStrokeWidth(int fontSize) {
+ return fontSize / 10.0f;
+ }
+
+ /**
+ * Returns the icon size of tab close buttons under the specified size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Icon size of tab close buttons under the specified size.
+ */
+ public static int getTabCloseIconSize(int fontSize) {
+ return fontSize - 2;
+ }
+
+ /**
+ * Returns the table cell renderer insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Table cell renderer insets under the specified font size.
+ */
+ public static Insets getTableCellRendererInsets(int fontSize) {
+ Insets textInsets = getTextBorderInsets(fontSize);
+ Insets comboInsets = getComboBorderInsets(fontSize);
+ int topDelta = textInsets.top - comboInsets.top - 1;
+ int bottomDelta = textInsets.bottom - comboInsets.bottom - 2;
+ if (fontSize == 11) {
+ bottomDelta++;
+ }
+
+ int lrInset = SubstanceSizeUtils.getAdjustedSize(fontSize, 2, 4, 1,
+ false);
+ return new Insets(topDelta, lrInset, bottomDelta, lrInset);
+ }
+
+ /**
+ * Returns the text border insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Text border insets under the specified font size.
+ */
+ public static Insets getTextBorderInsets(int fontSize) {
+ // The base insets are 3,5,4,5. We add one pixel for
+ // each 3 extra points in base control size.
+ int tInset = getAdjustedSize(fontSize, 3, 3, 1, false);
+ int bInset = getAdjustedSize(fontSize, 4, 3, 1, false);
+ if (fontSize == 11) {
+ tInset = 3;
+ bInset = 3;
+ }
+ int lrInset = getAdjustedSize(fontSize, 5, 3, 1, false);
+ return new Insets(tInset, lrInset, bInset, lrInset);
+ }
+
+ /**
+ * Returns the text button padding amount on left and right sides under the
+ * specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Text button padding amount on left and right sides under the
+ * specified font size.
+ */
+ public static int getTextButtonLRPadding(int fontSize) {
+ return SubstanceSizeUtils.getAdjustedSize(fontSize, 3, 2, 1, false);
+ }
+
+ /**
+ * Returns the icon size of title pane buttons under the specified size.
+ *
+ * @return Icon size of title pane buttons under the specified size.
+ */
+ public static int getTitlePaneIconSize() {
+ return 5 + getControlFontSize();
+ }
+
+ /**
+ * Returns the tool bar drag inset under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Tool bar drag inset under the specified font size.
+ */
+ public static int getToolBarDragInset(int fontSize) {
+ return fontSize + 5;
+ }
+
+ /**
+ * Returns the tool bar insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Tool bar insets under the specified font size.
+ */
+ public static Insets getToolBarInsets(int fontSize) {
+ int lbrInset = getAdjustedSize(fontSize, 2, 3, 1, false);
+ int tInset = getAdjustedSize(fontSize, 1, 3, 1, false);
+ return new Insets(tInset, lbrInset, lbrInset, lbrInset);
+ }
+
+ /**
+ * Returns the tooltip border insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Tooltip border insets under the specified font size.
+ */
+ public static Insets getToolTipBorderInsets(int fontSize) {
+ // The base insets are 1,1,1,1. We add one pixel for
+ // each 3 extra points in base control size.
+ int inset = getAdjustedSize(fontSize, 1, 3, 1, false);
+ return new Insets(inset, inset, inset, inset);
+ }
+
+ /**
+ * Returns the tree cell renderer insets under the specified font size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Tree cell renderer insets under the specified font size.
+ */
+ public static Insets getTreeCellRendererInsets(int fontSize) {
+ Insets listCellInsets = getListCellRendererInsets(fontSize);
+ return new Insets(listCellInsets.top - 1, listCellInsets.left - 2,
+ listCellInsets.bottom - 1, listCellInsets.right - 2);
+ }
+
+ /**
+ * Returns the icon size of tree expand / collapse icons under the specified
+ * size.
+ *
+ * @param fontSize
+ * Font size.
+ * @return Icon size of tree expand / collapse icons under the specified
+ * size.
+ */
+ public static int getTreeIconSize(int fontSize) {
+ int extraPadding = SubstanceSizeUtils.getExtraPadding(fontSize);
+ int extraPadding2 = 2 * extraPadding;
+ return 10 + extraPadding2;
+ }
+
+ /**
+ * Returns the points to pixels ratio of the current font policy.
+ *
+ * @return The points to pixels ratio of the current font policy.
+ */
+ public static double getPointsToPixelsRatio() {
+ return pointsToPixelsRatio;
+ }
+
+ /**
+ * Resets the points to pixels ratio based on the specified font policy.
+ *
+ * @param fontPolicy
+ * Font policy.
+ */
+ public static void resetPointsToPixelsRatio(FontPolicy fontPolicy) {
+ if (fontPolicy instanceof DefaultGnomeFontPolicy) {
+ pointsToPixelsRatio = DefaultGnomeFontPolicy
+ .getPointsToPixelsRatio();
+ } else {
+ pointsToPixelsRatio = Toolkit.getDefaultToolkit()
+ .getScreenResolution() / 72.0;
+ }
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceSpinnerButton.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceSpinnerButton.java
new file mode 100644
index 0000000..eed0cb8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceSpinnerButton.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.border.SubstanceButtonBorder;
+
+/**
+ * Spinner button in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceSpinnerButton extends JButton implements Sideable,
+ SubstanceInternalArrowButton {
+ static {
+ AnimationConfigurationManager.getInstance().disallowAnimations(
+ AnimationFacet.GHOSTING_BUTTON_PRESS,
+ SubstanceSpinnerButton.class);
+ AnimationConfigurationManager.getInstance().disallowAnimations(
+ AnimationFacet.GHOSTING_ICON_ROLLOVER,
+ SubstanceSpinnerButton.class);
+ }
+
+ /**
+ * Button orientation.
+ */
+ private int orientation;
+
+ private abstract static class SpinnerButtonBorder extends
+ SubstanceButtonBorder {
+ public SpinnerButtonBorder(Class<?> buttonShaperClass) {
+ super(buttonShaperClass);
+ }
+ }
+
+ /**
+ * Simple constructor.
+ *
+ * @param spinner
+ * The owner spinner.
+ * @param orientation
+ * The orientation of the spinner icon arrow.
+ */
+ public SubstanceSpinnerButton(JSpinner spinner, final int orientation) {
+ this.setEnabled(spinner.isEnabled());
+ this.setFocusable(false);
+ this.setRequestFocusEnabled(false);
+ this.setMargin(new Insets(0, 0, 0, 2));
+ this.setBorder(new SpinnerButtonBorder(ClassicButtonShaper.class) {
+ @Override
+ public Insets getBorderInsets(Component c) {
+ int extraPadding = SubstanceSizeUtils
+ .getExtraPadding(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ // Bring the icons closer together instead of
+ // having them centered in the spinner buttons
+ int delta = SubstanceSizeUtils.getAdjustedSize(
+ SubstanceSizeUtils.getComponentFontSize(c), 3, 3, 1,
+ false);
+ int deltaTop = (orientation == SwingConstants.NORTH) ? delta
+ : 0;
+ int deltaBottom = (orientation == SwingConstants.NORTH) ? 0
+ : delta;
+ return new Insets(extraPadding + deltaTop, extraPadding,
+ extraPadding + deltaBottom, extraPadding);
+ }
+ });
+ this.orientation = orientation;
+
+ this.setOpaque(false);
+ this.setBorderPainted(false);
+ this.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY, Boolean.TRUE);
+ }
+
+ @Override
+ public void setBorder(Border border) {
+ if (border instanceof SpinnerButtonBorder) {
+ super.setBorder(border);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Component#isFocusable()
+ */
+ @Override
+ public boolean isFocusable() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.Sideable#getSide()
+ */
+ @Override
+ public SubstanceConstants.Side getSide() {
+ switch (this.orientation) {
+ case SwingConstants.NORTH:
+ return SubstanceConstants.Side.BOTTOM;
+ case SwingConstants.WEST:
+ return SubstanceConstants.Side.RIGHT;
+ case SwingConstants.SOUTH:
+ return SubstanceConstants.Side.TOP;
+ case SwingConstants.EAST:
+ return SubstanceConstants.Side.LEFT;
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ protected void paintBorder(Graphics g) {
+ if (SubstanceCoreUtilities.isButtonNeverPainted(this)) {
+ return;
+ }
+
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) this.getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ ComponentState currState = modelStateInfo.getCurrModelState();
+ float extraAlpha = stateTransitionTracker.getActiveStrength();
+
+ if (currState == ComponentState.DISABLED_UNSELECTED)
+ extraAlpha = 0.0f;
+
+ if (extraAlpha == 0.0f)
+ return;
+
+ boolean isNextButton = "Spinner.nextButton".equals(this.getName());
+
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(this);
+ int borderDelta = (int) Math.floor(1.5 * SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize));
+ float radius = Math.max(0, 2.0f
+ * SubstanceSizeUtils
+ .getClassicButtonCornerRadius(componentFontSize)
+ - borderDelta);
+
+ int width = getWidth();
+ int height = getHeight();
+
+ JSpinner parent = (JSpinner) this.getParent();
+ BufferedImage offscreen = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D g2offscreen = offscreen.createGraphics();
+ int offsetX = this.getX();
+ int offsetY = this.getY();
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this, ColorSchemeAssociationKind.BORDER,
+ currState);
+
+ if (isNextButton) {
+ SubstanceImageCreator.paintTextComponentBorder(this, g2offscreen,
+ 0, 0, width, 1 * height, radius, baseBorderScheme);
+ g2offscreen.translate(-offsetX, -offsetY);
+ SubstanceImageCreator.paintTextComponentBorder(parent, g2offscreen,
+ 0, 0, parent.getWidth(), parent.getHeight(), radius,
+ baseBorderScheme);
+ g2offscreen.translate(offsetX, offsetY);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ g2offscreen.setComposite(AlphaComposite.SrcOver
+ .derive(contribution));
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this,
+ ColorSchemeAssociationKind.BORDER, activeState);
+
+ SubstanceImageCreator.paintTextComponentBorder(this,
+ g2offscreen, 0, 0, width, 1 * height, radius,
+ borderScheme);
+ g2offscreen.translate(-offsetX, -offsetY);
+ SubstanceImageCreator.paintTextComponentBorder(parent,
+ g2offscreen, 0, 0, parent.getWidth(), parent
+ .getHeight(), radius, borderScheme);
+ g2offscreen.translate(offsetX, offsetY);
+ }
+ } else {
+ SubstanceImageCreator.paintTextComponentBorder(this, g2offscreen,
+ 0, 0, width, 1 * height, radius, baseBorderScheme);
+ g2offscreen.translate(-offsetX, -offsetY);
+ SubstanceImageCreator.paintTextComponentBorder(parent, g2offscreen,
+ 0, 0, parent.getWidth(), parent.getHeight(), radius,
+ baseBorderScheme);
+ g2offscreen.translate(offsetX, offsetY);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ g2offscreen.setComposite(AlphaComposite.SrcOver
+ .derive(contribution));
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this,
+ ColorSchemeAssociationKind.BORDER, activeState);
+
+ SubstanceImageCreator.paintTextComponentBorder(this,
+ g2offscreen, 0, 0, width, 1 * height, radius,
+ borderScheme);
+ g2offscreen.translate(-offsetX, -offsetY);
+ SubstanceImageCreator.paintTextComponentBorder(parent,
+ g2offscreen, 0, 0, parent.getWidth(), parent
+ .getHeight(), radius, borderScheme);
+ g2offscreen.translate(offsetX, offsetY);
+ }
+ }
+ g2offscreen.dispose();
+
+ // System.out.println(prevState + ":" + currState + ":" + extraAlpha);
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this, extraAlpha,
+ g));
+ g2d.drawImage(offscreen, 0, 0, null);
+ g2d.dispose();
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(this);
+ int width = getWidth();
+ int height = getHeight();
+ int clipDelta = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize);
+
+ if (this.getComponentOrientation().isLeftToRight()) {
+ g2d.clipRect(clipDelta, 0, width - clipDelta, height);
+ } else {
+ g2d.clipRect(0, 0, width - clipDelta, height);
+ }
+ super.paint(g2d);
+ g2d.dispose();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceSplitPaneDivider.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceSplitPaneDivider.java
new file mode 100644
index 0000000..c6c7dc1
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceSplitPaneDivider.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Map;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.basic.BasicSplitPaneDivider;
+import javax.swing.plaf.basic.BasicSplitPaneUI;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.ModelStateInfo;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.ui.SubstanceSplitPaneUI;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
+
+/**
+ * Split pane divider in <code>Substance</code> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceSplitPaneDivider extends BasicSplitPaneDivider implements
+ TransitionAwareUI {
+ /**
+ * Listener for transition animations.
+ */
+ private RolloverControlListener substanceRolloverListener;
+
+ protected StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Listener on property change events.
+ */
+ private PropertyChangeListener substancePropertyChangeListener;
+ /**
+ * Surrogate button model for tracking the thumb transitions.
+ */
+ private ButtonModel gripModel;
+
+ /**
+ * Simple constructor.
+ *
+ * @param ui
+ * Associated UI.
+ */
+ public SubstanceSplitPaneDivider(SubstanceSplitPaneUI ui) {
+ super(ui);
+ this.setLayout(new SubstanceDividerLayout());
+ }
+
+ @Override
+ public void setBasicSplitPaneUI(BasicSplitPaneUI newUI) {
+ if (this.splitPane != null) {
+ // fix for defect 358 - multiple listeners were installed
+ // on the same split pane
+ this.uninstall();
+ }
+
+ if (newUI != null) {
+ // installing
+ this.splitPane = newUI.getSplitPane();
+
+ this.gripModel = new DefaultButtonModel();
+ this.gripModel.setArmed(false);
+ this.gripModel.setSelected(false);
+ this.gripModel.setPressed(false);
+ this.gripModel.setRollover(false);
+ this.gripModel.setEnabled(this.splitPane.isEnabled());
+
+ this.stateTransitionTracker = new StateTransitionTracker(
+ this.splitPane, this.gripModel);
+
+ // fix for defect 109 - memory leak on changing skin
+ this.substanceRolloverListener = new RolloverControlListener(this,
+ this.gripModel);
+ this.addMouseListener(this.substanceRolloverListener);
+ this.addMouseMotionListener(this.substanceRolloverListener);
+
+ this.substancePropertyChangeListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("enabled".equals(evt.getPropertyName())) {
+ boolean isEnabled = splitPane.isEnabled();
+ gripModel.setEnabled(isEnabled);
+ if (leftButton != null)
+ leftButton.setEnabled(isEnabled);
+ if (rightButton != null)
+ rightButton.setEnabled(isEnabled);
+ setEnabled(isEnabled);
+ }
+ }
+ };
+ // System.out.println("Registering " + this.hashCode() + ":"
+ // + this.substancePropertyChangeListener.hashCode() + " on "
+ // + this.splitPane.hashCode());
+ this.splitPane
+ .addPropertyChangeListener(this.substancePropertyChangeListener);
+
+ this.stateTransitionTracker.registerModelListeners();
+ } else {
+ uninstall();
+ }
+ super.setBasicSplitPaneUI(newUI);
+ }
+
+ /**
+ * Uninstalls this divider.
+ */
+ private void uninstall() {
+ // uninstalling
+ // fix for defect 109 - memory leak on changing skin
+ this.removeMouseListener(this.substanceRolloverListener);
+ this.removeMouseMotionListener(this.substanceRolloverListener);
+ this.substanceRolloverListener = null;
+
+ if (this.substancePropertyChangeListener != null) {
+ // System.out.println("Unregistering " + this.hashCode() + ":"
+ // + this.substancePropertyChangeListener.hashCode()
+ // + " from " + this.splitPane.hashCode());
+ this.splitPane
+ .removePropertyChangeListener(this.substancePropertyChangeListener);
+ this.substancePropertyChangeListener = null;
+ }
+
+ this.stateTransitionTracker.unregisterModelListeners();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Component#paint(java.awt.Graphics)
+ */
+ @Override
+ public void paint(Graphics g) {
+ if (SubstanceCoreUtilities.hasFlatAppearance(this.splitPane, true)) {
+ BackgroundPaintingUtils.updateIfOpaque(g, this.splitPane);
+ }
+
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ ModelStateInfo modelStateInfo = this.stateTransitionTracker
+ .getModelStateInfo();
+ ComponentState currState = modelStateInfo.getCurrModelState();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(this.splitPane,
+ currState);
+
+ // compute the grip handle dimension
+ int minSizeForGripPresence = SubstanceSizeUtils.getAdjustedSize(
+ SubstanceSizeUtils.getComponentFontSize(this), 30, 1, 2, false);
+ int maxGripSize = SubstanceSizeUtils.getAdjustedSize(SubstanceSizeUtils
+ .getComponentFontSize(this), 40, 1, 3, false);
+ if (this.splitPane.getOrientation() == JSplitPane.HORIZONTAL_SPLIT) {
+ int thumbHeight = this.getHeight();
+ if (thumbHeight >= minSizeForGripPresence) {
+ int gripHeight = thumbHeight / 4;
+ if (gripHeight > maxGripSize)
+ gripHeight = maxGripSize;
+
+ int thumbWidth = this.getWidth();
+
+ int gripX = 0;
+ int gripY = (thumbHeight - gripHeight) / 2;
+
+ // draw the grip bumps
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ float contribution = activeEntry.getValue()
+ .getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ ComponentState activeState = activeEntry.getKey();
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.splitPane, alpha * contribution, g));
+ SubstanceImageCreator.paintSplitDividerBumpImage(graphics,
+ this, gripX, gripY, thumbWidth, gripHeight, false,
+ SubstanceColorSchemeUtilities.getColorScheme(this,
+ ColorSchemeAssociationKind.MARK,
+ activeState));
+ }
+ }
+ } else {
+ int thumbWidth = this.getWidth();
+ if (thumbWidth >= minSizeForGripPresence) {
+ int gripWidth = thumbWidth / 4;
+ if (gripWidth > maxGripSize)
+ gripWidth = maxGripSize;
+
+ int thumbHeight = this.getHeight();
+ // int gripHeight = thumbHeight * 2 / 3;
+
+ int gripX = (thumbWidth - gripWidth) / 2;
+ int gripY = 1;
+
+ // draw the grip bumps
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ float contribution = activeEntry.getValue()
+ .getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ ComponentState activeState = activeEntry.getKey();
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ this.splitPane, alpha * contribution, g));
+ SubstanceImageCreator.paintSplitDividerBumpImage(graphics,
+ this, gripX, gripY, gripWidth, thumbHeight, true,
+ SubstanceColorSchemeUtilities.getColorScheme(this,
+ ColorSchemeAssociationKind.MARK,
+ activeState));
+ }
+ }
+ }
+
+ graphics.dispose();
+
+ super.paint(g);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicSplitPaneDivider#createLeftOneTouchButton()
+ */
+ @Override
+ protected JButton createLeftOneTouchButton() {
+ JButton oneTouchButton = new JButton() {
+ // Don't want the button to participate in focus traversable.
+ @Override
+ public boolean isFocusable() {
+ return false;
+ }
+ };
+ Icon verticalSplit = new TransitionAwareIcon(oneTouchButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(splitPane);
+ return SubstanceImageCreator.getArrowIcon(
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconWidth(fontSize),
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconHeight(fontSize),
+ SubstanceSizeUtils
+ .getArrowStrokeWidth(fontSize),
+ SwingConstants.NORTH, scheme);
+ }
+ }, "substance.splitPane.left.vertical");
+ Icon horizontalSplit = new TransitionAwareIcon(oneTouchButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(splitPane);
+ return SubstanceImageCreator.getArrowIcon(
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconWidth(fontSize),
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconHeight(fontSize),
+ SubstanceSizeUtils
+ .getArrowStrokeWidth(fontSize),
+ SwingConstants.WEST, scheme);
+ }
+ }, "substance.splitPane.left.horizontal");
+ oneTouchButton
+ .setIcon(this.splitPane.getOrientation() == JSplitPane.VERTICAL_SPLIT ? verticalSplit
+ : horizontalSplit);
+
+ oneTouchButton.putClientProperty(
+ SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY, Boolean.TRUE);
+ // fix for issue 281 - set empty border so that the arrow
+ // icon is not cropped
+ oneTouchButton.setBorder(new EmptyBorder(0, 0, 0, 0));
+
+ oneTouchButton.setRequestFocusEnabled(false);
+ oneTouchButton
+ .setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ oneTouchButton.setFocusPainted(false);
+ oneTouchButton.setBorderPainted(false);
+ return oneTouchButton;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.plaf.basic.BasicSplitPaneDivider#createRightOneTouchButton()
+ */
+ @Override
+ protected JButton createRightOneTouchButton() {
+ JButton oneTouchButton = new JButton() {
+ // Don't want the button to participate in focus traversable.
+ @Override
+ public boolean isFocusable() {
+ return false;
+ }
+ };
+ Icon verticalSplit = new TransitionAwareIcon(oneTouchButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(splitPane);
+ return SubstanceImageCreator.getArrowIcon(
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconWidth(fontSize),
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconHeight(fontSize),
+ SubstanceSizeUtils
+ .getArrowStrokeWidth(fontSize),
+ SwingConstants.SOUTH, scheme);
+ }
+ }, "substance.splitPane.right.vertical");
+ Icon horizontalSplit = new TransitionAwareIcon(oneTouchButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(splitPane);
+ return SubstanceImageCreator.getArrowIcon(
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconWidth(fontSize),
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconHeight(fontSize),
+ SubstanceSizeUtils
+ .getArrowStrokeWidth(fontSize),
+ SwingConstants.EAST, scheme);
+ }
+ }, "substance.splitPane.right.horizontal");
+ oneTouchButton
+ .setIcon(this.splitPane.getOrientation() == JSplitPane.VERTICAL_SPLIT ? verticalSplit
+ : horizontalSplit);
+
+ oneTouchButton.putClientProperty(
+ SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY, Boolean.TRUE);
+ // fix for issue 281 - set empty border so that the arrow
+ // icon is not cropped
+ oneTouchButton.setBorder(new EmptyBorder(0, 0, 0, 0));
+
+ oneTouchButton
+ .setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ oneTouchButton.setFocusPainted(false);
+ oneTouchButton.setBorderPainted(false);
+ oneTouchButton.setRequestFocusEnabled(false);
+ // b.setOpaque(false);
+ return oneTouchButton;
+ }
+
+ /**
+ * Updates the one-touch buttons.
+ *
+ * @param orientation
+ * Split pane orientation.
+ */
+ public void updateOneTouchButtons(int orientation) {
+ if (orientation == JSplitPane.VERTICAL_SPLIT) {
+ if (this.leftButton != null) {
+ this.leftButton.setIcon(new TransitionAwareIcon(
+ this.leftButton, new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(splitPane);
+ return SubstanceImageCreator
+ .getArrowIcon(
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconWidth(fontSize),
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconHeight(fontSize),
+ SubstanceSizeUtils
+ .getArrowStrokeWidth(fontSize),
+ SwingConstants.NORTH, scheme);
+ }
+ }, "substance.splitPane.left.vertical"));
+ }
+ if (this.rightButton != null) {
+ this.rightButton.setIcon(new TransitionAwareIcon(
+ this.rightButton, new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(splitPane);
+ return SubstanceImageCreator
+ .getArrowIcon(
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconWidth(fontSize),
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconHeight(fontSize),
+ SubstanceSizeUtils
+ .getArrowStrokeWidth(fontSize),
+ SwingConstants.SOUTH, scheme);
+ }
+ }, "substance.splitPane.right.vertical"));
+ }
+ } else {
+ if (this.leftButton != null) {
+ this.leftButton.setIcon(new TransitionAwareIcon(
+ this.leftButton, new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(splitPane);
+ return SubstanceImageCreator
+ .getArrowIcon(
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconWidth(fontSize),
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconHeight(fontSize),
+ SubstanceSizeUtils
+ .getArrowStrokeWidth(fontSize),
+ SwingConstants.WEST, scheme);
+ }
+ }, "substance.splitPane.left.horizontal"));
+ }
+ if (this.rightButton != null) {
+ this.rightButton.setIcon(new TransitionAwareIcon(
+ this.rightButton, new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(splitPane);
+ return SubstanceImageCreator
+ .getArrowIcon(
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconWidth(fontSize),
+ SubstanceSizeUtils
+ .getSplitPaneArrowIconHeight(fontSize),
+ SubstanceSizeUtils
+ .getArrowStrokeWidth(fontSize),
+ SwingConstants.EAST, scheme);
+ }
+ }, "substance.splitPane.right.horizontal"));
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seeorg.pushingpixels.substance.utils.Trackable#isInside(java.awt.event.
+ * MouseEvent)
+ */
+ @Override
+ public boolean isInside(MouseEvent me) {
+ // entire area is sensitive
+ return true;
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+
+ /**
+ * Layout manager for the split pane divider.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class SubstanceDividerLayout extends DividerLayout {
+ @Override
+ public void layoutContainer(Container c) {
+ if (leftButton != null && rightButton != null
+ && c == SubstanceSplitPaneDivider.this) {
+ if (splitPane.isOneTouchExpandable()) {
+ Insets insets = getInsets();
+
+ if (orientation == JSplitPane.VERTICAL_SPLIT) {
+ int extraX = (insets != null) ? insets.left : 0;
+ int blockSize = getHeight();
+
+ if (insets != null) {
+ blockSize -= (insets.top + insets.bottom);
+ blockSize = Math.max(blockSize, 0);
+ }
+
+ int y = (c.getSize().height - blockSize) / 2;
+
+ int offset = SubstanceSizeUtils
+ .getSplitPaneButtonOffset(SubstanceSizeUtils
+ .getComponentFontSize(splitPane));
+ leftButton.setBounds(extraX + offset, y, leftButton
+ .getPreferredSize().width * 2 / 3, blockSize);
+ rightButton.setBounds(leftButton.getX()
+ + leftButton.getWidth(), y, rightButton
+ .getPreferredSize().width * 2 / 3, blockSize);
+ } else {
+ int extraY = (insets != null) ? insets.top : 0;
+ int blockSize = getWidth();
+
+ if (insets != null) {
+ blockSize -= (insets.left + insets.right);
+ blockSize = Math.max(blockSize, 0);
+ }
+
+ int x = (c.getSize().width - blockSize) / 2;
+
+ int offset = SubstanceSizeUtils
+ .getSplitPaneButtonOffset(SubstanceSizeUtils
+ .getComponentFontSize(splitPane));
+ leftButton.setBounds(x, extraY + offset, blockSize,
+ leftButton.getPreferredSize().height * 2 / 3);
+ rightButton.setBounds(x, leftButton.getY()
+ + leftButton.getHeight(), blockSize, leftButton
+ .getPreferredSize().height * 2 / 3);
+ }
+ } else {
+ leftButton.setBounds(-5, -5, 1, 1);
+ rightButton.setBounds(-5, -5, 1, 1);
+ }
+ }
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceStripingUtils.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceStripingUtils.java
new file mode 100644
index 0000000..ac32feb
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceStripingUtils.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.Color;
+
+import javax.swing.JComponent;
+
+import org.pushingpixels.substance.internal.ui.SubstanceTableUI;
+
+/**
+ * <p>
+ * This class is used to speed up the striping of lists, tables, trees and
+ * comboboxes that use Substance default renderers. This class if for internal
+ * use only.
+ * </p>
+ *
+ * <p>
+ * The usage is this:
+ * </p>
+ * <ul>
+ * <li>Call {@link #setup(JComponent)} before starting painting the component
+ * cells. An example -
+ * {@link SubstanceTableUI#paint(java.awt.Graphics, JComponent)} that should
+ * call this method prior to the call to its <code>paintCells</code>.</li>
+ * <li>The specific renderer should call
+ * {@link #applyStripedBackground(JComponent, int, JComponent)}.</li>
+ * <li>After all cells have been renderered, call {@link #tearDown(JComponent)}.
+ * </ul>
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceStripingUtils {
+ /**
+ * Name of the client property that stores the background fill color of odd
+ * rows. The value should be an instance of {@link Color}.
+ */
+ private static final String ODD_COLOR = "substancelaf.internal.stripingColor.odd";
+
+ /**
+ * Name of the client property that stores the background fill color of even
+ * rows. The value should be an instance of {@link Color}.
+ */
+ private static final String EVEN_COLOR = "substancelaf.internal.stripingColor.even";
+
+ /**
+ * Sets up the specified component for the UI delegate striping.
+ *
+ * @param comp
+ * Component.
+ */
+ public static void setup(JComponent comp) {
+ comp.putClientProperty(EVEN_COLOR, SubstanceColorUtilities
+ .getStripedBackground(comp, 0));
+ comp.putClientProperty(ODD_COLOR, SubstanceColorUtilities
+ .getStripedBackground(comp, 1));
+ }
+
+ /**
+ * Cleans the component after the UI delegate striping is over.
+ *
+ * @param comp
+ * Component. Should be the same as passed to
+ * {@link #setup(JComponent)
+ * }.
+ */
+ public static void tearDown(JComponent comp) {
+ comp.putClientProperty(EVEN_COLOR, null);
+ comp.putClientProperty(ODD_COLOR, null);
+ }
+
+ /**
+ * Applies the striped background to the specified renderer.
+ *
+ * @param component
+ * Component (should be the same as passed to
+ * {@link #setup(JComponent)
+ * }).
+ * @param rowIndex
+ * Row index.
+ * @param renderer
+ * Renderer component.
+ */
+ public static void applyStripedBackground(JComponent component,
+ int rowIndex, JComponent renderer) {
+ Color backgr = (Color) component
+ .getClientProperty((rowIndex % 2) == 0 ? EVEN_COLOR : ODD_COLOR);
+ if (backgr == null)
+ return;
+ renderer.setBackground(backgr);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceTextUtilities.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceTextUtilities.java
new file mode 100755
index 0000000..0253dd3
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceTextUtilities.java
@@ -0,0 +1,600 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.image.*;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicGraphicsUtils;
+import javax.swing.text.JTextComponent;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.lafwidget.text.LockBorder;
+import org.pushingpixels.lafwidget.utils.RenderingUtils;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.utils.border.SubstanceTextComponentBorder;
+
+/**
+ * Text-related utilities. This class if for internal use only.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTextUtilities {
+ public static final String ENFORCE_FG_COLOR = "substancelaf.internal.textUtilities.enforceFgColor";
+
+ /**
+ * Paints text with drop shadow.
+ *
+ * @param c
+ * Component.
+ * @param g
+ * Graphics context.
+ * @param foregroundColor
+ * Foreground color.
+ * @param text
+ * Text to paint.
+ * @param width
+ * Text rectangle width.
+ * @param height
+ * Text rectangle height.
+ * @param xOffset
+ * Text rectangle X offset.
+ * @param yOffset
+ * Text rectangle Y offset.
+ */
+ public static void paintTextWithDropShadow(JComponent c, Graphics g,
+ Color foregroundColor, String text, int width, int height,
+ int xOffset, int yOffset) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ RenderingUtils.installDesktopHints(graphics, c);
+
+ // blur the text shadow
+ BufferedImage blurred = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D gBlurred = (Graphics2D) blurred.getGraphics();
+ gBlurred.setFont(graphics.getFont());
+ gBlurred.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
+ // Color neg =
+ // SubstanceColorUtilities.getNegativeColor(foregroundColor);
+ float luminFactor = SubstanceColorUtilities
+ .getColorStrength(foregroundColor);
+ gBlurred.setColor(SubstanceColorUtilities
+ .getNegativeColor(foregroundColor));
+ ConvolveOp convolve = new ConvolveOp(new Kernel(3, 3, new float[] {
+ .02f, .05f, .02f, .05f, .02f, .05f, .02f, .05f, .02f }),
+ ConvolveOp.EDGE_NO_OP, null);
+ gBlurred.drawString(text, xOffset, yOffset - 1);
+ blurred = convolve.filter(blurred, null);
+
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c,
+ luminFactor, g));
+ graphics.drawImage(blurred, 0, 0, null);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c, g));
+
+ FontMetrics fm = graphics.getFontMetrics();
+ SubstanceTextUtilities.paintText(graphics, c, new Rectangle(xOffset,
+ yOffset - fm.getAscent(), width - xOffset, fm.getHeight()),
+ text, -1, graphics.getFont(), foregroundColor, graphics
+ .getClipBounds());
+
+ graphics.dispose();
+ }
+
+ /**
+ * Paints the specified text.
+ *
+ * @param g
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param textRect
+ * Text rectangle.
+ * @param text
+ * Text to paint.
+ * @param mnemonicIndex
+ * Mnemonic index.
+ * @param font
+ * Font to use.
+ * @param color
+ * Color to use.
+ * @param clip
+ * Optional clip. Can be <code>null</code>.
+ * @param transform
+ * Optional transform to apply. Can be <code>null</code>.
+ */
+ private static void paintText(Graphics g, JComponent comp,
+ Rectangle textRect, String text, int mnemonicIndex,
+ java.awt.Font font, java.awt.Color color, java.awt.Rectangle clip,
+ java.awt.geom.AffineTransform transform) {
+ if ((text == null) || (text.length() == 0))
+ return;
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ // workaroundBug6576507(g2d);
+ // RenderingUtils.installDesktopHints(g2d);
+
+ g2d.setFont(font);
+ g2d.setColor(color);
+ // fix for issue 420 - call clip() instead of setClip() to
+ // respect the currently set clip shape
+ if (clip != null)
+ g2d.clip(clip);
+ if (transform != null)
+ g2d.transform(transform);
+ BasicGraphicsUtils.drawStringUnderlineCharAt(g2d, text, mnemonicIndex,
+ textRect.x, textRect.y + g2d.getFontMetrics().getAscent());
+ g2d.dispose();
+ }
+
+ /**
+ * Paints the specified text.
+ *
+ * @param g
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param textRect
+ * Text rectangle.
+ * @param text
+ * Text to paint.
+ * @param mnemonicIndex
+ * Mnemonic index.
+ * @param font
+ * Font to use.
+ * @param color
+ * Color to use.
+ * @param clip
+ * Optional clip. Can be <code>null</code>.
+ */
+ public static void paintText(Graphics g, JComponent comp,
+ Rectangle textRect, String text, int mnemonicIndex,
+ java.awt.Font font, java.awt.Color color, java.awt.Rectangle clip) {
+ SubstanceTextUtilities.paintText(g, comp, textRect, text,
+ mnemonicIndex, font, color, clip, null);
+ }
+
+ /**
+ * Paints the specified vertical text.
+ *
+ * @param g
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param textRect
+ * Text rectangle.
+ * @param text
+ * Text to paint.
+ * @param mnemonicIndex
+ * Mnemonic index.
+ * @param font
+ * Font to use.
+ * @param color
+ * Color to use.
+ * @param clip
+ * Optional clip. Can be <code>null</code>.
+ * @param isFromBottomToTop
+ * If <code>true</code>, the text will be painted from bottom to
+ * top, otherwise the text will be painted from top to bottom.
+ */
+ public static void paintVerticalText(Graphics g, JComponent comp,
+ Rectangle textRect, String text, int mnemonicIndex,
+ java.awt.Font font, java.awt.Color color, java.awt.Rectangle clip,
+ boolean isFromBottomToTop) {
+ if ((text == null) || (text.length() == 0))
+ return;
+
+ AffineTransform at;
+
+ if (!isFromBottomToTop) {
+ at = AffineTransform.getTranslateInstance(textRect.x
+ + textRect.width, textRect.y);
+ at.rotate(Math.PI / 2);
+ } else {
+ at = AffineTransform.getTranslateInstance(textRect.x, textRect.y
+ + textRect.height);
+ at.rotate(-Math.PI / 2);
+ }
+ Rectangle newRect = new Rectangle(0, 0, textRect.width, textRect.height);
+
+ SubstanceTextUtilities.paintText(g, comp, newRect, text, mnemonicIndex,
+ font, color, clip, at);
+ }
+
+ /**
+ * Paints the text of the specified button.
+ *
+ * @param g
+ * Graphic context.
+ * @param button
+ * Button
+ * @param textRect
+ * Text rectangle
+ * @param text
+ * Text to paint
+ * @param mnemonicIndex
+ * Mnemonic index.
+ */
+ public static void paintText(Graphics g, AbstractButton button,
+ Rectangle textRect, String text, int mnemonicIndex) {
+ paintText(g, button, button.getModel(), textRect, text, mnemonicIndex);
+ }
+
+ /**
+ * Paints the text of the specified button.
+ *
+ * @param g
+ * Graphic context.
+ * @param button
+ * Button
+ * @param model
+ * Button model.
+ * @param textRect
+ * Text rectangle
+ * @param text
+ * Text to paint
+ * @param mnemonicIndex
+ * Mnemonic index.
+ */
+ public static void paintText(Graphics g, AbstractButton button,
+ ButtonModel model, Rectangle textRect, String text,
+ int mnemonicIndex) {
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) button
+ .getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+
+ float buttonAlpha = SubstanceColorSchemeUtilities.getAlpha(button,
+ ComponentState.getState(button));
+
+ if (button instanceof JMenuItem) {
+ paintMenuItemText(g, (JMenuItem) button, textRect, text,
+ mnemonicIndex, stateTransitionTracker.getModelStateInfo(),
+ buttonAlpha);
+ } else {
+ paintText(g, button, textRect, text, mnemonicIndex,
+ stateTransitionTracker.getModelStateInfo(), buttonAlpha);
+ }
+ }
+
+ /**
+ * Paints the specified text.
+ *
+ * @param g
+ * Graphics context.
+ * @param component
+ * Component.
+ * @param textRect
+ * Text rectangle.
+ * @param text
+ * Text to paint.
+ * @param mnemonicIndex
+ * Mnemonic index.
+ * @param state
+ * Component state.
+ * @param textAlpha
+ * Alpha channel for painting the text.
+ */
+ public static void paintText(Graphics g, JComponent component,
+ Rectangle textRect, String text, int mnemonicIndex,
+ ComponentState state, float textAlpha) {
+ Color fgColor = getForegroundColor(component, text, state, textAlpha);
+
+ SubstanceTextUtilities.paintText(g, component, textRect, text,
+ mnemonicIndex, component.getFont(), fgColor, null);
+ }
+
+ public static void paintText(Graphics g, JComponent component,
+ Rectangle textRect, String text, int mnemonicIndex,
+ StateTransitionTracker.ModelStateInfo modelStateInfo,
+ float textAlpha) {
+ Color fgColor = getForegroundColor(component, text, modelStateInfo,
+ textAlpha);
+
+ SubstanceTextUtilities.paintText(g, component, textRect, text,
+ mnemonicIndex, component.getFont(), fgColor, null);
+ }
+
+ public static void paintMenuItemText(Graphics g, JMenuItem menuItem,
+ Rectangle textRect, String text, int mnemonicIndex,
+ StateTransitionTracker.ModelStateInfo modelStateInfo,
+ float textAlpha) {
+ Color fgColor = getMenuComponentForegroundColor(menuItem, text,
+ modelStateInfo, textAlpha);
+
+ SubstanceTextUtilities.paintText(g, menuItem, textRect, text,
+ mnemonicIndex, menuItem.getFont(), fgColor, null);
+ }
+
+ /**
+ * Returns the foreground color for the specified component.
+ *
+ * @param component
+ * Component.
+ * @param text
+ * Text. If empty or <code>null</code>, the result is
+ * <code>null</code>.
+ * @param state
+ * Component state.
+ * @param textAlpha
+ * Alpha channel for painting the text. If value is less than
+ * 1.0, the result is an opaque color which is an interpolation
+ * between the "real" foreground color and the background color
+ * of the component. This is done to ensure that native text
+ * rasterization will be performed on 6u10+ on Windows.
+ * @return The foreground color for the specified component.
+ */
+ public static Color getForegroundColor(JComponent component, String text,
+ ComponentState state, float textAlpha) {
+ if ((text == null) || (text.length() == 0))
+ return null;
+
+ boolean toEnforceFgColor = (SwingUtilities.getAncestorOfClass(
+ CellRendererPane.class, component) != null)
+ || Boolean.TRUE.equals(component
+ .getClientProperty(ENFORCE_FG_COLOR));
+
+ Color fgColor = toEnforceFgColor ? component.getForeground()
+ : SubstanceColorSchemeUtilities
+ .getColorScheme(component, state).getForegroundColor();
+
+ // System.out.println(text + ":" + prevState.name() + "->" +
+ // state.name() + ":" + fgColor);
+ if (textAlpha < 1.0f) {
+ Color bgFillColor = SubstanceColorUtilities
+ .getBackgroundFillColor(component);
+ if (bgFillColor != null) {
+ fgColor = SubstanceColorUtilities.getInterpolatedColor(fgColor,
+ bgFillColor, textAlpha);
+ }
+ }
+ return fgColor;
+ }
+
+ /**
+ * Returns the foreground color for the specified component.
+ *
+ * @param component
+ * Component.
+ * @param text
+ * Text. If empty or <code>null</code>, the result is
+ * <code>null</code>.
+ * @param textAlpha
+ * Alpha channel for painting the text. If value is less than
+ * 1.0, the result is an opaque color which is an interpolation
+ * between the "real" foreground color and the background color
+ * of the component. This is done to ensure that native text
+ * rasterization will be performed on 6u10 on Windows.
+ * @return The foreground color for the specified component.
+ */
+ public static Color getForegroundColor(JComponent component, String text,
+ StateTransitionTracker.ModelStateInfo modelStateInfo,
+ float textAlpha) {
+ if ((text == null) || (text.length() == 0))
+ return null;
+
+ boolean toEnforceFgColor = (SwingUtilities.getAncestorOfClass(
+ CellRendererPane.class, component) != null)
+ || Boolean.TRUE.equals(component
+ .getClientProperty(ENFORCE_FG_COLOR));
+
+ Color fgColor;
+ if (toEnforceFgColor) {
+ fgColor = component.getForeground();
+ } else {
+ fgColor = SubstanceColorUtilities.getForegroundColor(component,
+ modelStateInfo);
+ }
+
+ // System.out.println(text + ":" + prevState.name() + "->" +
+ // state.name() + ":" + fgColor);
+ if (textAlpha < 1.0f) {
+ Color bgFillColor = SubstanceColorUtilities
+ .getBackgroundFillColor(component);
+ fgColor = SubstanceColorUtilities.getInterpolatedColor(fgColor,
+ bgFillColor, textAlpha);
+ }
+ return fgColor;
+ }
+
+ /**
+ * Returns the foreground color for the specified menu component.
+ *
+ * @param menuComponent
+ * Menu component.
+ * @param text
+ * Text. If empty or <code>null</code>, the result is
+ * <code>null</code>.
+ * @param modelStateInfo
+ * Model state info for the specified component.
+ * @param textAlpha
+ * Alpha channel for painting the text. If value is less than
+ * 1.0, the result is an opaque color which is an interpolation
+ * between the "real" foreground color and the background color
+ * of the component. This is done to ensure that native text
+ * rasterization will be performed on 6u10 on Windows.
+ * @return The foreground color for the specified component.
+ */
+ public static Color getMenuComponentForegroundColor(Component menuComponent,
+ String text, StateTransitionTracker.ModelStateInfo modelStateInfo,
+ float textAlpha) {
+ if ((text == null) || (text.length() == 0))
+ return null;
+
+ Color fgColor = SubstanceColorUtilities
+ .getMenuComponentForegroundColor(menuComponent, modelStateInfo);
+
+ if (textAlpha < 1.0f) {
+ Color bgFillColor = SubstanceColorUtilities
+ .getBackgroundFillColor(menuComponent);
+ fgColor = SubstanceColorUtilities.getInterpolatedColor(fgColor,
+ bgFillColor, textAlpha);
+ }
+ return fgColor;
+ }
+
+ /**
+ * Paints background of the specified text component.
+ *
+ * @param g
+ * Graphics context.
+ * @param comp
+ * Component.
+ */
+ public static void paintTextCompBackground(Graphics g, JComponent comp) {
+ Color backgroundFillColor = getTextBackgroundFillColor(comp);
+
+ boolean toPaintWatermark = (SubstanceLookAndFeel.getCurrentSkin(comp)
+ .getWatermark() != null)
+ && (SubstanceCoreUtilities.toDrawWatermark(comp) || !comp
+ .isOpaque());
+ paintTextCompBackground(g, comp, backgroundFillColor, toPaintWatermark);
+ }
+
+ public static Color getTextBackgroundFillColor(JComponent comp) {
+ Color backgroundFillColor = SubstanceColorUtilities
+ .getBackgroundFillColor(comp);
+ JTextComponent componentForTransitions = SubstanceCoreUtilities
+ .getTextComponentForTransitions(comp);
+
+ if (componentForTransitions != null) {
+ ComponentUI ui = componentForTransitions.getUI();
+ if (ui instanceof TransitionAwareUI) {
+ TransitionAwareUI trackable = (TransitionAwareUI) ui;
+ StateTransitionTracker stateTransitionTracker = trackable
+ .getTransitionTracker();
+
+ Color outerTextComponentBorderColor = SubstanceColorUtilities
+ .getOuterTextComponentBorderColor(backgroundFillColor);
+ outerTextComponentBorderColor = SubstanceColorUtilities
+ .getInterpolatedColor(outerTextComponentBorderColor,
+ backgroundFillColor, 0.6);
+
+ float selectionStrength = stateTransitionTracker
+ .getFacetStrength(ComponentStateFacet.SELECTION);
+ float rolloverStrength = stateTransitionTracker
+ .getFacetStrength(ComponentStateFacet.ROLLOVER);
+ backgroundFillColor = SubstanceColorUtilities
+ .getInterpolatedColor(outerTextComponentBorderColor,
+ backgroundFillColor, Math.max(
+ selectionStrength, rolloverStrength));
+ }
+ }
+ return backgroundFillColor;
+ }
+
+ /**
+ * Paints background of the specified text component.
+ *
+ * @param g
+ * Graphics context.
+ * @param comp
+ * Component.
+ * @param backgr
+ * Background color.
+ * @param toOverlayWatermark
+ * If <code>true</code>, this method will paint the watermark
+ * overlay on top of the background fill.
+ */
+ private static void paintTextCompBackground(Graphics g, JComponent comp,
+ Color backgr, boolean toOverlayWatermark) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ int componentFontSize = SubstanceSizeUtils.getComponentFontSize(comp);
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(componentFontSize));
+ Border compBorder = comp.getBorder();
+
+ if (compBorder instanceof LockBorder) {
+ compBorder = ((LockBorder) compBorder).getOriginalBorder();
+ }
+ boolean isSubstanceBorder = compBorder instanceof SubstanceTextComponentBorder;
+
+ if (!isSubstanceBorder) {
+ Border border = compBorder;
+ while (border instanceof CompoundBorder) {
+ Border outer = ((CompoundBorder) border).getOutsideBorder();
+ if (outer instanceof SubstanceTextComponentBorder) {
+ isSubstanceBorder = true;
+ break;
+ }
+ Border inner = ((CompoundBorder) border).getInsideBorder();
+ if (inner instanceof SubstanceTextComponentBorder) {
+ isSubstanceBorder = true;
+ break;
+ }
+ border = inner;
+ }
+ }
+
+ Shape contour = isSubstanceBorder ? SubstanceOutlineUtilities
+ .getBaseOutline(
+ comp.getWidth(),
+ comp.getHeight(),
+ Math
+ .max(
+ 0,
+ 2.0f
+ * SubstanceSizeUtils
+ .getClassicButtonCornerRadius(componentFontSize)
+ - borderDelta), null,
+ borderDelta)
+ : new Rectangle(0, 0, comp.getWidth(), comp.getHeight());
+
+ BackgroundPaintingUtils.update(g, comp, false);
+ SubstanceWatermark watermark = SubstanceCoreUtilities.getSkin(comp)
+ .getWatermark();
+ if (watermark != null) {
+ watermark.drawWatermarkImage(g2d, comp, 0, 0, comp.getWidth(), comp
+ .getHeight());
+ }
+ g2d.setColor(backgr);
+ g2d.fill(contour);
+
+ if (toOverlayWatermark) {
+ if (watermark != null) {
+ g2d.clip(contour);
+ watermark.drawWatermarkImage(g2d, comp, 0, 0, comp.getWidth(),
+ comp.getHeight());
+ }
+ }
+
+ g2d.dispose();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceTitleButton.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceTitleButton.java
new file mode 100644
index 0000000..0d48f2b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceTitleButton.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import javax.accessibility.AccessibleContext;
+import javax.swing.JButton;
+import javax.swing.UIManager;
+
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+
+/**
+ * Title button in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTitleButton extends JButton implements
+ SubstanceInternalButton {
+ static {
+ AnimationConfigurationManager.getInstance().disallowAnimations(
+ AnimationFacet.GHOSTING_BUTTON_PRESS,
+ SubstanceTitleButton.class);
+ AnimationConfigurationManager.getInstance().disallowAnimations(
+ AnimationFacet.GHOSTING_ICON_ROLLOVER,
+ SubstanceTitleButton.class);
+ }
+
+ private String uiKey;
+
+ public SubstanceTitleButton(String uiKey) {
+ this.uiKey = uiKey;
+ this.setOpaque(false);
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return false;
+ }
+
+ public SubstanceTitleButton() {
+ this("");
+ }
+
+ @Override
+ public boolean isFocusTraversable() {
+ return false;
+ }
+
+ @Override
+ public void requestFocus() {
+ };
+
+ @Override
+ public AccessibleContext getAccessibleContext() {
+ AccessibleContext ac = super.getAccessibleContext();
+ if (uiKey != null) {
+ ac.setAccessibleName(UIManager.getString(uiKey));
+ uiKey = null;
+ }
+ return ac;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceTitlePane.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceTitlePane.java
new file mode 100755
index 0000000..ba87912
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceTitlePane.java
@@ -0,0 +1,1871 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import org.pushingpixels.lafwidget.animation.effects.GhostPaintingUtils;
+import org.pushingpixels.lafwidget.utils.RenderingUtils;
+import org.pushingpixels.lafwidget.utils.TrackableThread;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceConstants.SubstanceWidgetType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.skin.SkinInfo;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.ui.SubstanceButtonUI;
+import org.pushingpixels.substance.internal.ui.SubstanceRootPaneUI;
+import org.pushingpixels.substance.internal.utils.icon.SubstanceIconFactory;
+import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;
+
+import javax.swing.*;
+import javax.swing.plaf.UIResource;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Title pane for <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTitlePane extends JComponent {
+ /**
+ * PropertyChangeListener added to the JRootPane.
+ */
+ private PropertyChangeListener propertyChangeListener;
+
+ /**
+ * JMenuBar, typically renders the system menu items.
+ */
+ protected JMenuBar menuBar;
+
+ /**
+ * Action used to close the Window.
+ */
+ private Action closeAction;
+
+ /**
+ * Action used to iconify the Frame.
+ */
+ private Action iconifyAction;
+
+ /**
+ * Action to restore the Frame size.
+ */
+ private Action restoreAction;
+
+ /**
+ * Action to restore the Frame size.
+ */
+ private Action maximizeAction;
+
+ /**
+ * Button used to maximize or restore the frame.
+ */
+ protected JButton toggleButton;
+
+ /**
+ * Button used to minimize the frame
+ */
+ protected JButton minimizeButton;
+
+ /**
+ * Button used to close the frame.
+ */
+ protected JButton closeButton;
+
+ /**
+ * Listens for changes in the state of the Window listener to update the
+ * state of the widgets.
+ */
+ private WindowListener windowListener;
+
+ /**
+ * Window we're currently in.
+ */
+ protected Window window;
+
+ /**
+ * JRootPane rendering for.
+ */
+ protected JRootPane rootPane;
+
+ /**
+ * Buffered Frame.state property. As state isn't bound, this is kept to
+ * determine when to avoid updating widgets.
+ */
+ private int state;
+
+ /**
+ * SubstanceRootPaneUI that created us.
+ */
+ private SubstanceRootPaneUI rootPaneUI;
+
+ /**
+ * The logfile name for the heap status panel. Can be <code>null</code> - in
+ * this case the {@link HeapStatusThread} will not write heap information.
+ */
+ private static String heapStatusLogfileName;
+
+ /**
+ * The heap status panel of <code>this</code> title pane.
+ */
+ protected HeapStatusPanel heapStatusPanel;
+
+ /**
+ * The heap status toggle menu item of <code>this</code> title pane.
+ */
+ protected JCheckBoxMenuItem heapStatusMenuItem;
+
+ /**
+ * Listens on changes to <code>componentOrientation</code> and
+ * {@link SubstanceLookAndFeel#WINDOW_MODIFIED} properties.
+ */
+ protected PropertyChangeListener propertyListener;
+
+ /**
+ * Client property to mark every child to be either leading or trailing. The
+ * value must be one of {@link ExtraComponentKind}.
+ *
+ * @see #markExtraComponent(JComponent, ExtraComponentKind)
+ * @see #getTitleTextRectangle(int)
+ */
+ protected static final String EXTRA_COMPONENT_KIND = "substancelaf.internal.titlePane.extraComponentKind";
+
+ /**
+ * The application icon to be displayed.
+ */
+ protected Image appIcon;
+
+ /**
+ * Enumerates the types of children components.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected enum ExtraComponentKind {
+ /**
+ * Leading child components (left on LTR and right on RTL).
+ */
+ LEADING,
+
+ /**
+ * Trailing child components (right on LTR and left on RTL).
+ */
+ TRAILING,
+
+ /**
+ * Middeling child compoennt, will jostle for position with the title text
+ * subclasses are responsible for the placement
+ */
+ MIDDLING,
+ }
+
+ /**
+ * Panel that shows heap status and allows running the garbage collector.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class HeapStatusPanel extends JPanel {
+ /**
+ * The current heap size in kilobytes.
+ */
+ private int currHeapSizeKB;
+
+ /**
+ * The current used portion of heap in kilobytes.
+ */
+ private int currTakenHeapSizeKB;
+
+ /**
+ * History of used heap portion (in percents). Each value is in 0.0-1.0
+ * range.
+ */
+ private LinkedList<Double> graphValues;
+
+ /**
+ * Creates new heap status panel.
+ */
+ public HeapStatusPanel() {
+ this.graphValues = new LinkedList<Double>();
+ HeapStatusThread.getInstance();
+ }
+
+ /**
+ * Updates the values for <code>this</code> heap status panel.
+ *
+ * @param currHeapSizeKB
+ * The current heap size in kilobytes.
+ * @param currTakenHeapSizeKB
+ * The current used portion of heap in kilobytes.
+ */
+ public synchronized void updateStatus(int currHeapSizeKB,
+ int currTakenHeapSizeKB) {
+ this.currHeapSizeKB = currHeapSizeKB;
+ this.currTakenHeapSizeKB = currTakenHeapSizeKB;
+ double newGraphValue = (double) currTakenHeapSizeKB
+ / (double) currHeapSizeKB;
+ this.graphValues.addLast(newGraphValue);
+ this.repaint();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paint(java.awt.Graphics)
+ */
+ @Override
+ public synchronized void paint(Graphics g) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ SubstanceColorScheme scheme = SubstanceCoreUtilities.getSkin(this)
+ .getActiveColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE);
+
+ graphics.setColor(scheme.getDarkColor());
+ int w = this.getWidth();
+ int h = this.getHeight();
+
+ graphics.drawRect(0, 0, w - 1, h - 1);
+
+ graphics.setColor(scheme.getExtraLightColor());
+ graphics.fillRect(1, 1, w - 2, h - 2);
+
+ while (this.graphValues.size() > (w - 2))
+ this.graphValues.removeFirst();
+
+ int xOff = w - this.graphValues.size() - 1;
+ graphics.setColor(scheme.getMidColor());
+ int count = 0;
+ for (double value : this.graphValues) {
+ int valueH = (int) (value * (h - 2));
+ graphics.drawLine(xOff + count, h - 1 - valueH, xOff + count,
+ h - 2);
+ count++;
+ }
+
+ graphics.setFont(UIManager.getFont("Panel.font"));
+ FontMetrics fm = graphics.getFontMetrics();
+
+ StringBuffer longFormat = new StringBuffer();
+ Formatter longFormatter = new Formatter(longFormat);
+ longFormatter.format("%.1fMB / %.1fMB",
+ this.currTakenHeapSizeKB / 1024.f,
+ this.currHeapSizeKB / 1024.f);
+ int strW = fm.stringWidth(longFormat.toString());
+ int strH = fm.getAscent() + fm.getDescent();
+
+ graphics.setColor(scheme.getForegroundColor());
+ RenderingUtils.installDesktopHints(graphics, this);
+ if (strW < (w - 5)) {
+ graphics.drawString(longFormat.toString(), (w - strW) / 2,
+ (h + strH) / 2 - 2);
+ } else {
+ String shortFormat = (this.currTakenHeapSizeKB / 1024)
+ + "MB / " + (this.currHeapSizeKB / 1024) + "MB";
+ strW = fm.stringWidth(shortFormat);
+ graphics.drawString(shortFormat, (w - strW) / 2,
+ (h + strH) / 2 - 2);
+ }
+
+ graphics.dispose();
+ }
+
+ /**
+ * Returns the preferred width of this panel.
+ *
+ * @return Preferred width of this panel.
+ */
+ public int getPreferredWidth() {
+ BufferedImage dummy = new BufferedImage(1, 1,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = dummy.createGraphics();
+ RenderingUtils.installDesktopHints(g2d, this);
+ g2d.setFont(UIManager.getFont("Panel.font"));
+ FontMetrics fm = g2d.getFontMetrics();
+ int result = fm.stringWidth("100.9MB / 200.9MB");
+ g2d.dispose();
+ return result;
+ }
+ }
+
+ /**
+ * Thread for heap status panel.
+ */
+ public static class HeapStatusThread extends TrackableThread {
+ /**
+ * Current heap size in kilobytes.
+ */
+ private int heapSizeKB;
+
+ /**
+ * Current used portion of heap in kilobytes.
+ */
+ private int takenHeapSizeKB;
+
+ /**
+ * All heap status panels.
+ */
+ private static Set<WeakReference<HeapStatusPanel>> panels = new HashSet<WeakReference<HeapStatusPanel>>();
+
+ /**
+ * Single instance of <code>this</code> thread.
+ */
+ private static HeapStatusThread instance;
+
+ /**
+ * Formatter object (for logfile).
+ */
+ private SimpleDateFormat format;
+
+ /**
+ * Signifies whether a stop request has been issued on <code>this</code>
+ * thread using the {@link #requestStop()} call.
+ */
+ private boolean isStopRequested;
+
+ /**
+ * Simple constructor. Defined private for singleton.
+ *
+ * @see #getInstance()
+ */
+ private HeapStatusThread() {
+ this.format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss.SSS");
+ this.isStopRequested = false;
+ this.setName("Substance heap status");
+ }
+
+ /**
+ * Gets singleton instance of <code>this</code> thread.
+ *
+ * @return Singleton instance of <code>this</code> thread.
+ */
+ public synchronized static HeapStatusThread getInstance() {
+ if (HeapStatusThread.instance == null) {
+ HeapStatusThread.instance = new HeapStatusThread();
+ HeapStatusThread.instance.start();
+ }
+ return HeapStatusThread.instance;
+ }
+
+ /**
+ * Registers new heap status panel with <code>this</code> thread.
+ *
+ * @param panel
+ * Heap statuc panel.
+ */
+ public static synchronized void registerPanel(HeapStatusPanel panel) {
+ panels.add(new WeakReference<HeapStatusPanel>(panel));
+ }
+
+ /**
+ * Unregisters new heap status panel from <code>this</code> thread.
+ *
+ * @param panel
+ * Heap statuc panel.
+ */
+ public static synchronized void unregisterPanel(HeapStatusPanel panel) {
+ for (Iterator<WeakReference<HeapStatusPanel>> it = panels
+ .iterator(); it.hasNext();) {
+ WeakReference<HeapStatusPanel> ref = it.next();
+ HeapStatusPanel currPanel = ref.get();
+ if (panel == currPanel) {
+ it.remove();
+ return;
+ }
+ }
+ }
+
+ /**
+ * Updates the values of heap status.
+ */
+ private synchronized void updateHeapCounts() {
+ long heapSize = Runtime.getRuntime().totalMemory();
+ long heapFreeSize = Runtime.getRuntime().freeMemory();
+
+ this.heapSizeKB = (int) (heapSize / 1024);
+ this.takenHeapSizeKB = (int) ((heapSize - heapFreeSize) / 1024);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Thread#run()
+ */
+ @Override
+ public void run() {
+ while (!this.isStopRequested) {
+ try {
+ // update every 0.5 seconds
+ Thread.sleep(500);
+ } catch (InterruptedException ignore) {
+ }
+ if (!SubstanceWidgetManager.getInstance().isAllowedAnywhere(
+ SubstanceWidgetType.TITLE_PANE_HEAP_STATUS))
+ continue;
+ this.updateHeapCounts();
+ for (Iterator<WeakReference<HeapStatusPanel>> it = panels
+ .iterator(); it.hasNext();) {
+ WeakReference<HeapStatusPanel> refPanel = it.next();
+ HeapStatusPanel panel = refPanel.get();
+ if (panel == null) {
+ // prune
+ it.remove();
+ continue;
+ }
+
+ panel.updateStatus(this.heapSizeKB, this.takenHeapSizeKB);
+ }
+ // see if need to put info in log file
+ if (SubstanceTitlePane.heapStatusLogfileName != null) {
+ PrintWriter pw = null;
+ try {
+ pw = new PrintWriter(new FileWriter(
+ SubstanceTitlePane.heapStatusLogfileName, true));
+ pw.println(this.format.format(new Date()) + " "
+ + this.takenHeapSizeKB + "KB / "
+ + this.heapSizeKB + "KB");
+ } catch (IOException ignore) {
+ } finally {
+ if (pw != null) {
+ pw.close();
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void requestStop() {
+ this.isStopRequested = true;
+ HeapStatusThread.instance = null;
+ }
+ }
+
+ /**
+ * Creates a new title pane.
+ *
+ * @param root
+ * Root pane.
+ * @param ui
+ * Root pane UI.
+ */
+ public SubstanceTitlePane(JRootPane root, SubstanceRootPaneUI ui) {
+ this.rootPane = root;
+ this.rootPaneUI = ui;
+
+ this.state = -1;
+
+ this.installSubcomponents();
+ this.installDefaults();
+
+ this.setLayout(this.createLayout());
+
+ this.setToolTipText(this.getTitle());
+
+ SubstanceLookAndFeel.setDecorationType(this,
+ DecorationAreaType.PRIMARY_TITLE_PANE);
+ this.setForeground(SubstanceColorUtilities
+ .getForegroundColor(SubstanceCoreUtilities.getSkin(this)
+ .getBackgroundColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE)));
+ // SubstanceColorSchemeUtilities
+ // .getColorScheme(this, ComponentState.ACTIVE)));
+ }
+
+ /**
+ * Uninstalls the necessary state.
+ */
+ public void uninstall() {
+ this.uninstallListeners();
+ this.window = null;
+
+ HeapStatusThread.unregisterPanel(this.heapStatusPanel);
+
+ // Swing bug (?) - the updateComponentTree never gets to the
+ // system menu (and in our case we have radio menu items with
+ // rollover listeners). Fix for defect 109 - memory leak on skin
+ // switch
+ if ((this.menuBar != null) && (this.menuBar.getMenuCount() > 0)) {
+ this.menuBar.getUI().uninstallUI(this.menuBar);
+ SubstanceCoreUtilities.uninstallMenu(this.menuBar.getMenu(0));
+ }
+
+ if (this.heapStatusPanel != null) {
+ for (MouseListener listener : this.heapStatusPanel
+ .getMouseListeners())
+ this.heapStatusPanel.removeMouseListener(listener);
+ HeapStatusThread.unregisterPanel(this.heapStatusPanel);
+ this.remove(this.heapStatusPanel);
+ }
+
+ if (this.menuBar != null)
+ this.menuBar.removeAll();
+ this.removeAll();
+ }
+
+ /**
+ * Installs the necessary listeners.
+ */
+ private void installListeners() {
+ if (this.window != null) {
+ this.windowListener = new WindowHandler();
+ this.window.addWindowListener(this.windowListener);
+ this.propertyChangeListener = new PropertyChangeHandler();
+ this.window.addPropertyChangeListener(this.propertyChangeListener);
+ }
+
+ // Property change listener for pulsating close button
+ // when window has been marked as changed.
+ // Fix for defect 109 - memory leak on skin change.
+ this.propertyListener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(final PropertyChangeEvent evt) {
+ if (SubstanceLookAndFeel.WINDOW_MODIFIED.equals(evt
+ .getPropertyName())) {
+ syncCloseButtonTooltip();
+ // if (Boolean.TRUE.equals(evt.getNewValue())) {
+ // SubstanceTitlePane.this.closeButton
+ // .setToolTipText(SubstanceLookAndFeel
+ // .getLabelBundle().getString(
+ // "SystemMenu.close")
+ // + " ["
+ // + SubstanceLookAndFeel
+ // .getLabelBundle()
+ // .getString(
+ // "Tooltip.contentsNotSaved")
+ // + "]");
+ // } else {
+ // SubstanceTitlePane.this.closeButton
+ // .setToolTipText(SubstanceLookAndFeel
+ // .getLabelBundle().getString(
+ // "SystemMenu.close"));
+ // }
+ // SubstanceTitlePane.this.closeButton.repaint();
+ }
+
+ if ("componentOrientation".equals(evt.getPropertyName())) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (SubstanceTitlePane.this.menuBar != null) {
+ SubstanceTitlePane.this.menuBar
+ .applyComponentOrientation((ComponentOrientation) evt
+ .getNewValue());
+ }
+ }
+ });
+ }
+ }
+ };
+ // Wire it on the frame itself and its root pane.
+ this.rootPane.addPropertyChangeListener(this.propertyListener);
+ if (this.getFrame() != null)
+ this.getFrame().addPropertyChangeListener(this.propertyListener);
+ }
+
+ /**
+ * Uninstalls the necessary listeners.
+ */
+ private void uninstallListeners() {
+ if (this.window != null) {
+ this.window.removeWindowListener(this.windowListener);
+ this.windowListener = null;
+ this.window
+ .removePropertyChangeListener(this.propertyChangeListener);
+ this.propertyChangeListener = null;
+ }
+
+ // Fix for defect 109 - memory leak on skin change.
+ this.rootPane.removePropertyChangeListener(this.propertyListener);
+ if (this.getFrame() != null)
+ this.getFrame().removePropertyChangeListener(this.propertyListener);
+ this.propertyListener = null;
+
+ }
+
+ /**
+ * Returns the <code>JRootPane</code> this was created for.
+ */
+ @Override
+ public JRootPane getRootPane() {
+ return this.rootPane;
+ }
+
+ /**
+ * Returns the decoration style of the <code>JRootPane</code>.
+ *
+ * @return Decoration style of the <code>JRootPane</code>.
+ */
+ protected int getWindowDecorationStyle() {
+ return this.getRootPane().getWindowDecorationStyle();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Component#addNotify()
+ */
+ @Override
+ public void addNotify() {
+ super.addNotify();
+
+ this.uninstallListeners();
+
+ this.window = SwingUtilities.getWindowAncestor(this);
+ if (this.window != null) {
+ this.setActive(this.window.isActive());
+ if (this.window instanceof Frame) {
+ this.setState(((Frame) this.window).getExtendedState());
+ } else {
+ this.setState(0);
+ }
+ if (this.getComponentCount() == 0) {
+ // fix for issue 385 - add the sub-components uninstalled
+ // in the removeNotify. This happens when a decorated
+ // dialog has been disposed and then reshown.
+ this.installSubcomponents();
+ }
+ this.installListeners();
+ }
+ this.setToolTipText(this.getTitle());
+ this.updateAppIcon();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Component#removeNotify()
+ */
+ @Override
+ public void removeNotify() {
+ super.removeNotify();
+
+ this.uninstall();
+ this.window = null;
+ }
+
+ /**
+ * Adds any sub-Components contained in the <code>SubstanceTitlePane</code>.
+ */
+ private void installSubcomponents() {
+ int decorationStyle = this.getWindowDecorationStyle();
+ if (decorationStyle == JRootPane.FRAME) {
+ this.createActions();
+ this.menuBar = this.createMenuBar();
+ if (this.menuBar != null) {
+ this.add(this.menuBar);
+ }
+ this.createButtons();
+ this.add(this.minimizeButton);
+ this.add(this.toggleButton);
+ this.add(this.closeButton);
+
+ this.heapStatusPanel = new HeapStatusPanel();
+ this.markExtraComponent(this.heapStatusPanel,
+ ExtraComponentKind.TRAILING);
+ this.add(this.heapStatusPanel);
+ boolean isHeapStatusPanelShowing = SubstanceWidgetManager
+ .getInstance().isAllowed(rootPane,
+ SubstanceWidgetType.TITLE_PANE_HEAP_STATUS);
+ this.heapStatusPanel.setVisible(isHeapStatusPanelShowing);
+ this.heapStatusPanel.setPreferredSize(new Dimension(80, this
+ .getPreferredSize().height));
+ this.heapStatusPanel.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(rootPane).getString(
+ "Tooltip.heapStatusPanel"));
+ this.heapStatusPanel.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ System.gc();
+ }
+ });
+
+ HeapStatusThread.registerPanel(this.heapStatusPanel);
+ } else {
+ if ((decorationStyle == JRootPane.PLAIN_DIALOG)
+ || (decorationStyle == JRootPane.INFORMATION_DIALOG)
+ || (decorationStyle == JRootPane.ERROR_DIALOG)
+ || (decorationStyle == JRootPane.COLOR_CHOOSER_DIALOG)
+ || (decorationStyle == JRootPane.FILE_CHOOSER_DIALOG)
+ || (decorationStyle == JRootPane.QUESTION_DIALOG)
+ || (decorationStyle == JRootPane.WARNING_DIALOG)) {
+ this.createActions();
+ this.menuBar = this.createMenuBar();
+ if (this.menuBar != null) {
+ this.add(this.menuBar);
+ }
+ this.createButtons();
+ this.add(this.closeButton);
+ }
+ }
+ }
+
+ /**
+ * Installs the fonts and necessary properties.
+ */
+ private void installDefaults() {
+ this.setFont(UIManager.getFont("InternalFrame.titleFont", this
+ .getLocale()));
+ }
+
+ /**
+ * Returns the <code>JMenuBar</code> displaying the appropriate system menu
+ * items.
+ *
+ * @return <code>JMenuBar</code> displaying the appropriate system menu
+ * items.
+ */
+ protected JMenuBar createMenuBar() {
+ this.menuBar = new SubstanceMenuBar();
+ this.menuBar.setFocusable(false);
+ this.menuBar.setBorderPainted(true);
+ this.menuBar.add(this.createMenu());
+ this.menuBar.setOpaque(false);
+ // support for RTL
+ this.menuBar.applyComponentOrientation(this.rootPane
+ .getComponentOrientation());
+
+ this.markExtraComponent(this.menuBar, ExtraComponentKind.LEADING);
+
+ return this.menuBar;
+ }
+
+ /**
+ * Create the <code>Action</code>s that get associated with the buttons and
+ * menu items.
+ */
+ private void createActions() {
+ this.closeAction = new CloseAction();
+ if (this.getWindowDecorationStyle() == JRootPane.FRAME) {
+ this.iconifyAction = new IconifyAction();
+ this.restoreAction = new RestoreAction();
+ this.maximizeAction = new MaximizeAction();
+ }
+ }
+
+ /**
+ * Returns the <code>JMenu</code> displaying the appropriate menu items for
+ * manipulating the Frame.
+ *
+ * @return <code>JMenu</code> displaying the appropriate menu items for
+ * manipulating the Frame.
+ */
+ private JMenu createMenu() {
+ JMenu menu = new JMenu("");
+ menu.setOpaque(false);
+ menu.setBackground(null);
+ //if (this.getWindowDecorationStyle() == JRootPane.FRAME) {
+ this.addMenuItems(menu);
+ //}
+ menu.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() > 1) {
+ closeAction.actionPerformed(new ActionEvent(e.getSource(),
+ ActionEvent.ACTION_PERFORMED, null,
+ EventQueue.getMostRecentEventTime(), e.getModifiers()));
+ }
+ }
+ });
+ return menu;
+ }
+
+ /**
+ * Adds the necessary <code>JMenuItem</code>s to the specified menu.
+ *
+ * @param menu
+ * Menu.
+ */
+ private void addMenuItems(JMenu menu) {
+ if (this.getWindowDecorationStyle() == JRootPane.FRAME) {
+ menu.add(this.restoreAction);
+
+ menu.add(this.iconifyAction);
+
+ if (Toolkit.getDefaultToolkit().isFrameStateSupported(
+ Frame.MAXIMIZED_BOTH)) {
+ menu.add(this.maximizeAction);
+ }
+
+ if (SubstanceCoreUtilities.toShowExtraWidgets(rootPane)) {
+ menu.addSeparator();
+ JMenu skinMenu = new JMenu(SubstanceCoreUtilities
+ .getResourceBundle(rootPane).getString("SystemMenu.skins"));
+ Map<String, SkinInfo> allSkins = SubstanceLookAndFeel.getAllSkins();
+ for (Map.Entry<String, SkinInfo> skinEntry : allSkins.entrySet()) {
+ final String skinClassName = skinEntry.getValue()
+ .getClassName();
+ JMenuItem jmiSkin = new JMenuItem(skinEntry.getKey());
+ jmiSkin.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ SubstanceLookAndFeel.setSkin(skinClassName);
+ }
+ });
+ }
+ });
+
+ skinMenu.add(jmiSkin);
+ }
+ menu.add(skinMenu);
+ }
+
+ menu.addSeparator();
+ }
+
+ menu.add(this.closeAction);
+ }
+
+ /**
+ * Returns a <code>JButton</code> appropriate for placement on the
+ * TitlePane.
+ *
+ * @return Title button.
+ */
+ private JButton createTitleButton() {
+ JButton button = new SubstanceTitleButton();
+
+ button.setFocusPainted(false);
+ button.setFocusable(false);
+ button.setOpaque(true);
+
+ this.markExtraComponent(button, ExtraComponentKind.TRAILING);
+
+ return button;
+ }
+
+ /**
+ * Creates the Buttons that will be placed on the TitlePane.
+ */
+ private void createButtons() {
+ this.closeButton = this.createTitleButton();
+ this.closeButton.setAction(this.closeAction);
+ this.closeButton.setText(null);
+ this.closeButton.setBorder(null);
+ // this.closeButton.setToolTipText(SubstanceLookAndFeel
+ // .getLabelBundle().getString(
+ // "SystemMenu.close"));
+
+ Icon closeIcon = new TransitionAwareIcon(closeButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ return SubstanceIconFactory
+ .getTitlePaneIcon(
+ SubstanceIconFactory.IconKind.CLOSE,
+ scheme,
+ SubstanceCoreUtilities
+ .getSkin(rootPane)
+ .getBackgroundColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE));
+ }
+ }, "substance.titlePane.closeIcon");
+ this.closeButton.setIcon(closeIcon);
+
+ this.closeButton.setFocusable(false);
+ this.closeButton.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY,
+ Boolean.TRUE);
+
+ this.closeButton.putClientProperty(
+ SubstanceButtonUI.IS_TITLE_CLOSE_BUTTON, Boolean.TRUE);
+
+ if (this.getWindowDecorationStyle() == JRootPane.FRAME) {
+ this.minimizeButton = this.createTitleButton();
+ this.minimizeButton.setAction(this.iconifyAction);
+ this.minimizeButton.setText(null);
+ this.minimizeButton.setBorder(null);
+
+ Icon minIcon = new TransitionAwareIcon(this.minimizeButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ return SubstanceIconFactory
+ .getTitlePaneIcon(
+ SubstanceIconFactory.IconKind.MINIMIZE,
+ scheme,
+ SubstanceCoreUtilities
+ .getSkin(rootPane)
+ .getBackgroundColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE));
+ }
+ }, "substance.titlePane.minIcon");
+ this.minimizeButton.setIcon(minIcon);
+
+ this.minimizeButton.setFocusable(false);
+ this.minimizeButton.putClientProperty(
+ SubstanceLookAndFeel.FLAT_PROPERTY, Boolean.TRUE);
+ this.minimizeButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(rootPane)
+ .getString("SystemMenu.iconify"));
+
+ this.toggleButton = this.createTitleButton();
+ this.toggleButton.setAction(this.restoreAction);
+ this.toggleButton.setBorder(null);
+ this.toggleButton.setText(null);
+
+ Icon maxIcon = new TransitionAwareIcon(this.toggleButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ return SubstanceIconFactory
+ .getTitlePaneIcon(
+ SubstanceIconFactory.IconKind.MAXIMIZE,
+ scheme,
+ SubstanceCoreUtilities
+ .getSkin(rootPane)
+ .getBackgroundColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE));
+ }
+ }, "substance.titlePane.maxIcon");
+ this.toggleButton.setIcon(maxIcon);
+
+ this.toggleButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(rootPane).getString(
+ "SystemMenu.maximize"));
+ this.toggleButton.setFocusable(false);
+ this.toggleButton.putClientProperty(
+ SubstanceLookAndFeel.FLAT_PROPERTY, Boolean.TRUE);
+
+ }
+ syncCloseButtonTooltip();
+ }
+
+ /**
+ * Returns the <code>LayoutManager</code> that should be installed on the
+ * <code>SubstanceTitlePane</code>.
+ *
+ * @return Layout manager.
+ */
+ protected LayoutManager createLayout() {
+ return new TitlePaneLayout();
+ }
+
+ /**
+ * Updates state dependant upon the Window's active state.
+ *
+ * @param isActive
+ * if <code>true</code>, the window is in active state.
+ */
+ void setActive(boolean isActive) {
+ if (getRootPane() != null) {
+ this.getRootPane().repaint();
+ }
+ }
+
+ /**
+ * Sets the state of the Window.
+ *
+ * @param state
+ * Window state.
+ */
+ private void setState(int state) {
+ this.setState(state, false);
+ }
+
+ /**
+ * Sets the state of the window. If <code>updateRegardless</code> is true
+ * and the state has not changed, this will update anyway.
+ *
+ * @param state
+ * Window state.
+ * @param updateRegardless
+ * if <code>true</code>, the update is done in any case.
+ */
+ private void setState(int state, boolean updateRegardless) {
+ Window w = this.getWindow();
+
+ if ((w != null) && (this.getWindowDecorationStyle() == JRootPane.FRAME)) {
+ if ((this.state == state) && !updateRegardless) {
+ return;
+ }
+ Frame frame = this.getFrame();
+
+ if (frame != null) {
+ final JRootPane rootPane = this.getRootPane();
+
+ if (((state & Frame.MAXIMIZED_BOTH) != 0)
+ && ((rootPane.getBorder() == null) || (rootPane
+ .getBorder() instanceof UIResource))
+ && frame.isShowing()) {
+ rootPane.setBorder(null);
+ } else {
+ if ((state & Frame.MAXIMIZED_BOTH) == 0) {
+ // This is a croak, if state becomes bound, this can
+ // be nuked.
+ this.rootPaneUI.installBorder(rootPane);
+ }
+ }
+ if (frame.isResizable()) {
+ if ((state & Frame.MAXIMIZED_BOTH) != 0) {
+ Icon restoreIcon = new TransitionAwareIcon(
+ this.toggleButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ return SubstanceIconFactory
+ .getTitlePaneIcon(
+ SubstanceIconFactory.IconKind.RESTORE,
+ scheme,
+ SubstanceCoreUtilities
+ .getSkin(
+ rootPane)
+ .getBackgroundColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE));
+ }
+ }, "substance.titlePane.restoreIcon");
+ this
+ .updateToggleButton(this.restoreAction,
+ restoreIcon);
+ this.toggleButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(rootPane).getString(
+ "SystemMenu.restore"));
+ this.maximizeAction.setEnabled(false);
+ this.restoreAction.setEnabled(true);
+ } else {
+ Icon maxIcon = new TransitionAwareIcon(
+ this.toggleButton,
+ new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(
+ SubstanceColorScheme scheme) {
+ return SubstanceIconFactory
+ .getTitlePaneIcon(
+ SubstanceIconFactory.IconKind.MAXIMIZE,
+ scheme,
+ SubstanceCoreUtilities
+ .getSkin(
+ rootPane)
+ .getBackgroundColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE));
+ }
+ }, "substance.titlePane.maxIcon");
+ this.updateToggleButton(this.maximizeAction, maxIcon);
+ this.toggleButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(rootPane).getString(
+ "SystemMenu.maximize"));
+ this.maximizeAction.setEnabled(true);
+ this.restoreAction.setEnabled(false);
+ }
+ if ((this.toggleButton.getParent() == null)
+ || (this.minimizeButton.getParent() == null)) {
+ this.add(this.toggleButton);
+ this.add(this.minimizeButton);
+ this.revalidate();
+ this.repaint();
+ }
+ this.toggleButton.setText(null);
+ } else {
+ this.maximizeAction.setEnabled(false);
+ this.restoreAction.setEnabled(false);
+ if (this.toggleButton.getParent() != null) {
+ this.remove(this.toggleButton);
+ this.revalidate();
+ this.repaint();
+ }
+ }
+ } else {
+ // Not contained in a Frame
+ this.maximizeAction.setEnabled(false);
+ this.restoreAction.setEnabled(false);
+ this.iconifyAction.setEnabled(false);
+ this.remove(this.toggleButton);
+ this.remove(this.minimizeButton);
+ this.revalidate();
+ this.repaint();
+ }
+ this.closeAction.setEnabled(true);
+ this.state = state;
+ }
+ }
+
+ /**
+ * Updates the toggle button to contain the Icon <code>icon</code>, and
+ * Action <code>action</code>.
+ *
+ * @param action
+ * Action.
+ * @param icon
+ * Icon.
+ */
+ private void updateToggleButton(Action action, Icon icon) {
+ this.toggleButton.setAction(action);
+ this.toggleButton.setIcon(icon);
+ this.toggleButton.setText(null);
+ }
+
+ /**
+ * Returns the Frame rendering in. This will return null if the
+ * <code>JRootPane</code> is not contained in a <code>Frame</code>.
+ *
+ * @return Frame.
+ */
+ private Frame getFrame() {
+ Window window = this.getWindow();
+
+ if (window instanceof Frame) {
+ return (Frame) window;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the <code>Window</code> the <code>JRootPane</code> is contained
+ * in. This will return null if there is no parent ancestor of the
+ * <code>JRootPane</code>.
+ *
+ * @return Window.
+ */
+ private Window getWindow() {
+ return this.window;
+ }
+
+ /**
+ * Returns the String to display as the title.
+ *
+ * @return Display title.
+ */
+ private String getTitle() {
+ Window w = this.getWindow();
+
+ if (w instanceof Frame) {
+ return ((Frame) w).getTitle();
+ }
+ if (w instanceof Dialog) {
+ return ((Dialog) w).getTitle();
+ }
+ return null;
+ }
+
+ public DecorationAreaType getThisDecorationType() {
+ DecorationAreaType dat = SubstanceLookAndFeel.getDecorationType(this);
+ if (dat == DecorationAreaType.PRIMARY_TITLE_PANE) {
+ return SubstanceCoreUtilities.isPaintRootPaneActivated(getRootPane())
+ ? DecorationAreaType.PRIMARY_TITLE_PANE
+ : DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE;
+ } else if (dat == DecorationAreaType.SECONDARY_TITLE_PANE) {
+ return SubstanceCoreUtilities.isPaintRootPaneActivated(getRootPane())
+ ? DecorationAreaType.SECONDARY_TITLE_PANE
+ : DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE;
+ } else {
+ return dat;
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
+ */
+ @Override
+ public void paintComponent(Graphics g) {
+ // long start = System.nanoTime();
+ // As state isn't bound, we need a convenience place to check
+ // if it has changed. Changing the state typically changes the
+ if (this.getFrame() != null) {
+ this.setState(this.getFrame().getExtendedState());
+ }
+ final JRootPane rootPane = this.getRootPane();
+ Window window = this.getWindow();
+ boolean leftToRight = (window == null) ? rootPane
+ .getComponentOrientation().isLeftToRight() : window
+ .getComponentOrientation().isLeftToRight();
+ int width = this.getWidth();
+ int height = this.getHeight();
+
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(rootPane);
+ if (skin == null) {
+ SubstanceCoreUtilities
+ .traceSubstanceApiUsage(this,
+ "Substance delegate used when Substance is not the current LAF");
+ }
+ DecorationAreaType decorationType = getThisDecorationType();
+ SubstanceColorScheme scheme = skin
+ .getEnabledColorScheme(decorationType);
+
+ int xOffset = 0;
+ String theTitle = this.getTitle();
+
+ if (theTitle != null) {
+ FontMetrics fm = rootPane.getFontMetrics(g.getFont());
+ Rectangle titleTextRect = this.getTitleTextRectangle(fm.stringWidth(theTitle));
+ int titleWidth = titleTextRect.width;
+ String clippedTitle = SubstanceCoreUtilities.clipString(fm,
+ titleWidth, theTitle);
+ // show tooltip with full title only if necessary
+ if (theTitle.equals(clippedTitle)) {
+ this.setToolTipText(null);
+ } else {
+ this.setToolTipText(theTitle);
+ }
+ theTitle = clippedTitle;
+ if (leftToRight)
+ xOffset = titleTextRect.x;
+ else
+ xOffset = titleTextRect.x + titleTextRect.width
+ - fm.stringWidth(theTitle);
+ }
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ Font font = SubstanceLookAndFeel.getFontPolicy().getFontSet(
+ "Substance", null).getWindowTitleFont();
+ graphics.setFont(font);
+
+ BackgroundPaintingUtils
+ .update(graphics, SubstanceTitlePane.this, false, decorationType);
+ // DecorationPainterUtils.paintDecorationBackground(graphics,
+ // SubstanceTitlePane.this, false);
+
+ // draw the title (if needed)
+ if (theTitle != null) {
+ FontMetrics fm = rootPane.getFontMetrics(graphics.getFont());
+ int yOffset = ((height - fm.getHeight()) / 2) + fm.getAscent();
+
+ SubstanceTextUtilities.paintTextWithDropShadow(this, graphics,
+ SubstanceColorUtilities.getForegroundColor(scheme),
+ theTitle, width, height, xOffset, yOffset);
+ }
+
+ GhostPaintingUtils.paintGhostImages(this, graphics);
+
+ // long end = System.nanoTime();
+ // System.out.println(end - start);
+ graphics.dispose();
+ }
+
+ /**
+ * Computes the rectangle of the title text. This method looks at all the
+ * children components of the title pane, grouping them by leading and
+ * trailing (based on {@link #EXTRA_COMPONENT_KIND} client property). The
+ * title text rectangle is the space between the leading group and the
+ * trailing group.
+ *
+ * @return Rectangle of the title text.
+ * @throws IllegalStateException
+ * If at least one child component of this title pane is not
+ * marked with the {@link #EXTRA_COMPONENT_KIND} client
+ * property.
+ * @see #markExtraComponent(JComponent, ExtraComponentKind)
+ * @see #EXTRA_COMPONENT_KIND
+ */
+ protected Rectangle getTitleTextRectangle(int preferredWidth) {
+ JRootPane rootPane = this.getRootPane();
+ Window window = this.getWindow();
+ boolean leftToRight = (window == null) ? rootPane
+ .getComponentOrientation().isLeftToRight() : window
+ .getComponentOrientation().isLeftToRight();
+
+ if (leftToRight) {
+ int maxLeadingX = 0;
+ int minTrailingX = this.getWidth();
+ int maxMiddlingX = maxLeadingX;
+ int minMiddlingX = minTrailingX;
+
+ for (int i = 0; i < this.getComponentCount(); i++) {
+ Component child = this.getComponent(i);
+ if (!child.isVisible())
+ continue;
+ if (child instanceof JComponent) {
+ ExtraComponentKind kind = (ExtraComponentKind) ((JComponent) child)
+ .getClientProperty(EXTRA_COMPONENT_KIND);
+ if (kind == null) {
+ throw new IllegalStateException("Title pane child "
+ + child.getClass().getName()
+ + " is not marked as leading or trailing");
+ }
+ switch (kind) {
+ case LEADING:
+ int cx = child.getX() + child.getWidth();
+ if (cx > maxLeadingX)
+ maxLeadingX = cx;
+ break;
+ case MIDDLING:
+ cx = child.getX();
+ if (cx < minMiddlingX)
+ minMiddlingX = cx;
+ cx = child.getX() + child.getWidth();
+ if (cx > maxMiddlingX)
+ maxMiddlingX = cx;
+ break;
+ case TRAILING:
+ cx = child.getX();
+ if (cx < minTrailingX)
+ minTrailingX = cx;
+ break;
+ }
+ }
+ }
+ minMiddlingX = Math.max(minMiddlingX, maxLeadingX);
+ maxMiddlingX = Math.min(maxMiddlingX, minTrailingX);
+ int leadWidth = minMiddlingX - maxLeadingX - 15;
+ int trailWidth = minTrailingX - maxMiddlingX - 15;
+ int start, end;
+ if (maxMiddlingX <= minMiddlingX) {
+ start = maxLeadingX + 10;
+ end = minTrailingX - 5;
+ } else if ((preferredWidth > leadWidth)
+ && ((trailWidth > leadWidth) || (preferredWidth <= trailWidth)))
+ {
+ start = maxMiddlingX + 10;
+ end = minTrailingX - 5;
+ } else {
+ start = maxLeadingX + 10;
+ end = minMiddlingX - 5;
+ }
+ return new Rectangle(start, 0, end - start, this.getHeight());
+ } else {
+ int minLeadingX = this.getWidth();
+ int maxTrailingX = 0;
+ int maxMiddlingX = maxTrailingX;
+ int minMiddlingX = minLeadingX;
+
+
+ for (int i = 0; i < this.getComponentCount(); i++) {
+ Component child = this.getComponent(i);
+ if (!child.isVisible())
+ continue;
+ if (child instanceof JComponent) {
+ ExtraComponentKind kind = (ExtraComponentKind) ((JComponent) child)
+ .getClientProperty(EXTRA_COMPONENT_KIND);
+ if (kind == null) {
+ throw new IllegalStateException("Title pane child "
+ + child.getClass().getName()
+ + " is not marked as leading or trailing");
+ }
+
+ switch (kind) {
+ case LEADING:
+ int cx = child.getX();
+ if (cx < minLeadingX)
+ minLeadingX = cx;
+ break;
+ case MIDDLING:
+ cx = child.getX();
+ if (cx < minMiddlingX)
+ minMiddlingX = cx;
+ cx = child.getX() + child.getWidth();
+ if (cx > maxMiddlingX)
+ maxMiddlingX = cx;
+ break;
+ case TRAILING:
+ cx = child.getX() + child.getWidth();
+ if (cx > maxTrailingX)
+ maxTrailingX = cx;
+ break;
+ }
+ }
+ }
+
+ minMiddlingX = Math.max(minMiddlingX, maxTrailingX);
+ maxMiddlingX = Math.min(maxMiddlingX, minLeadingX);
+ int leadWidth = minLeadingX - maxMiddlingX- 15;
+ int trailWidth = minMiddlingX - maxTrailingX - 15;
+ int start, end;
+ if (maxMiddlingX <= minMiddlingX) {
+ start = maxTrailingX + 5;
+ end = minLeadingX - 10;
+ } else if ((preferredWidth > leadWidth)
+ && ((trailWidth > leadWidth) || (preferredWidth <= trailWidth)))
+ {
+ start = maxTrailingX+ 10;
+ end = minMiddlingX - 5;
+ } else {
+ start = maxMiddlingX + 5;
+ end = minLeadingX - 10;
+ }
+
+ return new Rectangle(start, 0, end - start, this.getHeight());
+ }
+ }
+
+ /**
+ * Actions used to <code>close</code> the <code>Window</code>.
+ */
+ private class CloseAction extends AbstractAction {
+ /**
+ * Creates a new close action.
+ */
+ public CloseAction() {
+ super(SubstanceCoreUtilities.getResourceBundle(rootPane).getString(
+ "SystemMenu.close"), SubstanceImageCreator.getCloseIcon(
+ SubstanceCoreUtilities.getSkin(rootPane)
+ .getActiveColorScheme(getThisDecorationType()),
+ SubstanceCoreUtilities.getSkin(rootPane)
+ .getBackgroundColorScheme(getThisDecorationType())));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Window window = SubstanceTitlePane.this.getWindow();
+
+ if (window != null) {
+ window.dispatchEvent(new WindowEvent(window,
+ WindowEvent.WINDOW_CLOSING));
+ }
+ }
+ }
+
+ /**
+ * Actions used to <code>iconfiy</code> the <code>Frame</code>.
+ */
+ private class IconifyAction extends AbstractAction {
+ /**
+ * Creates a new iconify action.
+ */
+ public IconifyAction() {
+ super(
+ SubstanceCoreUtilities.getResourceBundle(rootPane)
+ .getString("SystemMenu.iconify"),
+ SubstanceImageCreator
+ .getMinimizeIcon(
+ SubstanceCoreUtilities
+ .getSkin(rootPane)
+ .getActiveColorScheme(
+ getThisDecorationType()),
+ SubstanceCoreUtilities
+ .getSkin(rootPane)
+ .getBackgroundColorScheme(
+ getThisDecorationType())));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Frame frame = SubstanceTitlePane.this.getFrame();
+ if (frame != null) {
+ frame.setExtendedState(SubstanceTitlePane.this.state
+ | Frame.ICONIFIED);
+ }
+ }
+ }
+
+ /**
+ * Actions used to <code>restore</code> the <code>Frame</code>.
+ */
+ private class RestoreAction extends AbstractAction {
+ /**
+ * Creates a new restore action.
+ */
+ public RestoreAction() {
+ super(
+ SubstanceCoreUtilities.getResourceBundle(rootPane)
+ .getString("SystemMenu.restore"),
+ SubstanceImageCreator
+ .getRestoreIcon(
+ SubstanceCoreUtilities
+ .getSkin(rootPane)
+ .getActiveColorScheme(
+ getThisDecorationType()),
+ SubstanceCoreUtilities
+ .getSkin(rootPane)
+ .getBackgroundColorScheme(
+ getThisDecorationType())));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Frame frame = SubstanceTitlePane.this.getFrame();
+
+ if (frame == null) {
+ return;
+ }
+
+ if ((SubstanceTitlePane.this.state & Frame.ICONIFIED) != 0) {
+ frame.setExtendedState(SubstanceTitlePane.this.state
+ & ~Frame.ICONIFIED);
+ } else {
+ frame.setExtendedState(SubstanceTitlePane.this.state
+ & ~Frame.MAXIMIZED_BOTH);
+ }
+ }
+ }
+
+ /**
+ * Actions used to <code>restore</code> the <code>Frame</code>.
+ */
+ private class MaximizeAction extends AbstractAction {
+ /**
+ * Creates a new maximize action.
+ */
+ public MaximizeAction() {
+ super(
+ SubstanceCoreUtilities.getResourceBundle(rootPane)
+ .getString("SystemMenu.maximize"),
+ SubstanceImageCreator
+ .getMaximizeIcon(
+ SubstanceCoreUtilities
+ .getSkin(rootPane)
+ .getActiveColorScheme(
+ getThisDecorationType()),
+ SubstanceCoreUtilities
+ .getSkin(rootPane)
+ .getEnabledColorScheme(
+ getThisDecorationType())));
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Frame frame = SubstanceTitlePane.this.getFrame();
+ if (frame != null) {
+ if (frame instanceof JFrame) {
+ SubstanceRootPaneUI rpUI = (SubstanceRootPaneUI) ((JFrame) frame)
+ .getRootPane().getUI();
+ rpUI.setMaximized();
+ }
+ frame.setExtendedState(SubstanceTitlePane.this.state
+ | Frame.MAXIMIZED_BOTH);
+ }
+ }
+ }
+
+ /**
+ * Class responsible for drawing the system menu. Looks up the image to draw
+ * from the Frame associated with the <code>JRootPane</code>.
+ */
+ public class SubstanceMenuBar extends JMenuBar {
+ @Override
+ public void paint(Graphics g) {
+ if (appIcon != null) {
+ g.drawImage(appIcon, 0, 0, null);
+ } else {
+ Icon icon = UIManager.getIcon("InternalFrame.icon");
+ if (icon != null) {
+ icon.paintIcon(this, g, 0, 0);
+ }
+ }
+ }
+
+ @Override
+ public Dimension getMinimumSize() {
+ return this.getPreferredSize();
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size = super.getPreferredSize();
+
+ int iSize = SubstanceSizeUtils.getTitlePaneIconSize();
+ return new Dimension(Math.max(iSize, size.width), Math.max(
+ size.height, iSize));
+ }
+ }
+
+ /**
+ * Layout manager for the title pane.
+ *
+ * @author Kirill Graphics
+ */
+ protected class TitlePaneLayout implements LayoutManager {
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
+ * java.awt.Component)
+ */
+ @Override
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
+ */
+ @Override
+ public void removeLayoutComponent(Component c) {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container c) {
+ int height = this.computeHeight();
+ //noinspection SuspiciousNameCombination
+ return new Dimension(height, height);
+ }
+
+ /**
+ * Computes title pane height.
+ *
+ * @return Title pane height.
+ */
+ private int computeHeight() {
+ FontMetrics fm = SubstanceTitlePane.this.rootPane
+ .getFontMetrics(SubstanceTitlePane.this.getFont());
+ int fontHeight = fm.getHeight();
+ fontHeight += 7;
+ int iconHeight = SubstanceSizeUtils.getTitlePaneIconSize();
+
+ return Math.max(fontHeight, iconHeight);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container c) {
+ return this.preferredLayoutSize(c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
+ */
+ @Override
+ public void layoutContainer(Container c) {
+ boolean leftToRight = (SubstanceTitlePane.this.window == null) ? SubstanceTitlePane.this
+ .getRootPane().getComponentOrientation().isLeftToRight()
+ : SubstanceTitlePane.this.window.getComponentOrientation()
+ .isLeftToRight();
+
+ int w = SubstanceTitlePane.this.getWidth();
+ int x;
+ int y;
+ int spacing;
+ int buttonHeight;
+ int buttonWidth;
+
+ if ((SubstanceTitlePane.this.closeButton != null)
+ && (SubstanceTitlePane.this.closeButton.getIcon() != null)) {
+ buttonHeight = SubstanceTitlePane.this.closeButton.getIcon()
+ .getIconHeight();
+ buttonWidth = SubstanceTitlePane.this.closeButton.getIcon()
+ .getIconWidth();
+ } else {
+ buttonHeight = SubstanceSizeUtils.getTitlePaneIconSize();
+ buttonWidth = SubstanceSizeUtils.getTitlePaneIconSize();
+ }
+
+ y = (getHeight() - buttonHeight) / 2;
+
+ // assumes all buttons have the same dimensions
+ // these dimensions include the borders
+
+ spacing = 5;
+ x = leftToRight ? spacing : w - buttonWidth - spacing;
+ if (SubstanceTitlePane.this.menuBar != null) {
+ SubstanceTitlePane.this.menuBar.setBounds(x, y, buttonWidth,
+ buttonHeight);
+ // System.out.println(menuBar.getBounds());
+ }
+
+ x = leftToRight ? w : 0;
+ spacing = 3;
+ x += leftToRight ? -spacing - buttonWidth : spacing;
+ if (SubstanceTitlePane.this.closeButton != null) {
+ SubstanceTitlePane.this.closeButton.setBounds(x, y,
+ buttonWidth, buttonHeight);
+ }
+
+ if (!leftToRight)
+ x += buttonWidth;
+
+ if (SubstanceTitlePane.this.getWindowDecorationStyle() == JRootPane.FRAME) {
+ if (Toolkit.getDefaultToolkit().isFrameStateSupported(
+ Frame.MAXIMIZED_BOTH)) {
+ if (SubstanceTitlePane.this.toggleButton.getParent() != null) {
+ spacing = 10;
+ x += leftToRight ? -spacing - buttonWidth : spacing;
+ SubstanceTitlePane.this.toggleButton.setBounds(x, y,
+ buttonWidth, buttonHeight);
+ if (!leftToRight) {
+ x += buttonWidth;
+ }
+ }
+ }
+
+ if ((SubstanceTitlePane.this.minimizeButton != null)
+ && (SubstanceTitlePane.this.minimizeButton.getParent() != null)) {
+ spacing = 2;
+ x += leftToRight ? -spacing - buttonWidth : spacing;
+ SubstanceTitlePane.this.minimizeButton.setBounds(x, y,
+ buttonWidth, buttonHeight);
+ if (!leftToRight) {
+ x += buttonWidth;
+ }
+ }
+
+ if ((SubstanceTitlePane.this.heapStatusPanel != null)
+ && SubstanceTitlePane.this.heapStatusPanel.isVisible()) {
+ spacing = 5;
+ x += leftToRight ? (-spacing - SubstanceTitlePane.this.heapStatusPanel
+ .getPreferredWidth())
+ : spacing;
+ SubstanceTitlePane.this.heapStatusPanel.setBounds(x, 1,
+ SubstanceTitlePane.this.heapStatusPanel
+ .getPreferredWidth(),
+ SubstanceTitlePane.this.getHeight() - 3);
+ }
+ }
+ // buttonsWidth = leftToRight ? w - x : x;
+ }
+
+ }
+
+ /**
+ * PropertyChangeListener installed on the Window. Updates the necessary
+ * state as the state of the Window changes.
+ */
+ private class PropertyChangeHandler implements PropertyChangeListener {
+ @Override
+ public void propertyChange(PropertyChangeEvent pce) {
+ String name = pce.getPropertyName();
+
+ // Frame.state isn't currently bound.
+ if ("resizable".equals(name) || "state".equals(name)) {
+ Frame frame = SubstanceTitlePane.this.getFrame();
+
+ if (frame != null) {
+ SubstanceTitlePane.this.setState(frame.getExtendedState(),
+ true);
+ }
+ if ("resizable".equals(name)) {
+ SubstanceTitlePane.this.getRootPane().repaint();
+ }
+ } else {
+ if ("title".equals(name)) {
+ SubstanceTitlePane.this.repaint();
+ SubstanceTitlePane.this.setToolTipText((String) pce
+ .getNewValue());
+ } else if ("componentOrientation".equals(name)) {
+ revalidate();
+ repaint();
+ } else if ("iconImage".equals(name)) {
+ updateAppIcon();
+ revalidate();
+ repaint();
+ }
+ }
+ }
+ }
+
+ /**
+ * WindowListener installed on the Window, updates the state as necessary.
+ */
+ private class WindowHandler extends WindowAdapter {
+ @Override
+ public void windowActivated(WindowEvent ev) {
+ SubstanceTitlePane.this.setActive(true);
+ }
+
+ @Override
+ public void windowDeactivated(WindowEvent ev) {
+ SubstanceTitlePane.this.setActive(false);
+ }
+ }
+
+ /**
+ * Sets location for heap status logfile.
+ *
+ * @param heapStatusLogfileName
+ * Logfile for the heap status panel.
+ */
+ public static void setHeapStatusLogfileName(String heapStatusLogfileName) {
+ SubstanceTitlePane.heapStatusLogfileName = heapStatusLogfileName;
+ }
+
+ /**
+ * Synchronizes the tooltip of the close button.
+ */
+ protected void syncCloseButtonTooltip() {
+ if (SubstanceCoreUtilities.isRootPaneModified(this.getRootPane())) {
+ this.closeButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(rootPane).getString("SystemMenu.close")
+ + " ["
+ + SubstanceCoreUtilities.getResourceBundle(rootPane)
+ .getString("Tooltip.contentsNotSaved") + "]");
+ } else {
+ this.closeButton.setToolTipText(SubstanceCoreUtilities
+ .getResourceBundle(rootPane).getString("SystemMenu.close"));
+ }
+ this.closeButton.repaint();
+ }
+
+ /**
+ * Marks the specified child component with the specified extra component
+ * kind.
+ *
+ * @param comp
+ * Child component.
+ * @param kind
+ * Extra kind.
+ * @see #getTitleTextRectangle(int)
+ * @see #EXTRA_COMPONENT_KIND
+ */
+ protected void markExtraComponent(JComponent comp, ExtraComponentKind kind) {
+ comp.putClientProperty(EXTRA_COMPONENT_KIND, kind);
+ }
+
+ /**
+ * Updates the application icon.
+ */
+ private void updateAppIcon() {
+ Window window = getWindow();
+ if (window == null) {
+ this.appIcon = null;
+ return;
+ }
+ this.appIcon = null;
+ while (window != null && appIcon == null) {
+ java.util.List<Image> icons = window.getIconImages();
+
+ if (icons.size() == 0) {
+ window = window.getOwner();
+ } else {
+ int prefSize = SubstanceSizeUtils.getTitlePaneIconSize();
+ this.appIcon = SubstanceCoreUtilities.getScaledIconImage(icons,
+ prefSize, prefSize);
+ }
+ }
+ }
+
+ public AbstractButton getCloseButton() {
+ return this.closeButton;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceWidgetManager.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceWidgetManager.java
new file mode 100644
index 0000000..e670a04
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceWidgetManager.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.util.*;
+
+import javax.swing.JRootPane;
+
+import org.pushingpixels.substance.api.SubstanceConstants.SubstanceWidgetType;
+
+public class SubstanceWidgetManager {
+ private static SubstanceWidgetManager instance;
+
+ private Set<SubstanceWidgetType> globalAllowed;
+
+ private Set<SubstanceWidgetType> globalDisallowed;
+
+ private WeakHashMap<JRootPane, Set<SubstanceWidgetType>> specificAllowed;
+
+ private WeakHashMap<JRootPane, Set<SubstanceWidgetType>> specificDisallowed;
+
+ public static synchronized SubstanceWidgetManager getInstance() {
+ if (instance == null)
+ instance = new SubstanceWidgetManager();
+ return instance;
+ }
+
+ private SubstanceWidgetManager() {
+ this.globalAllowed = EnumSet.noneOf(SubstanceWidgetType.class);
+ this.globalDisallowed = EnumSet.noneOf(SubstanceWidgetType.class);
+ this.specificAllowed = new WeakHashMap<JRootPane, Set<SubstanceWidgetType>>();
+ this.specificDisallowed = new WeakHashMap<JRootPane, Set<SubstanceWidgetType>>();
+ }
+
+ public void register(JRootPane rootPane, boolean isAllowed,
+ SubstanceWidgetType... substanceWidgets) {
+ if (rootPane == null) {
+ for (SubstanceWidgetType widget : substanceWidgets) {
+ if (isAllowed) {
+ this.globalAllowed.add(widget);
+ this.globalDisallowed.remove(widget);
+ } else {
+ this.globalDisallowed.add(widget);
+ this.globalAllowed.remove(widget);
+ }
+ }
+ } else {
+ Set<SubstanceWidgetType> toAddTo = null;
+ Set<SubstanceWidgetType> toRemoveFrom = null;
+ if (isAllowed) {
+ toAddTo = this.specificAllowed.get(rootPane);
+ if (toAddTo == null) {
+ toAddTo = EnumSet.noneOf(SubstanceWidgetType.class);
+ this.specificAllowed.put(rootPane, toAddTo);
+ }
+ toRemoveFrom = this.specificDisallowed.get(rootPane);
+ } else {
+ toAddTo = this.specificDisallowed.get(rootPane);
+ if (toAddTo == null) {
+ toAddTo = EnumSet.noneOf(SubstanceWidgetType.class);
+ this.specificDisallowed.put(rootPane, toAddTo);
+ }
+ toRemoveFrom = this.specificAllowed.get(rootPane);
+ }
+
+ for (SubstanceWidgetType widget : substanceWidgets) {
+ toAddTo.add(widget);
+ if (toRemoveFrom != null)
+ toRemoveFrom.remove(widget);
+ }
+ }
+ }
+
+ public boolean isAllowed(JRootPane rootPane, SubstanceWidgetType widget) {
+ if (this.specificDisallowed.containsKey(rootPane)) {
+ if (this.specificDisallowed.get(rootPane).contains(widget))
+ return false;
+ }
+ if (this.specificAllowed.containsKey(rootPane)) {
+ if (this.specificAllowed.get(rootPane).contains(widget))
+ return true;
+ }
+ if (this.globalDisallowed.contains(widget))
+ return false;
+ if (this.globalAllowed.contains(widget))
+ return true;
+ return false;
+ }
+
+ public boolean isAllowedAnywhere(SubstanceWidgetType widget) {
+ if (specificAllowed.size() > 0)
+ return true;
+ if (this.globalDisallowed.contains(widget))
+ return false;
+ if (this.globalAllowed.contains(widget))
+ return true;
+ return false;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceWidgetSupport.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceWidgetSupport.java
new file mode 100644
index 0000000..49f5ba0
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/SubstanceWidgetSupport.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.JInternalFrame.JDesktopIcon;
+
+import org.pushingpixels.lafwidget.LafWidgetSupport;
+import org.pushingpixels.lafwidget.utils.LafConstants.PasswordStrength;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.SubstanceConstants.SubstanceWidgetType;
+import org.pushingpixels.substance.internal.ui.SubstanceDesktopIconUI;
+import org.pushingpixels.substance.internal.ui.SubstanceTabbedPaneUI;
+
+/**
+ * Support for <a href="https://laf-widget.dev.java.net">laf-widget</a> layer.
+ * This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceWidgetSupport extends LafWidgetSupport {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.LafWidgetSupport#getComponentForHover(javax
+ * .swing .JInternalFrame.JDesktopIcon)
+ */
+ @Override
+ public JComponent getComponentForHover(JDesktopIcon desktopIcon) {
+ SubstanceDesktopIconUI ui = (SubstanceDesktopIconUI) desktopIcon
+ .getUI();
+ return ui.getComponentForHover();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.LafWidgetSupport#toInstallMenuSearch(javax
+ * .swing. JMenuBar)
+ */
+ @Override
+ public boolean toInstallMenuSearch(JMenuBar menuBar) {
+ // if the menu search widget has not been allowed,
+ // return false
+ if (!SubstanceWidgetManager.getInstance().isAllowed(
+ SwingUtilities.getRootPane(menuBar),
+ SubstanceWidgetType.MENU_SEARCH))
+ return false;
+ // don't install on menu bar of title panes
+ if (menuBar instanceof SubstanceTitlePane.SubstanceMenuBar)
+ return false;
+ return super.toInstallMenuSearch(menuBar);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetSupport#getSearchIcon(int,
+ * java.awt.ComponentOrientation)
+ */
+ @Override
+ public Icon getSearchIcon(int dimension,
+ ComponentOrientation componentOrientation) {
+ return SubstanceImageCreator.getSearchIcon(dimension,
+ SubstanceColorSchemeUtilities.getColorScheme(null,
+ ComponentState.DEFAULT), componentOrientation
+ .isLeftToRight());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetSupport#getArrowIcon(int)
+ */
+ @Override
+ public Icon getArrowIcon(int orientation) {
+ return SubstanceImageCreator.getArrowIcon(SubstanceSizeUtils
+ .getControlFontSize(), orientation,
+ SubstanceColorSchemeUtilities.getColorScheme(null,
+ ComponentState.DEFAULT));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetSupport#getNumberIcon(int)
+ */
+ @Override
+ public Icon getNumberIcon(int number) {
+ SubstanceColorScheme colorScheme = SubstanceLookAndFeel.getCurrentSkin(
+ null).getActiveColorScheme(DecorationAreaType.HEADER);
+ return SubstanceImageCreator.getHexaMarker(number, colorScheme);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.LafWidgetSupport#markButtonAsFlat(javax.swing
+ * .AbstractButton)
+ */
+ @Override
+ public void markButtonAsFlat(AbstractButton button) {
+ button.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY,
+ Boolean.TRUE);
+ button.setOpaque(false);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.LafWidgetSupport#getRolloverTabIndex(javax
+ * .swing. JTabbedPane)
+ */
+ @Override
+ public int getRolloverTabIndex(JTabbedPane tabbedPane) {
+ SubstanceTabbedPaneUI ui = (SubstanceTabbedPaneUI) tabbedPane.getUI();
+ return ui.getRolloverTabIndex();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.LafWidgetSupport#setTabAreaInsets(javax.swing
+ * .JTabbedPane , java.awt.Insets)
+ */
+ @Override
+ public void setTabAreaInsets(JTabbedPane tabbedPane, Insets tabAreaInsets) {
+ SubstanceTabbedPaneUI ui = (SubstanceTabbedPaneUI) tabbedPane.getUI();
+ ui.setTabAreaInsets(tabAreaInsets);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.LafWidgetSupport#getTabAreaInsets(javax.swing
+ * .JTabbedPane )
+ */
+ @Override
+ public Insets getTabAreaInsets(JTabbedPane tabbedPane) {
+ SubstanceTabbedPaneUI ui = (SubstanceTabbedPaneUI) tabbedPane.getUI();
+ return ui.getTabAreaInsets();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.LafWidgetSupport#getTabRectangle(javax.swing
+ * .JTabbedPane , int)
+ */
+ @Override
+ public Rectangle getTabRectangle(JTabbedPane tabbedPane, int tabIndex) {
+ SubstanceTabbedPaneUI ui = (SubstanceTabbedPaneUI) tabbedPane.getUI();
+ return ui.getTabRectangle(tabIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.LafWidgetSupport#paintPasswordStrengthMarker
+ * (java .awt.Graphics, int, int, int, int,
+ * org.pushingpixels.lafwidget.utils.LafConstants.PasswordStrength)
+ */
+ @Override
+ public void paintPasswordStrengthMarker(Graphics g, int x, int y,
+ int width, int height, PasswordStrength pStrength) {
+ Graphics2D g2 = (Graphics2D) g.create();
+
+ SubstanceColorScheme colorScheme = null;
+
+ if (pStrength == PasswordStrength.WEAK)
+ colorScheme = SubstanceColorSchemeUtilities.ORANGE;
+ if (pStrength == PasswordStrength.MEDIUM)
+ colorScheme = SubstanceColorSchemeUtilities.YELLOW;
+ if (pStrength == PasswordStrength.STRONG)
+ colorScheme = SubstanceColorSchemeUtilities.GREEN;
+
+ if (colorScheme != null) {
+ SubstanceImageCreator.paintRectangularBackground(null, g, x, y,
+ width, height, colorScheme, 0.5f, false);
+ }
+
+ g2.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.LafWidgetSupport#hasLockIcon(java.awt.Component
+ * )
+ */
+ @Override
+ public boolean hasLockIcon(Component comp) {
+ if (!SubstanceCoreUtilities.toShowExtraWidgets(comp))
+ return false;
+ return super.hasLockIcon(comp);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.LafWidgetSupport#getLockIcon(java.awt.Component
+ * )
+ */
+ @Override
+ public Icon getLockIcon(Component c) {
+ return SubstanceImageCreator.makeTransparent(null,
+ SubstanceImageCreator.getSmallLockIcon(
+ SubstanceColorSchemeUtilities.getColorScheme(null,
+ ComponentState.ENABLED), c), 0.3);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.lafwidget.LafWidgetSupport#toInstallExtraElements(java
+ * .awt. Component)
+ */
+ @Override
+ public boolean toInstallExtraElements(Component comp) {
+ return SubstanceCoreUtilities.toShowExtraWidgets(comp);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetSupport#getLookupIconSize()
+ */
+ @Override
+ public int getLookupIconSize() {
+ int result = 2 + SubstanceSizeUtils.getControlFontSize();
+ if (result % 2 != 0)
+ result++;
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.lafwidget.LafWidgetSupport#getLookupButtonSize()
+ */
+ @Override
+ public int getLookupButtonSize() {
+ return 4 + SubstanceSizeUtils.getControlFontSize();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/TabCloseListenerManager.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/TabCloseListenerManager.java
new file mode 100644
index 0000000..435e58e
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/TabCloseListenerManager.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.util.*;
+
+import javax.swing.JTabbedPane;
+
+import org.pushingpixels.substance.api.tabbed.BaseTabCloseListener;
+
+/**
+ * Manages the listeners registered on tab-close events. This class is <b>for
+ * internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TabCloseListenerManager {
+ /**
+ * Listeners registered on all tabbed panes.
+ */
+ private Set<BaseTabCloseListener> generalListeners;
+
+ /**
+ * Listeners registered on specific tabbed panes.
+ */
+ private Map<JTabbedPane, Set<BaseTabCloseListener>> specificListeners;
+
+ /**
+ * A single instance of <code>this</code> manager.
+ */
+ private static TabCloseListenerManager instance = new TabCloseListenerManager();
+
+ /**
+ * Returns the single instance of <code>this</code> class.
+ *
+ * @return The single instance of <code>this</code> class.
+ */
+ public static TabCloseListenerManager getInstance() {
+ return instance;
+ }
+
+ /**
+ * Simple constructor.
+ */
+ public TabCloseListenerManager() {
+ this.generalListeners = new HashSet<BaseTabCloseListener>();
+ this.specificListeners = new HashMap<JTabbedPane, Set<BaseTabCloseListener>>();
+ }
+
+ /**
+ * Unregisters the specified tabbed pane.
+ *
+ * @param tabbedPane
+ * Tabbed pane to unregister.
+ */
+ public synchronized void unregisterTabbedPane(JTabbedPane tabbedPane) {
+ this.specificListeners.remove(tabbedPane);
+ }
+
+ /**
+ * Registers the specified listener on tab-close events on <b>all</b> tabbed
+ * panes.
+ *
+ * @param listener
+ * Listener to register.
+ */
+ public synchronized void registerListener(BaseTabCloseListener listener) {
+ this.generalListeners.add(listener);
+ }
+
+ /**
+ * Unregisters the specified listener on tab-close events on <b>all</b>
+ * tabbed panes.
+ *
+ * @param listener
+ * Listener to unregister.
+ */
+ public synchronized void unregisterListener(BaseTabCloseListener listener) {
+ this.generalListeners.remove(listener);
+ }
+
+ /**
+ * Returns the set of all listeners registered on tab-close events on
+ * <b>all</b> tabbed panes.
+ *
+ * @return Set of all listeners registered on tab-close events on <b>all</b>
+ * tabbed panes.
+ */
+ public synchronized Set<BaseTabCloseListener> getListeners() {
+ return Collections.unmodifiableSet(this.generalListeners);
+ }
+
+ /**
+ * Registers the specified listener on tab-close events on <b>the
+ * specified</b> tabbed pane.
+ *
+ * @param tabbedPane
+ * Tabbed pane. If <code>null</code>, the tab close listener is
+ * registered globally (for all tabbed panes).
+ * @param listener
+ * Listener to register.
+ */
+ public synchronized void registerListener(JTabbedPane tabbedPane,
+ BaseTabCloseListener listener) {
+ if (tabbedPane == null) {
+ this.registerListener(listener);
+ } else {
+ Set<BaseTabCloseListener> listeners = this.specificListeners
+ .get(tabbedPane);
+ if (listeners == null) {
+ listeners = new HashSet<BaseTabCloseListener>();
+ this.specificListeners.put(tabbedPane, listeners);
+ }
+ listeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregisters the specified listener on tab-close events on <b>the
+ * specified</b> tabbed pane.
+ *
+ * @param tabbedPane
+ * Tabbed pane. If <code>null</code>, the tab close listener is
+ * unregistered globally (for all tabbed panes).
+ * @param listener
+ * Listener to unregister.
+ */
+ public synchronized void unregisterListener(JTabbedPane tabbedPane,
+ BaseTabCloseListener listener) {
+ if (tabbedPane == null) {
+ this.unregisterListener(listener);
+ } else {
+ Set<BaseTabCloseListener> listeners = this.specificListeners
+ .get(tabbedPane);
+ if (listeners != null)
+ listeners.remove(listener);
+ }
+ }
+
+ /**
+ * Returns the set of all listeners registered on tab-close events on <b>the
+ * specified</b> tabbed pane.
+ *
+ * @param tabbedPane
+ * Tabbed pane. If <code>null</code>, all globally registered tab
+ * close listeners are returned.
+ * @return The set of all listeners registered on tab-close events on <b>the
+ * specified</b> tabbed pane.
+ */
+ public synchronized Set<BaseTabCloseListener> getListeners(
+ JTabbedPane tabbedPane) {
+ if (tabbedPane == null)
+ return this.getListeners();
+
+ Set<BaseTabCloseListener> result = new HashSet<BaseTabCloseListener>();
+ for (BaseTabCloseListener listener : this.generalListeners)
+ result.add(listener);
+ Set<BaseTabCloseListener> listeners = this.specificListeners
+ .get(tabbedPane);
+ if (listeners != null) {
+ for (BaseTabCloseListener listener : listeners)
+ result.add(listener);
+ }
+
+ return Collections.unmodifiableSet(result);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/TraitInfoImpl.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/TraitInfoImpl.java
new file mode 100644
index 0000000..37412e7
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/TraitInfoImpl.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import org.pushingpixels.substance.api.trait.SubstanceTraitInfo;
+
+/**
+ * Basic class for trait info.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TraitInfoImpl implements SubstanceTraitInfo {
+ /**
+ * The display name of the associated trait.
+ */
+ private String displayName;
+
+ /**
+ * The class name of the associated trait.
+ */
+ private String className;
+
+ /**
+ * Indication whether the associated trait is default in the application.
+ */
+ private boolean isDefault;
+
+ /**
+ * Simple constructor.
+ *
+ * @param displayName
+ * Display name of the associated trait.
+ * @param className
+ * Class name of the associated trait.
+ */
+ public TraitInfoImpl(String displayName, String className) {
+ this.displayName = displayName;
+ this.className = className;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.SubstanceTraitInfo#getClassName()
+ */
+ @Override
+ public String getClassName() {
+ return this.className;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.SubstanceTrait#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return this.displayName;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.SubstanceTraitInfo#isDefault()
+ */
+ @Override
+ public boolean isDefault() {
+ return this.isDefault;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.SubstanceTraitInfo#setDefault(boolean)
+ */
+ @Override
+ public void setDefault(boolean isDefault) {
+ this.isDefault = isDefault;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/UpdateOptimizationAware.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/UpdateOptimizationAware.java
new file mode 100644
index 0000000..1fce83f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/UpdateOptimizationAware.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+public interface UpdateOptimizationAware {
+ public UpdateOptimizationInfo getUpdateOptimizationInfo();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/UpdateOptimizationInfo.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/UpdateOptimizationInfo.java
new file mode 100644
index 0000000..dd4fd16
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/UpdateOptimizationInfo.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils;
+
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.JComponent;
+
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+
+public class UpdateOptimizationInfo {
+ private JComponent component;
+
+ public boolean toDrawWatermark;
+
+ private Map<ComponentState, SubstanceColorScheme> highlightSchemeMap;
+
+ private Map<ComponentState, SubstanceColorScheme> highlightBorderSchemeMap;
+
+ private Map<ComponentState, SubstanceColorScheme> borderSchemeMap;
+
+ private Map<ComponentState, SubstanceColorScheme> fillSchemeMap;
+
+ private Map<ComponentState, Float> highlightAlphaMap;
+
+ private SubstanceColorScheme defaultScheme;
+
+ public DecorationAreaType decorationAreaType;
+
+ public boolean isInDecorationArea;
+
+ public UpdateOptimizationInfo(JComponent component) {
+ this.component = component;
+
+ this.toDrawWatermark = SubstanceCoreUtilities
+ .toDrawWatermark(this.component);
+ this.defaultScheme = SubstanceColorSchemeUtilities.getColorScheme(
+ this.component, ComponentState.ENABLED);
+ this.decorationAreaType = SubstanceLookAndFeel
+ .getDecorationType(this.component);
+
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(this.component);
+ this.isInDecorationArea = (this.decorationAreaType != null)
+ && skin.isRegisteredAsDecorationArea(this.decorationAreaType)
+ && SubstanceCoreUtilities.isOpaque(this.component);
+ }
+
+ public SubstanceColorScheme getHighlightColorScheme(ComponentState state) {
+ if (this.highlightSchemeMap == null) {
+ this.highlightSchemeMap = new HashMap<ComponentState, SubstanceColorScheme>();
+ //ComponentState.class);
+ }
+ SubstanceColorScheme result = this.highlightSchemeMap.get(state);
+ if (result == null) {
+ result = SubstanceColorSchemeUtilities
+ .getColorScheme(this.component,
+ ColorSchemeAssociationKind.HIGHLIGHT, state);
+ this.highlightSchemeMap.put(state, result);
+ }
+ return result;
+ }
+
+ public SubstanceColorScheme getBorderColorScheme(ComponentState state) {
+ if (this.borderSchemeMap == null) {
+ this.borderSchemeMap = new HashMap<ComponentState, SubstanceColorScheme>();
+ //ComponentState.class);
+ }
+ SubstanceColorScheme result = this.borderSchemeMap.get(state);
+ if (result == null) {
+ result = SubstanceColorSchemeUtilities.getColorScheme(
+ this.component, ColorSchemeAssociationKind.BORDER, state);
+ this.borderSchemeMap.put(state, result);
+ }
+ return result;
+ }
+
+ public SubstanceColorScheme getFillColorScheme(ComponentState state) {
+ if (state == ComponentState.ENABLED) {
+ return this.defaultScheme;
+ }
+ if (this.fillSchemeMap == null) {
+ this.fillSchemeMap = new HashMap<ComponentState, SubstanceColorScheme>();
+ //ComponentState.class);
+ }
+ SubstanceColorScheme result = this.fillSchemeMap.get(state);
+ if (result == null) {
+ result = SubstanceColorSchemeUtilities.getColorScheme(
+ this.component, state);
+ this.fillSchemeMap.put(state, result);
+ }
+ return result;
+ }
+
+ public SubstanceColorScheme getHighlightBorderColorScheme(
+ ComponentState state) {
+ if (this.highlightBorderSchemeMap == null) {
+ this.highlightBorderSchemeMap = new HashMap<ComponentState, SubstanceColorScheme>();
+ //ComponentState.class);
+ }
+ SubstanceColorScheme result = this.highlightBorderSchemeMap.get(state);
+ if (result == null) {
+ result = SubstanceColorSchemeUtilities.getColorScheme(
+ this.component,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER, state);
+ this.highlightBorderSchemeMap.put(state, result);
+ }
+ return result;
+ }
+
+ public float getHighlightAlpha(ComponentState state) {
+ if (this.highlightAlphaMap == null) {
+ this.highlightAlphaMap = new HashMap<ComponentState, Float>();
+ //ComponentState.class);
+ }
+ if (!this.highlightAlphaMap.containsKey(state)) {
+ this.highlightAlphaMap.put(state, SubstanceColorSchemeUtilities
+ .getHighlightAlpha(this.component, state));
+ }
+ return this.highlightAlphaMap.get(state);
+ }
+
+ public SubstanceColorScheme getDefaultScheme() {
+ return this.defaultScheme;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceBorder.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceBorder.java
new file mode 100644
index 0000000..c166a2e
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceBorder.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.border;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+import javax.swing.border.Border;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Gradient border for the <b>Substance</b> look and feel. This class is <b>for
+ * internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceBorder implements Border, UIResource {
+ /**
+ * Insets of <code>this</code> border.
+ */
+ protected Insets myInsets;
+
+ /**
+ * Border alpha.
+ */
+ protected float alpha;
+
+ /**
+ * When the border is painted, the default radius is multiplied by this
+ * factor.
+ */
+ protected float radiusScaleFactor;
+
+ /**
+ * Cache of small border images.
+ */
+ private static LazyResettableHashMap<BufferedImage> smallImageCache = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceBorder");
+
+ /**
+ * Creates a new border with dynamic insets (computed at the invocation time
+ * of {@link #getBorderInsets(Component)} call).
+ */
+ public SubstanceBorder() {
+ super();
+ this.alpha = 1.0f;
+ this.radiusScaleFactor = 0.5f;
+ }
+
+ /**
+ * Creates a new border with dynamic insets (computed at the invocation time
+ * of {@link #getBorderInsets(Component)} call).
+ *
+ * @param radiusScaleFactor
+ * Radius scale factor.
+ */
+ public SubstanceBorder(float radiusScaleFactor) {
+ this();
+ this.radiusScaleFactor = radiusScaleFactor;
+ }
+
+ /**
+ * Creates a new border with the specified insets.
+ *
+ * @param insets
+ * Insets.
+ */
+ public SubstanceBorder(Insets insets) {
+ this();
+ this.myInsets = new Insets(insets.top, insets.left, insets.bottom,
+ insets.right);
+ }
+
+ /**
+ * Sets the alpha for this border.
+ *
+ * @param alpha
+ * Alpha factor.
+ */
+ public void setAlpha(float alpha) {
+ this.alpha = alpha;
+ }
+
+ /**
+ * Paints border instance for the specified component.
+ *
+ * @param c
+ * The component.
+ * @param g
+ * Graphics context.
+ * @param x
+ * Component left X (in graphics context).
+ * @param y
+ * Component top Y (in graphics context).
+ * @param width
+ * Component width.
+ * @param height
+ * Component height.
+ * @param isEnabled
+ * Component enabled status.
+ * @param hasFocus
+ * Component focus ownership status.
+ * @param alpha
+ * Alpha value.
+ */
+ private void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height, boolean isEnabled, boolean hasFocus, float alpha) {
+ // failsafe for LAF change
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ return;
+ }
+
+ if ((width <= 0) || (height <= 0))
+ return;
+
+ if (alpha == 0.0f) {
+ return;
+ }
+
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ float radius = this.radiusScaleFactor
+ * SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(c));
+
+ ComponentState state = isEnabled ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED;
+ SubstanceColorScheme borderColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c, ColorSchemeAssociationKind.BORDER, state);
+ float finalAlpha = alpha
+ * SubstanceColorSchemeUtilities.getAlpha(c, state);
+
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c,
+ finalAlpha, g));
+
+ if (width * height < 100000) {
+ HashMapKey hashKey = SubstanceCoreUtilities
+ .getHashKey(SubstanceCoreUtilities.getBorderPainter(c)
+ .getDisplayName(), SubstanceSizeUtils
+ .getComponentFontSize(c), width, height, radius,
+ borderColorScheme.getDisplayName());
+ BufferedImage result = smallImageCache.get(hashKey);
+ if (result == null) {
+ result = SubstanceCoreUtilities.getBlankImage(width, height);
+ Graphics2D g2d = result.createGraphics();
+ SubstanceImageCreator.paintBorder(c, g2d, 0, 0, width, height,
+ radius, borderColorScheme);
+ g2d.dispose();
+ smallImageCache.put(hashKey, result);
+ }
+ graphics.drawImage(result, x, y, null);
+ } else {
+ // for borders larger than 100000 pixels, use simple
+ // painting
+ graphics.translate(x, y);
+ SubstanceImageCreator.paintSimpleBorder(c, graphics, width, height,
+ borderColorScheme);
+ }
+
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#paintBorder(java.awt.Component,
+ * java.awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height) {
+ paintBorder(c, g, x, y, width, height, c.isEnabled(), c.hasFocus(),
+ this.alpha);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#getBorderInsets(java.awt.Component)
+ */
+ @Override
+ public Insets getBorderInsets(Component c) {
+ if (this.myInsets == null) {
+ return SubstanceSizeUtils.getDefaultBorderInsets(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ }
+ return this.myInsets;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#isBorderOpaque()
+ */
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ /**
+ * Returns the radius scale factor of this border.
+ *
+ * @return The radius scale factor of this border.
+ */
+ public float getRadiusScaleFactor() {
+ return this.radiusScaleFactor;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceButtonBorder.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceButtonBorder.java
new file mode 100644
index 0000000..b3d880c
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceButtonBorder.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.border;
+
+import java.awt.Component;
+import java.awt.Graphics;
+
+import javax.swing.border.Border;
+import javax.swing.plaf.UIResource;
+
+/**
+ * Base class for button borders in <b>Substance </b> look-and-feel. This class
+ * is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class SubstanceButtonBorder implements Border, UIResource {
+ /**
+ * The associated button shaper class.
+ */
+ private Class<?> buttonShaperClass;
+
+ /**
+ * Simple constructor.
+ *
+ * @param buttonShaperClass
+ * The associated button shaper class.
+ */
+ public SubstanceButtonBorder(Class<?> buttonShaperClass) {
+ this.buttonShaperClass = buttonShaperClass;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#isBorderOpaque()
+ */
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#paintBorder(java.awt.Component,
+ * java.awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height) {
+ }
+
+ /**
+ * Returns the associated button shaper class.
+ *
+ * @return The associated button shaper class.
+ */
+ public Class<?> getButtonShaperClass() {
+ return this.buttonShaperClass;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceEtchedBorder.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceEtchedBorder.java
new file mode 100644
index 0000000..ef1a3c9
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceEtchedBorder.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.border;
+
+import java.awt.*;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+
+import javax.swing.border.Border;
+
+import org.pushingpixels.lafwidget.utils.RenderingUtils;
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Custom implementation of etched border.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceEtchedBorder implements Border {
+ /**
+ * Returns the highlight color for the specified component.
+ *
+ * @param c
+ * Component.
+ * @return Matching highlight color.
+ */
+ public Color getHighlightColor(Component c) {
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c, ColorSchemeAssociationKind.SEPARATOR,
+ ComponentState.ENABLED);
+ boolean isDark = colorScheme.isDark();
+ Color foreDark = isDark ? colorScheme.getExtraLightColor()
+ : SubstanceColorUtilities.getInterpolatedColor(colorScheme
+ .getMidColor(), colorScheme.getDarkColor(), 0.4);
+
+ return SubstanceColorUtilities.getAlphaColor(foreDark, 196);
+ }
+
+ /**
+ * Returns the shadow color for the specified component.
+ *
+ * @param c
+ * Component.
+ * @return Matching shadow color.
+ */
+ public Color getShadowColor(Component c) {
+ SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c, ColorSchemeAssociationKind.SEPARATOR,
+ ComponentState.ENABLED);
+ Color back = colorScheme.isDark() ? colorScheme.getDarkColor()
+ : colorScheme.getUltraLightColor();
+ return SubstanceColorUtilities.getAlphaColor(back, 196);
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height) {
+ int w = width;
+ int h = height;
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ float strokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_ROUND));
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ g2d.translate(x, y);
+
+ g2d.setColor(getShadowColor(c));
+
+ // this is to prevent clipping of thick outer borders.
+ int delta = (int) Math.floor(strokeWidth / 2.0);
+
+ g2d.draw(new Rectangle2D.Float(delta, delta, w - delta - 2
+ * strokeWidth, h - delta - 2 * strokeWidth));
+ // g2d.drawRect(0, 0, w - 2, h - 2);
+
+ g2d.setColor(getHighlightColor(c));
+ g2d.draw(new Line2D.Float(strokeWidth, h - 3 * strokeWidth,
+ strokeWidth, strokeWidth));
+ // g2d.drawLine(1, h - 3, 1, 1);
+ g2d.draw(new Line2D.Float(delta + strokeWidth, delta + strokeWidth, w
+ - delta - 3 * strokeWidth, delta + strokeWidth));
+ // g2d.drawLine(1, 1, w - 3, 1);
+
+ g2d.draw(new Line2D.Float(delta, h - delta - strokeWidth, w - delta
+ - strokeWidth, h - delta - strokeWidth));
+ // g2d.drawLine(0, h - 1, w - 1, h - 1);
+ g2d.draw(new Line2D.Float(w - delta - strokeWidth, h - delta
+ - strokeWidth, w - delta - strokeWidth, delta));
+ // g2d.drawLine(w - 1, h - 1, w - 1, 0);
+
+ g2d.dispose();
+
+ // this is a fix for defect 248 - in order to paint the TitledBorder
+ // text respecting the AA settings of the display, we have to
+ // set rendering hints on the passed Graphics object.
+ RenderingUtils.installDesktopHints((Graphics2D) g, c);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#getBorderInsets(java.awt.Component)
+ */
+ @Override
+ public Insets getBorderInsets(Component c) {
+ float borderStrokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ int prefSize = (int) (Math.ceil(2.0 * borderStrokeWidth));
+ return new Insets(prefSize, prefSize, prefSize, prefSize);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstancePaneBorder.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstancePaneBorder.java
new file mode 100755
index 0000000..d3d4251
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstancePaneBorder.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.border;
+
+import java.awt.*;
+
+import javax.swing.JRootPane;
+import javax.swing.SwingUtilities;
+import javax.swing.border.AbstractBorder;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Root pane and internal frame border in <b>Substance</b> look and feel. This
+ * class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstancePaneBorder extends AbstractBorder implements UIResource {
+ /**
+ * Default border thickness.
+ */
+ private static final int BORDER_THICKNESS = 4;
+ private static final int BORDER_ROUNDNESS = 12;
+
+ /**
+ * Default insets.
+ */
+ private static final Insets INSETS = new Insets(
+ SubstancePaneBorder.BORDER_THICKNESS,
+ SubstancePaneBorder.BORDER_THICKNESS,
+ SubstancePaneBorder.BORDER_THICKNESS,
+ SubstancePaneBorder.BORDER_THICKNESS);
+
+ public static DecorationAreaType getRootPaneType(JRootPane rootPane) {
+ DecorationAreaType type = SubstanceLookAndFeel.getDecorationType(rootPane);
+ if ((type == null) || (type == DecorationAreaType.NONE)) {
+ if (SubstanceCoreUtilities.isPaintRootPaneActivated(rootPane)) {
+ if (SubstanceCoreUtilities.isSecondaryWindow(rootPane)) {
+ type = DecorationAreaType.SECONDARY_TITLE_PANE;
+ } else {
+ type = DecorationAreaType.PRIMARY_TITLE_PANE;
+ }
+ } else {
+ if (SubstanceCoreUtilities.isSecondaryWindow(rootPane)) {
+ type = DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE;
+ } else {
+ type = DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE;
+ }
+ }
+ } else if (type == DecorationAreaType.PRIMARY_TITLE_PANE) {
+ if (!SubstanceCoreUtilities.isPaintRootPaneActivated(rootPane)) {
+ type =DecorationAreaType.PRIMARY_TITLE_PANE_INACTIVE;
+ }
+ } else if (type == DecorationAreaType.SECONDARY_TITLE_PANE) {
+ if (!SubstanceCoreUtilities.isPaintRootPaneActivated(rootPane)) {
+ type =DecorationAreaType.SECONDARY_TITLE_PANE_INACTIVE;
+ }
+ }
+ return type;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#paintBorder(java.awt.Component,
+ * java.awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) {
+ if (SubstanceCoreUtilities.isRoundedCorners(c)) {
+ paintRoundedBorder(c, g, x, y, w, h);
+ } else {
+ paintSquareBorder(c, g, x, y, w, h);
+ }
+ }
+
+ public void paintSquareBorder(Component c, Graphics g, int x, int y, int w, int h) {
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(c);
+ if (skin == null)
+ return;
+
+
+ SubstanceColorScheme scheme = skin
+ .getBackgroundColorScheme(getRootPaneType(SwingUtilities.getRootPane(c)));
+ Component titlePaneComp = SubstanceLookAndFeel
+ .getTitlePaneComponent(SwingUtilities.windowForComponent(c));
+ SubstanceColorScheme borderScheme = skin.getColorScheme(titlePaneComp,
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED);
+
+ Graphics2D graphics = (Graphics2D) g;
+
+ // bottom and right in ultra dark
+ graphics.setColor(borderScheme.getUltraDarkColor());
+ graphics.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
+ graphics.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
+ // top and left
+ graphics.setColor(borderScheme.getDarkColor());
+ graphics.drawLine(x, y, x + w - 2, y);
+ graphics.drawLine(x, y, x, y + h - 2);
+ // inner bottom and right
+ graphics.setColor(scheme.getMidColor());
+ graphics.drawLine(x + 1, y + h - 2, x + w - 2, y + h - 2);
+ graphics.drawLine(x + w - 2, y + 1, x + w - 2, y + h - 2);
+ // inner top and left
+ graphics.setColor(scheme.getMidColor());
+ graphics.drawLine(x + 1, y + 1, x + w - 3, y + 1);
+ graphics.drawLine(x + 1, y + 1, x + 1, y + h - 3);
+ // inner 2 and 3
+ graphics.setColor(scheme.getLightColor());
+ graphics.drawRect(x + 2, y + 2, w - 5, h - 5);
+ graphics.drawRect(x + 3, y + 3, w - 7, h - 7);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#getBorderInsets(java.awt.Component)
+ */
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return SubstancePaneBorder.INSETS;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.border.AbstractBorder#getBorderInsets(java.awt.Component,
+ * java.awt.Insets)
+ */
+ @Override
+ public Insets getBorderInsets(Component c, Insets newInsets) {
+ newInsets.top = SubstancePaneBorder.INSETS.top;
+ newInsets.left = SubstancePaneBorder.INSETS.left;
+ newInsets.bottom = SubstancePaneBorder.INSETS.bottom;
+ newInsets.right = SubstancePaneBorder.INSETS.right;
+ return newInsets;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#isBorderOpaque()
+ */
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ public void paintRoundedBorder(Component c, Graphics g, int x, int y, int w, int h) {
+ SubstanceColorScheme scheme = getColorScheme(c);
+ if (scheme == null) return;
+ SubstanceColorScheme borderScheme = getBorderColorScheme(c);
+
+ Graphics2D graphics = (Graphics2D) g;
+
+ int xl = x + BORDER_THICKNESS + 2;
+ int xr = x + w - BORDER_THICKNESS - 3;
+ int yt = y + BORDER_THICKNESS + 2;
+ int yb = y + h - BORDER_THICKNESS - 3;
+
+ Object rh = graphics.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ // bottom and right in ultra dark
+ graphics.setColor(borderScheme.getUltraDarkColor());
+ graphics.drawLine(xl, y + h - 1, xr, y + h - 1); // bottom
+ graphics.drawLine(x + w - 1, yt, x + w - 1, yb); // right
+ // se
+ graphics.fillOval(x+w- BORDER_ROUNDNESS,y+h- BORDER_ROUNDNESS, BORDER_ROUNDNESS, BORDER_ROUNDNESS);
+
+
+ // top and left
+ graphics.setColor(borderScheme.getDarkColor());
+ graphics.drawLine(xl, y, xr, y);
+ graphics.drawLine(x, yt, x, yb);
+ // nw, ne, sw
+ graphics.fillOval(0 ,0 , BORDER_ROUNDNESS, BORDER_ROUNDNESS);
+ graphics.fillOval(0 ,y+h- BORDER_ROUNDNESS, BORDER_ROUNDNESS, BORDER_ROUNDNESS);
+ graphics.fillOval(x+w- BORDER_ROUNDNESS,0 , BORDER_ROUNDNESS, BORDER_ROUNDNESS);
+
+
+
+ // inner bottom and right
+ graphics.setColor(scheme.getMidColor());
+ graphics.drawLine(xl, y + h - 2, xr, y + h - 2);
+ graphics.drawLine(x + w - 2, yt, x + w - 2, yb);
+ graphics.drawLine(xl, y + 1, xr, y + 1);
+ graphics.drawLine(x + 1, yt, x + 1, yb);
+
+ graphics.fillOval(1, 1, BORDER_ROUNDNESS, BORDER_ROUNDNESS);
+ graphics.fillOval(1, y + h - BORDER_ROUNDNESS - 1, BORDER_ROUNDNESS, BORDER_ROUNDNESS);
+ graphics.fillOval(x + w - BORDER_ROUNDNESS - 1, 1, BORDER_ROUNDNESS, BORDER_ROUNDNESS);
+ graphics.fillOval(x + w - BORDER_ROUNDNESS - 1, y + h - BORDER_ROUNDNESS - 1, BORDER_ROUNDNESS, BORDER_ROUNDNESS);
+
+
+
+ graphics.setColor(scheme.getLightColor());
+ graphics.drawLine(xl, y + 2, xr, y + 2);
+ graphics.drawLine(x + 2, yt, x + 2, yb);
+ graphics.drawLine(xl, y + h - 3, xr, y + h - 3);
+ graphics.drawLine(x + w - 3, yt, x + w - 3, yb);
+ graphics.drawLine(xl, y + 3, xr, y + 3);
+ graphics.drawLine(x + 3, yt, x + 3, yb);
+ graphics.drawLine(xl, y + h - 4, xr, y + h - 4);
+ graphics.drawLine(x + w - 4, yt, x + w - 4, yb);
+
+ graphics.fillOval(2, 2, BORDER_ROUNDNESS, BORDER_ROUNDNESS);
+ graphics.fillOval(2, y + h - BORDER_ROUNDNESS - 2, BORDER_ROUNDNESS, BORDER_ROUNDNESS);
+ graphics.fillOval(x + w - BORDER_ROUNDNESS -2, 2, BORDER_ROUNDNESS, BORDER_ROUNDNESS);
+ graphics.fillOval(x + w - BORDER_ROUNDNESS -2, y + h - BORDER_ROUNDNESS - 2, BORDER_ROUNDNESS, BORDER_ROUNDNESS);
+
+
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, rh);
+ }
+
+ private SubstanceColorScheme getColorScheme(Component c) {
+ JRootPane rp = c instanceof JRootPane
+ ? (JRootPane) c
+ : SwingUtilities.getRootPane(c);
+
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(c);
+ if (skin == null) return null;
+
+ DecorationAreaType type = getRootPaneType(rp);
+ return skin.getBackgroundColorScheme(type);
+ }
+
+ private SubstanceColorScheme getBorderColorScheme(Component c) {
+ JRootPane rp = c instanceof JRootPane
+ ? (JRootPane) c
+ : SwingUtilities.getRootPane(c);
+
+ SubstanceSkin skin = SubstanceCoreUtilities.getSkin(c);
+
+ if (skin == null) return null;
+
+
+ Component titlePaneComp = SubstanceLookAndFeel
+ .getTitlePaneComponent(SwingUtilities.windowForComponent(c));
+
+ return skin.getColorScheme(getRootPaneType(rp),
+ ColorSchemeAssociationKind.BORDER, ComponentState.ENABLED);
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceTableCellBorder.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceTableCellBorder.java
new file mode 100644
index 0000000..4d45a23
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceTableCellBorder.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.border;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+import javax.swing.JComponent;
+import javax.swing.border.Border;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.ui.SubstanceTableUI;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Table cell border for the <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTableCellBorder implements Border, UIResource {
+ /**
+ * Insets of <code>this</code> border.
+ */
+ protected Insets myInsets;
+
+ protected SubstanceTableUI ui;
+
+ protected SubstanceTableUI.TableCellId cellId;
+
+ /**
+ * Border alpha.
+ */
+ protected float alpha;
+
+ /**
+ * Cache of small border images.
+ */
+ private static LazyResettableHashMap<BufferedImage> smallImageCache = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceTableCellBorder");
+
+ /**
+ * Creates a new border with the specified insets.
+ *
+ * @param insets
+ * Insets.
+ */
+ public SubstanceTableCellBorder(Insets insets, SubstanceTableUI ui,
+ SubstanceTableUI.TableCellId cellId) {
+ this.myInsets = new Insets(insets.top, insets.left, insets.bottom,
+ insets.right);
+ this.ui = ui;
+ this.cellId = cellId;
+ this.alpha = 1.0f;
+ }
+
+ /**
+ * Paints border instance for the specified component.
+ *
+ * @param c
+ * The component.
+ * @param g
+ * Graphics context.
+ * @param x
+ * Component left X (in graphics context).
+ * @param y
+ * Component top Y (in graphics context).
+ * @param width
+ * Component width.
+ * @param height
+ * Component height.
+ * @param isEnabled
+ * Component enabled status.
+ * @param hasFocus
+ * Component focus ownership status.
+ */
+ private void paintBorder(JComponent c, Graphics g, int x, int y, int width,
+ int height, boolean isEnabled, boolean hasFocus) {
+ // failsafe for LAF change
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ return;
+ }
+
+ if ((width <= 0) || (height <= 0))
+ return;
+
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ float radius = 0.0f;
+ StateTransitionTracker stateTransitionTracker = ui
+ .getStateTransitionTracker(cellId);
+ StateTransitionTracker.ModelStateInfo modelStateInfo = (stateTransitionTracker == null) ? null
+ : stateTransitionTracker.getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = (modelStateInfo == null) ? null
+ : modelStateInfo.getStateContributionMap();
+ ComponentState currState = (modelStateInfo == null) ? ui
+ .getCellState(cellId) : modelStateInfo.getCurrModelState();
+ if (currState.isDisabled())
+ currState = ComponentState.DISABLED_SELECTED;
+
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c, ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ currState);
+
+ HashMapKey baseKey = SubstanceCoreUtilities.getHashKey(
+ SubstanceSizeUtils.getComponentFontSize(c), width, height,
+ radius, baseBorderScheme.getDisplayName());
+ BufferedImage baseLayer = smallImageCache.get(baseKey);
+ float baseAlpha = SubstanceColorSchemeUtilities.getAlpha(c, currState);
+
+ if (baseLayer == null) {
+ baseLayer = SubstanceCoreUtilities.getBlankImage(width, height);
+ Graphics2D g2d = baseLayer.createGraphics();
+ SubstanceImageCreator.paintBorder(c, g2d, 0, 0, width, height,
+ radius, baseBorderScheme);
+ g2d.dispose();
+ smallImageCache.put(baseKey, baseLayer);
+ }
+
+ graphics.setComposite(AlphaComposite.SrcOver.derive(baseAlpha
+ * this.alpha));
+ graphics.drawImage(baseLayer, x, y, null);
+
+ if (!currState.isDisabled() && (activeStates != null)
+ && (activeStates.size() > 1)) {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ activeState);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(
+ SubstanceSizeUtils.getComponentFontSize(c), width,
+ height, radius, borderScheme.getDisplayName());
+ BufferedImage layer = smallImageCache.get(key);
+ float activeAlpha = SubstanceColorSchemeUtilities.getAlpha(c,
+ activeState);
+
+ if (layer == null) {
+ layer = SubstanceCoreUtilities.getBlankImage(width, height);
+ Graphics2D g2d = layer.createGraphics();
+ SubstanceImageCreator.paintBorder(c, g2d, 0, 0, width,
+ height, radius, borderScheme);
+ g2d.dispose();
+ smallImageCache.put(key, layer);
+ }
+
+ graphics.setComposite(AlphaComposite.SrcOver.derive(activeAlpha
+ * this.alpha * contribution));
+ graphics.drawImage(layer, x, y, null);
+ }
+ }
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#paintBorder(java.awt.Component,
+ * java.awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height) {
+ paintBorder((JComponent) c, g, x, y, width, height, c.isEnabled(), c
+ .hasFocus());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#getBorderInsets(java.awt.Component)
+ */
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return this.myInsets;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#isBorderOpaque()
+ */
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ /**
+ * Sets the alpha for this border.
+ *
+ * @param alpha
+ * Alpha factor.
+ */
+ public void setAlpha(float alpha) {
+ this.alpha = alpha;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceTextComponentBorder.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceTextComponentBorder.java
new file mode 100644
index 0000000..6686df8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceTextComponentBorder.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.border;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+import javax.swing.JComponent;
+import javax.swing.border.Border;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.text.JTextComponent;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Text component border for the <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceTextComponentBorder implements Border, UIResource {
+ /**
+ * Insets of <code>this</code> border.
+ */
+ protected Insets myInsets;
+
+ /**
+ * Cache of small border images.
+ */
+ private static LazyResettableHashMap<BufferedImage> smallImageCache = new LazyResettableHashMap<BufferedImage>(
+ "SubstanceTextComponentBorder");
+
+ /**
+ * Creates a new border with the specified insets.
+ *
+ * @param insets
+ * Insets.
+ */
+ public SubstanceTextComponentBorder(Insets insets) {
+ this.myInsets = new Insets(insets.top, insets.left, insets.bottom,
+ insets.right);
+ }
+
+ /**
+ * Paints border instance for the specified component.
+ *
+ * @param c
+ * The component.
+ * @param g
+ * Graphics context.
+ * @param x
+ * Component left X (in graphics context).
+ * @param y
+ * Component top Y (in graphics context).
+ * @param width
+ * Component width.
+ * @param height
+ * Component height.
+ * @param isEnabled
+ * Component enabled status.
+ * @param hasFocus
+ * Component focus ownership status.
+ */
+ private void paintBorder(JComponent c, Graphics g, int x, int y, int width,
+ int height, boolean isEnabled, boolean hasFocus) {
+ // failsafe for LAF change
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
+ return;
+ }
+
+ if ((width <= 0) || (height <= 0))
+ return;
+
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ // float cyclePos = 1.0f;
+
+ float radius = 2.0f * SubstanceSizeUtils
+ .getClassicButtonCornerRadius(SubstanceSizeUtils
+ .getComponentFontSize(c));
+
+ JTextComponent componentForTransitions = SubstanceCoreUtilities
+ .getTextComponentForTransitions(c);
+
+ if (componentForTransitions != null) {
+ ComponentUI ui = componentForTransitions.getUI();
+ if (ui instanceof TransitionAwareUI) {
+ TransitionAwareUI trackable = (TransitionAwareUI) ui;
+ StateTransitionTracker stateTransitionTracker = trackable
+ .getTransitionTracker();
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ ComponentState currState = modelStateInfo.getCurrModelState();
+ if (currState.isDisabled())
+ currState = ComponentState.DISABLED_SELECTED;
+ if (width * height < 100000) {
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(componentForTransitions,
+ ColorSchemeAssociationKind.BORDER,
+ currState);
+
+ HashMapKey baseKey = SubstanceCoreUtilities.getHashKey(
+ SubstanceSizeUtils.getComponentFontSize(c), width,
+ height, radius, baseBorderScheme.getDisplayName());
+ BufferedImage baseLayer = smallImageCache.get(baseKey);
+ float baseAlpha = SubstanceColorSchemeUtilities.getAlpha(c,
+ currState);
+
+ if (baseLayer == null) {
+ baseLayer = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D g2d = baseLayer.createGraphics();
+ SubstanceImageCreator.paintTextComponentBorder(c, g2d,
+ 0, 0, width, height, radius, baseBorderScheme);
+ g2d.dispose();
+ smallImageCache.put(baseKey, baseLayer);
+ }
+
+ graphics.setComposite(AlphaComposite.SrcOver
+ .derive(baseAlpha));
+ graphics.drawImage(baseLayer, x, y, null);
+
+ if (!currState.isDisabled() && (activeStates.size() > 1)) {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue()
+ .getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(componentForTransitions,
+ ColorSchemeAssociationKind.BORDER,
+ activeState);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(
+ SubstanceSizeUtils.getComponentFontSize(c),
+ width, height, radius, borderScheme
+ .getDisplayName());
+ BufferedImage layer = smallImageCache.get(key);
+ float alpha = SubstanceColorSchemeUtilities
+ .getAlpha(c, activeState);
+
+ if (layer == null) {
+ layer = SubstanceCoreUtilities.getBlankImage(
+ width, height);
+ Graphics2D g2d = layer.createGraphics();
+ SubstanceImageCreator.paintTextComponentBorder(
+ c, g2d, 0, 0, width, height, radius,
+ borderScheme);
+ g2d.dispose();
+ smallImageCache.put(key, layer);
+ }
+
+ graphics.setComposite(AlphaComposite.SrcOver
+ .derive(alpha * contribution));
+ graphics.drawImage(layer, x, y, null);
+ }
+ }
+
+ } else {
+ // for borders larger than 100000 pixels, use simple
+ // painting
+ graphics.translate(x, y);
+
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(componentForTransitions,
+ ColorSchemeAssociationKind.BORDER,
+ currState);
+ float baseAlpha = SubstanceColorSchemeUtilities.getAlpha(c,
+ currState);
+ graphics.setComposite(AlphaComposite.SrcOver
+ .derive(baseAlpha));
+ SubstanceImageCreator.paintSimpleBorder(c, graphics, width,
+ height, baseBorderScheme);
+
+ if (!currState.isDisabled() && (activeStates.size() > 1)) {
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue()
+ .getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(componentForTransitions,
+ ColorSchemeAssociationKind.BORDER,
+ activeState);
+ float alpha = SubstanceColorSchemeUtilities
+ .getAlpha(c, activeState);
+ graphics.setComposite(AlphaComposite.SrcOver
+ .derive(alpha * contribution));
+ SubstanceImageCreator.paintSimpleBorder(c,
+ graphics, width, height, borderScheme);
+ }
+ }
+ }
+ }
+ } else {
+ ComponentState currState = isEnabled ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED;
+ SubstanceColorScheme borderColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c, ColorSchemeAssociationKind.BORDER,
+ currState);
+ if (width * height < 100000) {
+ HashMapKey hashKey = SubstanceCoreUtilities.getHashKey(
+ SubstanceSizeUtils.getComponentFontSize(c), width,
+ height, radius, borderColorScheme.getDisplayName());
+ BufferedImage result = smallImageCache.get(hashKey);
+ if (result == null) {
+ result = SubstanceCoreUtilities
+ .getBlankImage(width, height);
+ Graphics2D g2d = result.createGraphics();
+ SubstanceImageCreator.paintTextComponentBorder(c, g2d, 0,
+ 0, width, height, radius, borderColorScheme);
+ g2d.dispose();
+ smallImageCache.put(hashKey, result);
+ }
+ graphics.drawImage(result, x, y, null);
+ } else {
+ // for borders larger than 100000 pixels, use simple
+ // painting
+ graphics.translate(x, y);
+ SubstanceImageCreator.paintSimpleBorder(c, graphics, width,
+ height, borderColorScheme);
+ }
+ }
+
+ graphics.dispose();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#paintBorder(java.awt.Component,
+ * java.awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height) {
+ paintBorder((JComponent) c, g, x, y, width, height, c.isEnabled(), c
+ .hasFocus());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#getBorderInsets(java.awt.Component)
+ */
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return this.myInsets;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#isBorderOpaque()
+ */
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceToolBarBorder.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceToolBarBorder.java
new file mode 100644
index 0000000..0f5ea8e
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/border/SubstanceToolBarBorder.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.border;
+
+import java.awt.*;
+
+import javax.swing.JToolBar;
+import javax.swing.SwingConstants;
+import javax.swing.border.AbstractBorder;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Border for toolbar.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceToolBarBorder extends AbstractBorder implements
+ UIResource {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#paintBorder(java.awt.Component,
+ * java.awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) {
+ // failsafe for LAF change
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return;
+
+ Graphics2D graphics = (Graphics2D) g;
+ graphics.translate(x, y);
+
+ if (((JToolBar) c).isFloatable()) {
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c, ColorSchemeAssociationKind.SEPARATOR,
+ ComponentState.ENABLED);
+ int dragBumpsWidth = (int) (0.75 * SubstanceSizeUtils
+ .getToolBarDragInset(SubstanceSizeUtils
+ .getComponentFontSize(c)));
+ if (((JToolBar) c).getOrientation() == SwingConstants.HORIZONTAL) {
+ // fix for defect 3 on NB module
+ int height = c.getHeight() - 4;
+ if (height > 0) {
+ if (c.getComponentOrientation().isLeftToRight()) {
+ graphics.drawImage(SubstanceImageCreator.getDragImage(
+ c, scheme, dragBumpsWidth, height, 2), 2, 1,
+ null);
+ } else {
+ graphics.drawImage(SubstanceImageCreator.getDragImage(
+ c, scheme, dragBumpsWidth, height, 2), c
+ .getBounds().width
+ - dragBumpsWidth - 2, 1, null);
+ }
+ }
+ } else // vertical
+ {
+ // fix for defect 3 on NB module
+ int width = c.getWidth() - 4;
+ if (width > 0) {
+ graphics.drawImage(SubstanceImageCreator.getDragImage(c,
+ scheme, width, dragBumpsWidth, 2), 2, 2, null);
+ }
+ }
+ }
+
+ graphics.translate(-x, -y);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#getBorderInsets(java.awt.Component)
+ */
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return this.getBorderInsets(c, new Insets(0, 0, 0, 0));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * javax.swing.border.AbstractBorder#getBorderInsets(java.awt.Component,
+ * java.awt.Insets)
+ */
+ @Override
+ public Insets getBorderInsets(Component c, Insets newInsets) {
+ Insets defaultInsets = SubstanceSizeUtils
+ .getToolBarInsets(SubstanceSizeUtils.getComponentFontSize(c));
+ newInsets.set(defaultInsets.top, defaultInsets.left,
+ defaultInsets.bottom, defaultInsets.right);
+
+ JToolBar toolbar = (JToolBar) c;
+ if (toolbar.isFloatable()) {
+ int dragInset = SubstanceSizeUtils
+ .getToolBarDragInset(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ if (toolbar.getOrientation() == SwingConstants.HORIZONTAL) {
+ if (toolbar.getComponentOrientation().isLeftToRight()) {
+ newInsets.left = dragInset;
+ } else {
+ newInsets.right = dragInset;
+ }
+ } else {// vertical
+ newInsets.top = dragInset;
+ }
+ }
+
+ Insets margin = toolbar.getMargin();
+
+ if (margin != null) {
+ newInsets.left += margin.left;
+ newInsets.top += margin.top;
+ newInsets.right += margin.right;
+ newInsets.bottom += margin.bottom;
+ }
+
+ return newInsets;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/combo/ComboBoxBackgroundDelegate.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/combo/ComboBoxBackgroundDelegate.java
new file mode 100644
index 0000000..75c3ff8
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/combo/ComboBoxBackgroundDelegate.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.combo;
+
+import java.awt.AlphaComposite;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+import javax.swing.AbstractButton;
+import javax.swing.ButtonModel;
+import javax.swing.JComboBox;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.HashMapKey;
+import org.pushingpixels.substance.internal.utils.LazyResettableHashMap;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceOutlineUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+/**
+ * Delegate class for painting backgrounds of buttons in <b>Substance </b> look
+ * and feel. This class is <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ComboBoxBackgroundDelegate {
+ /**
+ * Cache for background images. Each time
+ * {@link #getFullAlphaBackground(javax.swing.JComboBox, javax.swing.ButtonModel, org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter, org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter, int, int)}
+ * is called, it checks <code>this</code> map to see if it already contains
+ * such background. If so, the background from the map is returned.
+ */
+ private static LazyResettableHashMap<BufferedImage> regularBackgrounds = new LazyResettableHashMap<BufferedImage>(
+ "ComboBoxBackgroundDelegate");
+
+ /**
+ * Retrieves the background for the specified button.
+ *
+ * @param combo
+ * combo box.
+ * @param model
+ * Button model.
+ * @param fillPainter
+ * Button fill painter.
+ * @param borderPainter
+ * Button border painter.
+ * @param width
+ * Button width.
+ * @param height
+ * Button height.
+ * @return Button background.
+ */
+ public static BufferedImage getFullAlphaBackground(JComboBox combo,
+ ButtonModel model, SubstanceFillPainter fillPainter,
+ SubstanceBorderPainter borderPainter, int width, int height) {
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) combo.getUI();
+ StateTransitionTracker.ModelStateInfo modelStateInfo = transitionAwareUI
+ .getTransitionTracker().getModelStateInfo();
+ ComponentState currState = modelStateInfo.getCurrModelState();
+
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ ClassicButtonShaper shaper = ClassicButtonShaper.INSTANCE;
+ int comboFontSize = SubstanceSizeUtils.getComponentFontSize(combo);
+ float radius = SubstanceSizeUtils
+ .getClassicButtonCornerRadius(comboFontSize);
+
+ SubstanceColorScheme baseFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(combo, currState);
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(combo, ColorSchemeAssociationKind.BORDER,
+ currState);
+
+ HashMapKey keyBase = SubstanceCoreUtilities.getHashKey(width, height,
+ baseFillScheme.getDisplayName(), baseBorderScheme
+ .getDisplayName(), fillPainter.getDisplayName(),
+ borderPainter.getDisplayName(), combo.getClass().getName(),
+ radius, comboFontSize);
+ BufferedImage layerBase = regularBackgrounds.get(keyBase);
+ if (layerBase == null) {
+ layerBase = createBackgroundImage(combo, shaper, fillPainter,
+ borderPainter, width, height, baseFillScheme,
+ baseBorderScheme, radius);
+ regularBackgrounds.put(keyBase, layerBase);
+ }
+ if (currState.isDisabled() || (activeStates.size() == 1)) {
+ return layerBase;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D g2d = result.createGraphics();
+ // draw the base layer
+ g2d.drawImage(layerBase, 0, 0, null);
+ // System.out.println("\nPainting base state " + currState);
+
+ // draw the other active layers
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ // System.out.println("Painting state " + activeState + "[curr is "
+ // + currState + "] with " + activeEntry.getValue());
+ if (activeState == currState)
+ continue;
+
+ float stateContribution = activeEntry.getValue().getContribution();
+ if (stateContribution > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(stateContribution));
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(combo, activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(combo,
+ ColorSchemeAssociationKind.BORDER, activeState);
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(width,
+ height, fillScheme.getDisplayName(), borderScheme
+ .getDisplayName(),
+ fillPainter.getDisplayName(), borderPainter
+ .getDisplayName(), combo.getClass().getName(),
+ radius, comboFontSize);
+ BufferedImage layer = regularBackgrounds.get(key);
+ if (layer == null) {
+ layer = createBackgroundImage(combo, shaper, fillPainter,
+ borderPainter, width, height, fillScheme,
+ borderScheme, radius);
+ regularBackgrounds.put(key, layer);
+ }
+ g2d.drawImage(layer, 0, 0, null);
+ }
+ }
+ g2d.dispose();
+ return result;
+ }
+
+ private static BufferedImage createBackgroundImage(JComboBox combo,
+ SubstanceButtonShaper shaper, SubstanceFillPainter fillPainter,
+ SubstanceBorderPainter borderPainter, int width, int height,
+ SubstanceColorScheme fillScheme, SubstanceColorScheme borderScheme,
+ float radius) {
+ int comboFontSize = SubstanceSizeUtils.getComponentFontSize(combo);
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(comboFontSize) / 2.0);
+ Shape contour = SubstanceOutlineUtilities.getBaseOutline(width, height,
+ radius, null, borderDelta);
+
+ BufferedImage newBackground = SubstanceCoreUtilities.getBlankImage(
+ width, height);
+ Graphics2D finalGraphics = (Graphics2D) newBackground.getGraphics();
+ fillPainter.paintContourBackground(finalGraphics, combo, width, height,
+ contour, false, fillScheme, true);
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(comboFontSize);
+ Shape contourInner = borderPainter.isPaintingInnerContour() ? SubstanceOutlineUtilities
+ .getBaseOutline(width, height, radius - borderThickness, null,
+ borderDelta + borderThickness)
+ : null;
+ borderPainter.paintBorder(finalGraphics, combo, width, height, contour,
+ contourInner, borderScheme);
+ return newBackground;
+ }
+
+ /**
+ * Simple constructor.
+ */
+ public ComboBoxBackgroundDelegate() {
+ super();
+ }
+
+ /**
+ * Updates background of the specified button.
+ *
+ * @param g
+ * Graphic context.
+ * @param combo
+ * Combo Box to update.
+ */
+ public void updateBackground(Graphics g, JComboBox combo,
+ ButtonModel comboModel) {
+ // failsafe for LAF change
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return;
+
+ int width = combo.getWidth();
+ int height = combo.getHeight();
+ int y = 0;
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(combo);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(combo);
+
+ BufferedImage bgImage = getFullAlphaBackground(combo, comboModel,
+ fillPainter, borderPainter, width, height);
+
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) combo.getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ // Two special cases here:
+ // 1. Combobox has flat appearance.
+ // 2. Combobox is disabled.
+ // For both cases, we need to set custom translucency.
+ boolean isFlat = SubstanceCoreUtilities.hasFlatAppearance(combo, false);
+ boolean isSpecial = isFlat || !combo.isEnabled();
+ float extraAlpha = 1.0f;
+ if (isSpecial) {
+ if (isFlat) {
+ // Special handling of flat combos
+ extraAlpha = 0.0f;
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState.isDisabled())
+ continue;
+ if (activeState == ComponentState.ENABLED)
+ continue;
+ extraAlpha += activeEntry.getValue().getContribution();
+ }
+ } else {
+ if (!combo.isEnabled()) {
+ extraAlpha = SubstanceColorSchemeUtilities.getAlpha(combo,
+ modelStateInfo.getCurrModelState());
+ }
+ }
+ }
+ if (extraAlpha > 0.0f) {
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(combo,
+ extraAlpha, g));
+ graphics.drawImage(bgImage, 0, y, null);
+ graphics.dispose();
+ }
+ }
+
+ /**
+ * Returns the memory usage string.
+ *
+ * @return Memory usage string.
+ */
+ static String getMemoryUsage() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("SubstanceBackgroundDelegate: \n");
+ sb.append("\t" + regularBackgrounds.size() + " regular");
+ // + pairwiseBackgrounds.size() + " pairwise");
+ return sb.toString();
+ }
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/combo/SubstanceComboBoxEditor.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/combo/SubstanceComboBoxEditor.java
new file mode 100644
index 0000000..5da2725
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/combo/SubstanceComboBoxEditor.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.combo;
+
+import javax.swing.JTextField;
+import javax.swing.plaf.basic.BasicComboBoxEditor;
+
+/**
+ * Combobox editor.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceComboBoxEditor extends BasicComboBoxEditor {
+ /**
+ * Creates a new editor.
+ */
+ public SubstanceComboBoxEditor() {
+ super();
+ this.editor = new JTextField("", 9);
+ this.editor.setBorder(null);
+ this.editor.setOpaque(false);
+ }
+
+ /**
+ * Combobox editor that implements the marker
+ * {@link javax.swing.plaf.UIResource} interface.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class UIResource extends SubstanceComboBoxEditor implements
+ javax.swing.plaf.UIResource {
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/combo/SubstanceComboPopup.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/combo/SubstanceComboPopup.java
new file mode 100755
index 0000000..9ad86a9
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/combo/SubstanceComboPopup.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.combo;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GraphicsConfiguration;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+
+import javax.swing.JComboBox;
+import javax.swing.JScrollBar;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.plaf.basic.BasicComboPopup;
+
+import org.pushingpixels.substance.internal.ui.SubstanceListUI;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.border.SubstanceBorder;
+
+/**
+ * Combo popup implementation in <b>Substance</b> look-and-feel. This class is
+ * <b>for internal use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceComboPopup extends BasicComboPopup {
+
+ /**
+ * Creates combo popup for the specified combobox.
+ *
+ * @param combo
+ * Combobox.
+ */
+ public SubstanceComboPopup(JComboBox combo) {
+ super(combo);
+ // fix for defect 154
+ this.setOpaque(true);
+ this.list.setBackground(combo.getBackground());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboPopup#configurePopup()
+ */
+ @Override
+ protected void configurePopup() {
+ super.configurePopup();
+ setBorder(new SubstanceBorder(new Insets(0, 2, 2, 2)));
+ }
+
+ /**
+ * Sets the list selection index to the selectedIndex. This method is used
+ * to synchronize the list selection with the combo box selection.
+ *
+ * @param selectedIndex
+ * the index to set the list
+ */
+ private void setListSelection(int selectedIndex) {
+ if (selectedIndex == -1) {
+ this.list.clearSelection();
+ } else {
+ this.list.setSelectedIndex(selectedIndex);
+ this.list.ensureIndexIsVisible(selectedIndex);
+ }
+ }
+
+ /**
+ * Calculates the upper left location of the popup.
+ *
+ * @return The upper left location of the popup.
+ */
+ private Point getPopupLocation() {
+ Dimension popupSize = this.comboBox.getSize();
+ Insets insets = this.getInsets();
+
+ // reduce the width of the scrollpane by the insets so that the popup
+ // is the same width as the combo box.
+ popupSize.setSize(popupSize.width - (insets.right + insets.left), this
+ .getPopupHeightForRowCount(this.comboBox.getMaximumRowCount()));
+ Rectangle popupBounds = this.computePopupBounds(0, this.comboBox
+ .getBounds().height, popupSize.width, popupSize.height);
+ Dimension scrollSize = popupBounds.getSize();
+ Point popupLocation = popupBounds.getLocation();
+
+ this.scroller.setMaximumSize(scrollSize);
+ this.scroller.setPreferredSize(scrollSize);
+ this.scroller.setMinimumSize(scrollSize);
+
+ this.list.revalidate();
+
+ return new Point(popupLocation.x, popupLocation.y);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboPopup#computePopupBounds(int, int,
+ * int, int)
+ */
+ @Override
+ protected Rectangle computePopupBounds(int px, int py, int pw, int ph) {
+ int popupFlyoutOrientation = SubstanceCoreUtilities
+ .getPopupFlyoutOrientation(this.comboBox);
+ Insets insets = this.getInsets();
+ int dx = 0;
+ int dy = 0;
+ switch (popupFlyoutOrientation) {
+ case SwingConstants.NORTH:
+ dy = -ph - (int) this.comboBox.getSize().getHeight() - insets.top
+ - insets.bottom;
+ break;
+ case SwingConstants.CENTER:
+ dy = -ph / 2 - (int) this.comboBox.getSize().getHeight() / 2
+ - insets.top / 2 - insets.bottom / 2;
+ break;
+ case SwingConstants.EAST:
+ dx = pw + insets.left + insets.right;
+ dy = -(int) this.comboBox.getSize().getHeight();
+ break;
+ case SwingConstants.WEST:
+ dx = -pw - insets.left - insets.right;
+ dy = -(int) this.comboBox.getSize().getHeight();
+ }
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ Rectangle screenBounds;
+
+ // Calculate the desktop dimensions relative to the combo box.
+ GraphicsConfiguration gc = this.comboBox.getGraphicsConfiguration();
+ Point p = new Point();
+ SwingUtilities.convertPointFromScreen(p, this.comboBox);
+ if (gc != null) {
+ Insets screenInsets = toolkit.getScreenInsets(gc);
+ screenBounds = gc.getBounds();
+ screenBounds.width -= (screenInsets.left + screenInsets.right);
+ screenBounds.height -= (screenInsets.top + screenInsets.bottom);
+ screenBounds.x += (p.x + screenInsets.left);
+ screenBounds.y += (p.y + screenInsets.top);
+ } else {
+ screenBounds = new Rectangle(p, toolkit.getScreenSize());
+ }
+
+ Rectangle rect = new Rectangle(px + dx, py + dy, pw, ph);
+ if ((py + ph > screenBounds.y + screenBounds.height)
+ && (ph < screenBounds.height)) {
+ rect.y = -rect.height - insets.top - insets.bottom;
+ }
+
+ // The following has been taken from JGoodies' Looks implementation
+ // for the popup prototype value
+ Object popupPrototypeDisplayValue = SubstanceCoreUtilities
+ .getComboPopupPrototypeDisplayValue(this.comboBox);
+ if (popupPrototypeDisplayValue != null) {
+ ListCellRenderer renderer = this.list.getCellRenderer();
+ Component c = renderer.getListCellRendererComponent(this.list,
+ popupPrototypeDisplayValue, -1, true, true);
+ int npw = c.getPreferredSize().width;
+ boolean hasVerticalScrollBar = this.comboBox.getItemCount() > this.comboBox
+ .getMaximumRowCount();
+ if (hasVerticalScrollBar) {
+ // Add the scrollbar width.
+ JScrollBar verticalBar = this.scroller.getVerticalScrollBar();
+ npw += verticalBar.getPreferredSize().width;
+ }
+
+ pw = Math.max(pw, npw);
+ rect.width = pw;
+ }
+
+ return rect;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.plaf.basic.BasicComboPopup#hide()
+ */
+ @Override
+ public void hide() {
+ super.hide();
+ SubstanceListUI ui = (SubstanceListUI) this.list.getUI();
+ ui.resetRolloverIndex();
+ // this.list.putClientProperty(SubstanceListUI.ROLLED_OVER_INDEX, null);
+ }
+
+ public JComboBox getCombobox() {
+ return this.comboBox;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/AbstractFilter.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/AbstractFilter.java
new file mode 100644
index 0000000..d043b31
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/AbstractFilter.java
@@ -0,0 +1,162 @@
+/*
+ * $Id: AbstractFilter.java 2254 2009-10-15 04:00:12Z kirillcool $
+ *
+ * Dual-licensed under LGPL (Sun and Romain Guy) and BSD (Romain Guy).
+ *
+ * Copyright 2005 Sun Microsystems, Inc., 4150 Network Circle,
+ * Santa Clara, California 95054, U.S.A. All rights reserved.
+ *
+ * Copyright (c) 2006 Romain Guy <romain.guy at mac.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.substance.internal.utils.filters;
+
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.*;
+
+/**
+ * <p>
+ * Provides an abstract implementation of the <code>BufferedImageOp</code>
+ * interface. This class can be used to created new image filters based on
+ * <code>BufferedImageOp</code>.
+ * </p>
+ *
+ * @author Romain Guy <romain.guy at mac.com>
+ */
+
+public abstract class AbstractFilter implements BufferedImageOp {
+ @Override
+ public abstract BufferedImage filter(BufferedImage src, BufferedImage dest);
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Rectangle2D getBounds2D(BufferedImage src) {
+ return new Rectangle(0, 0, src.getWidth(), src.getHeight());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BufferedImage createCompatibleDestImage(BufferedImage src,
+ ColorModel destCM) {
+ if (destCM == null) {
+ destCM = src.getColorModel();
+ }
+
+ return new BufferedImage(destCM, destCM.createCompatibleWritableRaster(
+ src.getWidth(), src.getHeight()),
+ destCM.isAlphaPremultiplied(), null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
+ return (Point2D) srcPt.clone();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public RenderingHints getRenderingHints() {
+ return null;
+ }
+
+ protected int[] getPixels(BufferedImage img, int x, int y, int w, int h,
+ int[] pixels) {
+ if (w == 0 || h == 0) {
+ return new int[0];
+ }
+
+ if (pixels == null) {
+ pixels = new int[w * h];
+ } else if (pixels.length < w * h) {
+ throw new IllegalArgumentException(
+ "pixels array must have a length" + " >= w*h");
+ }
+
+ int imageType = img.getType();
+ if (imageType == BufferedImage.TYPE_INT_ARGB
+ || imageType == BufferedImage.TYPE_INT_RGB) {
+ Raster raster = img.getRaster();
+ return (int[]) raster.getDataElements(x, y, w, h, pixels);
+ }
+
+ // Unmanages the image
+ return img.getRGB(x, y, w, h, pixels, 0, w);
+ }
+
+ /**
+ * <p>
+ * Writes a rectangular area of pixels in the destination
+ * <code>BufferedImage</code>. Calling this method on an image of type
+ * different from <code>BufferedImage.TYPE_INT_ARGB</code> and
+ * <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.
+ * </p>
+ *
+ * @param img
+ * the destination image
+ * @param x
+ * the x location at which to start storing pixels
+ * @param y
+ * the y location at which to start storing pixels
+ * @param w
+ * the width of the rectangle of pixels to store
+ * @param h
+ * the height of the rectangle of pixels to store
+ * @param pixels
+ * an array of pixels, stored as integers
+ * @throws IllegalArgumentException
+ * is <code>pixels</code> is non-null and of length < w*h
+ */
+ protected void setPixels(BufferedImage img, int x, int y, int w, int h,
+ int[] pixels) {
+ if (pixels == null || w == 0 || h == 0) {
+ return;
+ } else if (pixels.length < w * h) {
+ throw new IllegalArgumentException(
+ "pixels array must have a length" + " >= w*h");
+ }
+
+ int imageType = img.getType();
+ if (imageType == BufferedImage.TYPE_INT_ARGB
+ || imageType == BufferedImage.TYPE_INT_RGB) {
+ WritableRaster raster = img.getRaster();
+ raster.setDataElements(x, y, w, h, pixels);
+ } else {
+ // Unmanages the image
+ img.setRGB(x, y, w, h, pixels, 0, w);
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/ColorSchemeFilter.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/ColorSchemeFilter.java
new file mode 100644
index 0000000..d30d99e
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/ColorSchemeFilter.java
@@ -0,0 +1,212 @@
+/*
+ * $Id: ColorSchemeFilter.java 2353 2009-12-11 04:57:29Z kirillcool $
+ *
+ * Dual-licensed under LGPL (Sun and Romain Guy) and BSD (Romain Guy).
+ *
+ * Copyright 2005 Sun Microsystems, Inc., 4150 Network Circle,
+ * Santa Clara, California 95054, U.S.A. All rights reserved.
+ *
+ * Copyright (c) 2006 Romain Guy <romain.guy at mac.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.substance.internal.utils.filters;
+
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+import java.util.*;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * @author Romain Guy <romain.guy at mac.com>
+ * @author Kirill Grouchnikov
+ */
+
+public class ColorSchemeFilter extends AbstractFilter {
+ private int[] interpolated;
+
+ public static final int MAPSTEPS = 512;
+
+ protected final static LazyResettableHashMap<ColorSchemeFilter> filters = new LazyResettableHashMap<ColorSchemeFilter>(
+ "ColorSchemeFilter");
+
+ protected float originalBrightnessFactor;
+
+ public static ColorSchemeFilter getColorSchemeFilter(
+ SubstanceColorScheme scheme, float originalBrightnessFactor) {
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(scheme
+ .getDisplayName(), originalBrightnessFactor);
+ ColorSchemeFilter filter = filters.get(key);
+ if (filter == null) {
+ filter = new ColorSchemeFilter(scheme, originalBrightnessFactor);
+ filters.put(key, filter);
+ }
+ return filter;
+ }
+
+ /**
+ * @throws IllegalArgumentException
+ * if <code>scheme</code> is null
+ */
+ private ColorSchemeFilter(SubstanceColorScheme scheme,
+ float originalBrightnessFactor) {
+ if (scheme == null) {
+ throw new IllegalArgumentException("mixColor cannot be null");
+ }
+
+ this.originalBrightnessFactor = originalBrightnessFactor;
+
+ // collect the brightness factors of the color scheme
+ Map<Integer, Color> schemeColorMapping = new TreeMap<Integer, Color>();
+ schemeColorMapping.put(SubstanceColorUtilities
+ .getColorBrightness(scheme.getUltraLightColor().getRGB()),
+ scheme.getUltraLightColor());
+ schemeColorMapping.put(SubstanceColorUtilities
+ .getColorBrightness(scheme.getExtraLightColor().getRGB()),
+ scheme.getExtraLightColor());
+ schemeColorMapping.put(SubstanceColorUtilities
+ .getColorBrightness(scheme.getLightColor().getRGB()), scheme
+ .getLightColor());
+ schemeColorMapping.put(SubstanceColorUtilities
+ .getColorBrightness(scheme.getMidColor().getRGB()), scheme
+ .getMidColor());
+ schemeColorMapping.put(SubstanceColorUtilities
+ .getColorBrightness(scheme.getDarkColor().getRGB()), scheme
+ .getDarkColor());
+ schemeColorMapping.put(SubstanceColorUtilities
+ .getColorBrightness(scheme.getUltraDarkColor().getRGB()),
+ scheme.getUltraDarkColor());
+
+ List<Integer> schemeBrightness = new ArrayList<Integer>();
+ schemeBrightness.addAll(schemeColorMapping.keySet());
+ Collections.sort(schemeBrightness);
+
+ int lowestSchemeBrightness = schemeBrightness.get(0);
+ int highestSchemeBrightness = schemeBrightness.get(schemeBrightness
+ .size() - 1);
+ boolean hasSameBrightness = (highestSchemeBrightness == lowestSchemeBrightness);
+
+ Map<Integer, Color> stretchedColorMapping = new TreeMap<Integer, Color>();
+ for (Map.Entry<Integer, Color> entry : schemeColorMapping.entrySet()) {
+ int brightness = entry.getKey();
+ int stretched = hasSameBrightness ? brightness : 255 - 255
+ * (highestSchemeBrightness - brightness)
+ / (highestSchemeBrightness - lowestSchemeBrightness);
+ stretchedColorMapping.put(stretched, entry.getValue());
+ }
+ schemeBrightness = new ArrayList<Integer>();
+ schemeBrightness.addAll(stretchedColorMapping.keySet());
+ Collections.sort(schemeBrightness);
+
+ this.interpolated = new int[MAPSTEPS];
+ for (int i = 0; i < MAPSTEPS; i++) {
+ int brightness = (int) (256.0 * i / MAPSTEPS);
+ if (schemeBrightness.contains(brightness)) {
+ this.interpolated[i] = stretchedColorMapping.get(brightness)
+ .getRGB();
+ } else {
+ if (hasSameBrightness) {
+ this.interpolated[i] = stretchedColorMapping.get(
+ lowestSchemeBrightness).getRGB();
+ } else {
+ int currIndex = 0;
+ while (true) {
+ int currStopValue = schemeBrightness.get(currIndex);
+ int nextStopValue = schemeBrightness.get(currIndex + 1);
+ if ((brightness > currStopValue)
+ && (brightness < nextStopValue)) {
+ // interpolate
+ Color currStopColor = stretchedColorMapping
+ .get(currStopValue);
+ Color nextStopColor = stretchedColorMapping
+ .get(nextStopValue);
+ this.interpolated[i] = SubstanceColorUtilities
+ .getInterpolatedRGB(
+ currStopColor,
+ nextStopColor,
+ 1.0
+ - (double) (brightness - currStopValue)
+ / (double) (nextStopValue - currStopValue));
+ break;
+ }
+ currIndex++;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BufferedImage filter(BufferedImage src, BufferedImage dst) {
+ if (dst == null) {
+ dst = createCompatibleDestImage(src, null);
+ }
+
+ int width = src.getWidth();
+ int height = src.getHeight();
+
+ int[] pixels = new int[width * height];
+ getPixels(src, 0, 0, width, height, pixels);
+ mixColor(pixels);
+ setPixels(dst, 0, 0, width, height, pixels);
+
+ return dst;
+ }
+
+ private void mixColor(int[] pixels) {
+ for (int i = 0; i < pixels.length; i++) {
+ int argb = pixels[i];
+
+ int brightness = SubstanceColorUtilities.getColorBrightness(argb);
+
+ int r = (argb >>> 16) & 0xFF;
+ int g = (argb >>> 8) & 0xFF;
+ int b = (argb >>> 0) & 0xFF;
+
+ float[] hsb = Color.RGBtoHSB(r, g, b, null);
+ int pixelColor = interpolated[brightness * MAPSTEPS / 256];
+
+ int ri = (pixelColor >>> 16) & 0xFF;
+ int gi = (pixelColor >>> 8) & 0xFF;
+ int bi = (pixelColor >>> 0) & 0xFF;
+ float[] hsbi = Color.RGBtoHSB(ri, gi, bi, null);
+
+ hsb[0] = hsbi[0];
+ hsb[1] = hsbi[1];
+ hsb[2] = this.originalBrightnessFactor * hsb[2]
+ + (1.0f - this.originalBrightnessFactor) * hsbi[2];
+
+ int result = Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]);
+
+ pixels[i] = (argb & 0xFF000000) | ((result >> 16) & 0xFF) << 16
+ | ((result >> 8) & 0xFF) << 8 | (result & 0xFF);
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/GrayscaleFilter.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/GrayscaleFilter.java
new file mode 100644
index 0000000..be8ec3a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/GrayscaleFilter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.filters;
+
+import java.awt.image.BufferedImage;
+
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * @author Kirill Grouchnikov
+ */
+public class GrayscaleFilter extends AbstractFilter {
+ /**
+ * @throws IllegalArgumentException
+ * if <code>scheme</code> is null
+ */
+ public GrayscaleFilter() {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BufferedImage filter(BufferedImage src, BufferedImage dst) {
+ if (dst == null) {
+ dst = createCompatibleDestImage(src, null);
+ }
+
+ int width = src.getWidth();
+ int height = src.getHeight();
+
+ int[] pixels = new int[width * height];
+ getPixels(src, 0, 0, width, height, pixels);
+ grayScaleColor(pixels);
+ setPixels(dst, 0, 0, width, height, pixels);
+
+ return dst;
+ }
+
+ private void grayScaleColor(int[] pixels) {
+ for (int i = 0; i < pixels.length; i++) {
+ int argb = pixels[i];
+ int brightness = SubstanceColorUtilities.getColorBrightness(argb);
+ pixels[i] = (argb & 0xFF000000) | brightness << 16
+ | brightness << 8 | brightness;
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/NegatedFilter.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/NegatedFilter.java
new file mode 100644
index 0000000..1339606
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/NegatedFilter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.filters;
+
+import java.awt.image.BufferedImage;
+
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+
+/**
+ * @author Kirill Grouchnikov
+ */
+public class NegatedFilter extends AbstractFilter {
+ /**
+ * @throws IllegalArgumentException
+ * if <code>scheme</code> is null
+ */
+ public NegatedFilter() {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BufferedImage filter(BufferedImage src, BufferedImage dst) {
+ if (dst == null) {
+ dst = createCompatibleDestImage(src, null);
+ }
+
+ int width = src.getWidth();
+ int height = src.getHeight();
+
+ int[] pixels = new int[width * height];
+ getPixels(src, 0, 0, width, height, pixels);
+ negateColor(pixels);
+ setPixels(dst, 0, 0, width, height, pixels);
+
+ return dst;
+ }
+
+ private void negateColor(int[] pixels) {
+ for (int i = 0; i < pixels.length; i++) {
+ int argb = pixels[i];
+ pixels[i] = SubstanceColorUtilities.getNegativeColor(argb);
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/TranslucentFilter.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/TranslucentFilter.java
new file mode 100644
index 0000000..96331b9
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/filters/TranslucentFilter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.filters;
+
+import java.awt.image.BufferedImage;
+
+/**
+ * @author Kirill Grouchnikov
+ */
+public class TranslucentFilter extends AbstractFilter {
+ private double alpha;
+
+ public TranslucentFilter(double alpha) {
+ this.alpha = alpha;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BufferedImage filter(BufferedImage src, BufferedImage dst) {
+ if (dst == null) {
+ dst = createCompatibleDestImage(src, null);
+ }
+
+ int width = src.getWidth();
+ int height = src.getHeight();
+
+ int[] pixels = new int[width * height];
+ getPixels(src, 0, 0, width, height, pixels);
+ translucentColor(pixels);
+ setPixels(dst, 0, 0, width, height, pixels);
+
+ return dst;
+ }
+
+ private void translucentColor(int[] pixels) {
+ for (int i = 0; i < pixels.length; i++) {
+ int argb = pixels[i];
+ int transp = (int) (alpha * ((argb >>> 24) & 0xFF));
+ int r = (argb >>> 16) & 0xFF;
+ int g = (argb >>> 8) & 0xFF;
+ int b = (argb >>> 0) & 0xFF;
+
+ pixels[i] = (transp << 24) | (r << 16) | (g << 8) | b;
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/ArrowButtonTransitionAwareIcon.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/ArrowButtonTransitionAwareIcon.java
new file mode 100644
index 0000000..7ecf1ae
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/ArrowButtonTransitionAwareIcon.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.icon;
+
+import java.awt.AlphaComposite;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+import javax.swing.AbstractButton;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JComponent;
+import javax.swing.JMenu;
+import javax.swing.SwingConstants;
+
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.ComponentStateFacet;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.HashMapKey;
+import org.pushingpixels.substance.internal.utils.LazyResettableHashMap;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+/**
+ * Transition aware implementation of arrow button icons. Used for implementing
+ * icons of scroll bar buttons, combobox buttons, menus and more.
+ *
+ * @author Kirill Grouchnikov
+ */
+ at TransitionAware
+public class ArrowButtonTransitionAwareIcon implements Icon {
+ /**
+ * Icon cache to speed up the subsequent icon painting. The basic assumption
+ * is that the {@link #delegate} returns an icon that paints the same for
+ * the same parameters.
+ */
+ private static LazyResettableHashMap<Icon> iconMap = new LazyResettableHashMap<Icon>(
+ "ButtonArrowTransitionAwareIcon");
+
+ /**
+ * Arrow icon orientation. Must be one of {@link SwingConstants#NORTH},
+ * {@link SwingConstants#SOUTH}, {@link SwingConstants#EAST} or
+ * {@link SwingConstants#WEST}.
+ */
+ private int orientation;
+
+ /**
+ * Icon width.
+ */
+ protected int iconWidth;
+
+ /**
+ * Icon height.
+ */
+ protected int iconHeight;
+
+ /**
+ * Delegate to compute the actual icons.
+ */
+ protected TransitionAwareIcon.Delegate delegate;
+
+ protected JComponent component;
+
+ private TransitionAwareIcon.TransitionAwareUIDelegate transitionAwareUIDelegate;
+
+ public ArrowButtonTransitionAwareIcon(final AbstractButton button,
+ int orientation) {
+ this(button, new TransitionAwareIcon.TransitionAwareUIDelegate() {
+ @Override
+ public TransitionAwareUI getTransitionAwareUI() {
+ return (TransitionAwareUI) button.getUI();
+ }
+ }, orientation);
+ }
+
+ /**
+ * Creates an arrow icon.
+ *
+ * @param component
+ * Arrow button.
+ * @param orientation
+ * Arrow icon orientation.
+ */
+ public ArrowButtonTransitionAwareIcon(
+ final JComponent component,
+ TransitionAwareIcon.TransitionAwareUIDelegate transitionAwareUIDelegate,
+ final int orientation) {
+ this.component = component;
+ this.transitionAwareUIDelegate = transitionAwareUIDelegate;
+ this.orientation = orientation;
+ this.delegate = new TransitionAwareIcon.Delegate() {
+ @Override
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
+ int fontSize = SubstanceSizeUtils
+ .getComponentFontSize(component);
+ return SubstanceImageCreator.getArrowIcon(fontSize,
+ orientation, scheme);
+ }
+ };
+
+ this.iconWidth = this.delegate.getColorSchemeIcon(
+ SubstanceColorSchemeUtilities.getColorScheme(component,
+ ComponentState.ENABLED)).getIconWidth();
+ this.iconHeight = this.delegate.getColorSchemeIcon(
+ SubstanceColorSchemeUtilities.getColorScheme(component,
+ ComponentState.ENABLED)).getIconHeight();
+ }
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ this.getIconToPaint().paintIcon(c, g, x, y);
+ }
+
+ /**
+ * Returns the icon to be painted for the current state of the button.
+ *
+ * @return Icon to be painted.
+ */
+ private Icon getIconToPaint() {
+ boolean isMenu = (this.component instanceof JMenu);
+ StateTransitionTracker stateTransitionTracker = this.transitionAwareUIDelegate
+ .getTransitionAwareUI().getTransitionTracker();
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = isMenu ? modelStateInfo
+ .getStateNoSelectionContributionMap()
+ : modelStateInfo.getStateContributionMap();
+ ComponentState currState = isMenu ? modelStateInfo
+ .getCurrModelStateNoSelection() : modelStateInfo
+ .getCurrModelState();
+
+ // Use HIGHLIGHT for rollover menus (arrow icons)
+ // and MARK for the rest
+ ColorSchemeAssociationKind baseAssociationKind = isMenu
+ && currState.isFacetActive(ComponentStateFacet.ROLLOVER) ? ColorSchemeAssociationKind.HIGHLIGHT
+ : ColorSchemeAssociationKind.MARK;
+ SubstanceColorScheme baseScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.component, baseAssociationKind, currState);
+ float baseAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ this.component, currState);
+
+ HashMapKey keyBase = SubstanceCoreUtilities.getHashKey(this.component
+ .getClass().getName(), this.orientation, SubstanceSizeUtils
+ .getComponentFontSize(this.component), baseScheme
+ .getDisplayName(), baseAlpha);
+ Icon layerBase = iconMap.get(keyBase);
+ if (layerBase == null) {
+ Icon baseFullOpacity = this.delegate.getColorSchemeIcon(baseScheme);
+ if (baseAlpha == 1.0f) {
+ layerBase = baseFullOpacity;
+ iconMap.put(keyBase, layerBase);
+ } else {
+ BufferedImage baseImage = SubstanceCoreUtilities.getBlankImage(
+ baseFullOpacity.getIconWidth(), baseFullOpacity
+ .getIconHeight());
+ Graphics2D g2base = baseImage.createGraphics();
+ g2base.setComposite(AlphaComposite.SrcOver.derive(baseAlpha));
+ baseFullOpacity.paintIcon(this.component, g2base, 0, 0);
+ g2base.dispose();
+ layerBase = new ImageIcon(baseImage);
+ iconMap.put(keyBase, layerBase);
+ }
+ }
+ // System.out.println("Contribution map in painting");
+ // for (Map.Entry<ComponentState,
+ // StateTransitionTracker.StateContributionInfo> existing : activeStates
+ // .entrySet()) {
+ // System.out.println("\t" + existing.getKey() + " in ["
+ // + existing.getValue().start + ":" + existing.getValue().end
+ // + "] : " + existing.getValue().curr);
+ // }
+
+ if (currState.isDisabled() || (activeStates.size() == 1)) {
+ return layerBase;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(layerBase
+ .getIconWidth(), layerBase.getIconHeight());
+ Graphics2D g2d = result.createGraphics();
+ // draw the base layer
+ // System.out.println("Painting currState " + currState + ":" +
+ // baseAlpha);
+ layerBase.paintIcon(this.component, g2d, 0, 0);
+
+ // draw the other active layers
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ // System.out.println("Painting state " + activeState + "[curr is "
+ // + currState + "] with " + activeEntry.getValue());
+ if (activeState == currState)
+ continue;
+
+ float stateContribution = activeEntry.getValue().getContribution();
+ // System.out.println("Painting activeState "
+ // + activeState
+ // + ":"
+ // + stateContribution
+ // + "*"
+ // + SubstanceColorSchemeUtilities.getAlpha(this.component,
+ // activeState));
+ if (stateContribution > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(stateContribution));
+
+ ColorSchemeAssociationKind associationKind = isMenu
+ && activeState
+ .isFacetActive(ComponentStateFacet.ROLLOVER) ? ColorSchemeAssociationKind.HIGHLIGHT
+ : ColorSchemeAssociationKind.MARK;
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.component, associationKind,
+ activeState);
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(
+ this.component, activeState);
+
+ HashMapKey key = SubstanceCoreUtilities
+ .getHashKey(this.component.getClass().getName(),
+ this.orientation, SubstanceSizeUtils
+ .getComponentFontSize(this.component),
+ scheme.getDisplayName(), alpha);
+ Icon layer = iconMap.get(key);
+ if (layer == null) {
+ Icon fullOpacity = this.delegate.getColorSchemeIcon(scheme);
+ if (alpha == 1.0f) {
+ layer = fullOpacity;
+ iconMap.put(key, layer);
+ } else {
+ BufferedImage image = SubstanceCoreUtilities
+ .getBlankImage(fullOpacity.getIconWidth(),
+ fullOpacity.getIconHeight());
+ Graphics2D g2layer = image.createGraphics();
+ g2layer.setComposite(AlphaComposite.SrcOver
+ .derive(alpha));
+ fullOpacity.paintIcon(this.component, g2layer, 0, 0);
+ g2layer.dispose();
+ layer = new ImageIcon(image);
+ iconMap.put(key, layer);
+ }
+ }
+ layer.paintIcon(this.component, g2d, 0, 0);
+ }
+ }
+ g2d.dispose();
+ return new ImageIcon(result);
+ //
+ //
+ // SubstanceColorScheme currScheme = SubstanceColorSchemeUtilities
+ // .getColorScheme(this.component, currAssociationKind, currState);
+ //
+ // SubstanceColorScheme prevScheme = currScheme;
+ //
+ // // Use HIGHLIGHT for rollover menus (arrow icons)
+ // // and MARK for the rest
+ // if (prevState != currState) {
+ // ColorSchemeAssociationKind prevAssociationKind = (this.component
+ // instanceof JMenu)
+ // && prevState.isFacetActive(AnimationFacet.ROLLOVER) ?
+ // ColorSchemeAssociationKind.HIGHLIGHT
+ // : ColorSchemeAssociationKind.MARK;
+ // prevScheme = SubstanceColorSchemeUtilities.getColorScheme(
+ // this.component, prevAssociationKind, prevState);
+ // }
+ // cyclePos = stateTransitionTracker.getModelTransitionPosition();
+ // float currAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ // this.component, currState);
+ // float prevAlpha = SubstanceColorSchemeUtilities.getAlpha(
+ // this.component, prevState);
+ //
+ // HashMapKey key = SubstanceCoreUtilities.getHashKey(this.component
+ // .getClass().getName(), this.orientation, SubstanceSizeUtils
+ // .getComponentFontSize(this.component), currScheme
+ // .getDisplayName(), prevScheme.getDisplayName(), currAlpha,
+ // prevAlpha, cyclePos);
+ // Icon result = iconMap.get(key);
+ // if (result == null) {
+ // Icon icon = this.delegate.getColorSchemeIcon(currScheme);
+ // Icon prevIcon = this.delegate.getColorSchemeIcon(prevScheme);
+ //
+ // BufferedImage temp = SubstanceCoreUtilities.getBlankImage(icon
+ // .getIconWidth(), icon.getIconHeight());
+ // Graphics2D g2d = temp.createGraphics();
+ //
+ // if (currScheme == prevScheme) {
+ // // same scheme - can paint just the current icon, no matter
+ // // what the cycle position is.
+ // g2d.setComposite(AlphaComposite.getInstance(
+ // AlphaComposite.SRC_OVER, currAlpha));
+ // icon.paintIcon(this.component, g2d, 0, 0);
+ // } else {
+ // // make optimizations for limit values of the cycle position.
+ // if (cyclePos < 1.0f) {
+ // g2d.setComposite(AlphaComposite.SrcOver.derive(prevAlpha));
+ // prevIcon.paintIcon(this.component, g2d, 0, 0);
+ // }
+ // if (cyclePos > 0.0f) {
+ // g2d.setComposite(AlphaComposite.SrcOver.derive(currAlpha
+ // * cyclePos));
+ // icon.paintIcon(this.component, g2d, 0, 0);
+ // }
+ // }
+ //
+ // result = new ImageIcon(temp);
+ // iconMap.put(key, result);
+ // g2d.dispose();
+ // }
+ //
+ // return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.iconHeight;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.iconWidth;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/CheckBoxMenuItemIcon.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/CheckBoxMenuItemIcon.java
new file mode 100644
index 0000000..93bd869
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/CheckBoxMenuItemIcon.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.icon;
+
+import java.awt.AlphaComposite;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JMenuItem;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.ComponentStateFacet;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.HashMapKey;
+import org.pushingpixels.substance.internal.utils.LazyResettableHashMap;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+/**
+ * Icon for the {@link JCheckBoxMenuItem}s.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class CheckBoxMenuItemIcon implements Icon, UIResource {
+ /**
+ * The size of <code>this</code> icon.
+ */
+ private int size;
+
+ /**
+ * The associated menu item.
+ */
+ private JMenuItem menuItem;
+
+ /**
+ * Icon cache to speed up the painting.
+ */
+ private static LazyResettableHashMap<Icon> iconMap = new LazyResettableHashMap<Icon>(
+ "CheckBoxMenuItemIcon");
+
+ /**
+ * Creates a new icon.
+ *
+ * @param menuItem
+ * The corresponding menu item.
+ * @param size
+ * The size of <code>this</code> icon.
+ */
+ public CheckBoxMenuItemIcon(JMenuItem menuItem, int size) {
+ this.menuItem = menuItem;
+ this.size = size;
+ }
+
+ /**
+ * Returns the current icon to paint.
+ *
+ * @return Icon to paint.
+ */
+ private Icon getIconToPaint() {
+ if (this.menuItem == null)
+ return null;
+
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) this.menuItem
+ .getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(this.menuItem);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(this.menuItem);
+ ComponentState currState = modelStateInfo.getCurrModelState();
+
+ SubstanceColorScheme baseFillColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.menuItem, ColorSchemeAssociationKind.FILL,
+ currState);
+ SubstanceColorScheme baseMarkColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.menuItem, ColorSchemeAssociationKind.MARK,
+ currState);
+ SubstanceColorScheme baseBorderColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.menuItem,
+ ColorSchemeAssociationKind.BORDER, currState);
+ float visibility = stateTransitionTracker
+ .getFacetStrength(ComponentStateFacet.SELECTION);
+ boolean isCheckMarkFadingOut = !currState
+ .isFacetActive(ComponentStateFacet.SELECTION);
+
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(this.menuItem);
+ int checkMarkSize = this.size + 3;
+
+ HashMapKey keyBase = SubstanceCoreUtilities.getHashKey(fontSize,
+ checkMarkSize, fillPainter.getDisplayName(), borderPainter
+ .getDisplayName(),
+ baseFillColorScheme.getDisplayName(), baseMarkColorScheme
+ .getDisplayName(), baseBorderColorScheme
+ .getDisplayName(), visibility, isCheckMarkFadingOut);
+ Icon iconBase = iconMap.get(keyBase);
+ if (iconBase == null) {
+ iconBase = new ImageIcon(SubstanceImageCreator.getCheckBox(
+ this.menuItem, fillPainter, borderPainter, checkMarkSize,
+ currState, baseFillColorScheme, baseMarkColorScheme,
+ baseBorderColorScheme, visibility, isCheckMarkFadingOut));
+ iconMap.put(keyBase, iconBase);
+ }
+ if (currState.isDisabled() || (activeStates.size() == 1)) {
+ return iconBase;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(iconBase
+ .getIconWidth(), iconBase.getIconHeight());
+ Graphics2D g2d = result.createGraphics();
+ // draw the base layer
+ iconBase.paintIcon(this.menuItem, g2d, 0, 0);
+
+ // draw other active layers
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ // System.out.println("Painting state " + activeState + "[curr is "
+ // + currState + "] with " + activeEntry.getValue());
+ if (activeState == currState)
+ continue;
+
+ float stateContribution = activeEntry.getValue().getContribution();
+ if (stateContribution > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(stateContribution));
+ SubstanceColorScheme fillColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.menuItem,
+ ColorSchemeAssociationKind.FILL, activeState);
+ SubstanceColorScheme markColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.menuItem,
+ ColorSchemeAssociationKind.MARK, activeState);
+ SubstanceColorScheme borderColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.menuItem,
+ ColorSchemeAssociationKind.BORDER, activeState);
+
+ HashMapKey keyLayer = SubstanceCoreUtilities.getHashKey(
+ fontSize, checkMarkSize, fillPainter.getDisplayName(),
+ borderPainter.getDisplayName(), fillColorScheme
+ .getDisplayName(), markColorScheme
+ .getDisplayName(), borderColorScheme
+ .getDisplayName(), visibility);
+ Icon iconLayer = iconMap.get(keyLayer);
+ if (iconLayer == null) {
+ iconLayer = new ImageIcon(SubstanceImageCreator
+ .getCheckBox(this.menuItem, fillPainter,
+ borderPainter, checkMarkSize, currState,
+ fillColorScheme, markColorScheme,
+ borderColorScheme, visibility,
+ isCheckMarkFadingOut));
+ iconMap.put(keyLayer, iconLayer);
+ }
+
+ iconLayer.paintIcon(this.menuItem, g2d, 0, 0);
+ }
+ }
+
+ g2d.dispose();
+ return new ImageIcon(result);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ Icon iconToDraw = this.getIconToPaint();
+ if (iconToDraw != null)
+ iconToDraw.paintIcon(c, g, x, y);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.size + 2;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.size + 2;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/GlowingIcon.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/GlowingIcon.java
new file mode 100644
index 0000000..5deee8a
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/GlowingIcon.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.icon;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+
+import org.pushingpixels.substance.internal.animation.IconGlowTracker;
+import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+public class GlowingIcon implements Icon {
+
+ protected Icon delegate;
+
+ protected IconGlowTracker iconGlowTracker;
+
+ protected Map<Float, Icon> iconMap;
+
+ public GlowingIcon(Icon delegate, IconGlowTracker iconGlowTracker) {
+ this.delegate = delegate;
+ this.iconGlowTracker = iconGlowTracker;
+ this.iconMap = new HashMap<Float, Icon>();
+ }
+
+ public Icon getDelegate() {
+ return this.delegate;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ if (this.delegate == null)
+ return 0;
+ return this.delegate.getIconHeight();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ if (this.delegate == null)
+ return 0;
+ return this.delegate.getIconWidth();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ if (this.delegate == null)
+ return;
+ float fadePos = this.iconGlowTracker.getIconGlowPosition();
+ // System.out.println(fadePos);
+ Icon toPaint = this.iconMap.get(fadePos);
+ if (toPaint == null) {
+ int width = this.getIconWidth();
+ int height = this.getIconHeight();
+ BufferedImage image = SubstanceCoreUtilities.getBlankImage(width,
+ height);
+ Graphics2D graphics = (Graphics2D) image.getGraphics();
+ this.delegate.paintIcon(c, graphics, 0, 0);
+ for (int i = 0; i < width; i++) {
+ for (int j = 0; j < height; j++) {
+ int rgba = image.getRGB(i, j);
+ int transp = (rgba >>> 24) & 0xFF;
+ double coef = Math.sin(2.0 * Math.PI * fadePos / 2.0) / 3.0;
+ Color newColor = (coef >= 0.0) ? SubstanceColorUtilities
+ .getLighterColor(new Color(rgba), coef)
+ : SubstanceColorUtilities.getDarkerColor(new Color(
+ rgba), -coef);
+ image.setRGB(i, j, (transp << 24)
+ | (newColor.getRed() << 16)
+ | (newColor.getGreen() << 8) | newColor.getBlue());
+ }
+ }
+ toPaint = new ImageIcon(image);
+ this.iconMap.put(fadePos, toPaint);
+ }
+ toPaint.paintIcon(c, g, x, y);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/MenuArrowIcon.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/MenuArrowIcon.java
new file mode 100644
index 0000000..5b232df
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/MenuArrowIcon.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.icon;
+
+import java.awt.Component;
+import java.awt.Graphics;
+
+import javax.swing.*;
+import javax.swing.plaf.UIResource;
+
+/**
+ * Icon for the cascading {@link JMenu}s.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class MenuArrowIcon implements Icon, UIResource {
+ /**
+ * Icon for left-to-right {@link JMenu}s.
+ */
+ private Icon ltrIcon;
+
+ /**
+ * Icon for right-to-left {@link JMenu}s.
+ */
+ private Icon rtlIcon;
+
+ /**
+ * Creates the arrow icon for the specified menu.
+ *
+ * @param menu
+ * Menu.
+ */
+ public MenuArrowIcon(JMenu menu) {
+ this.ltrIcon = new ArrowButtonTransitionAwareIcon(menu,
+ SwingConstants.EAST);
+ this.rtlIcon = new ArrowButtonTransitionAwareIcon(menu,
+ SwingConstants.WEST);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.ltrIcon.getIconHeight();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.ltrIcon.getIconWidth();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ if (c.getComponentOrientation().isLeftToRight()) {
+ this.ltrIcon.paintIcon(c, g, x, y);
+ } else {
+ this.rtlIcon.paintIcon(c, g, x, y);
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/RadioButtonMenuItemIcon.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/RadioButtonMenuItemIcon.java
new file mode 100644
index 0000000..6726c6f
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/RadioButtonMenuItemIcon.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.icon;
+
+import java.awt.AlphaComposite;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JMenuItem;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.ComponentStateFacet;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.HashMapKey;
+import org.pushingpixels.substance.internal.utils.LazyResettableHashMap;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+/**
+ * Icon for the {@link JRadioButtonMenuItem}s.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RadioButtonMenuItemIcon implements Icon, UIResource {
+ /**
+ * The size of <code>this</code> icon.
+ */
+ private int size;
+
+ /**
+ * The associated menu item.
+ */
+ private JMenuItem menuItem;
+
+ /**
+ * Icon cache to speed up the painting.
+ */
+ private static LazyResettableHashMap<Icon> iconMap = new LazyResettableHashMap<Icon>(
+ "RadioButtonMenuItemIcon");
+
+ /**
+ * Creates a new icon.
+ *
+ * @param menuItem
+ * The corresponding menu item.
+ * @param size
+ * The size of <code>this</code> icon.
+ */
+ public RadioButtonMenuItemIcon(JMenuItem menuItem, int size) {
+ this.menuItem = menuItem;
+ this.size = size;
+ }
+
+ /**
+ * Returns the current icon to paint.
+ *
+ * @return Icon to paint.
+ */
+ private Icon getIconToPaint() {
+ if (this.menuItem == null)
+ return null;
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) this.menuItem
+ .getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(this.menuItem);
+ int checkMarkSize = this.size;
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(this.menuItem);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(this.menuItem);
+ ComponentState currState = modelStateInfo.getCurrModelState();
+
+ SubstanceColorScheme baseFillColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.menuItem, ColorSchemeAssociationKind.FILL,
+ currState);
+ SubstanceColorScheme baseMarkColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.menuItem, ColorSchemeAssociationKind.MARK,
+ currState);
+ SubstanceColorScheme baseBorderColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.menuItem,
+ ColorSchemeAssociationKind.BORDER, currState);
+ float visibility = stateTransitionTracker
+ .getFacetStrength(ComponentStateFacet.SELECTION);
+
+ HashMapKey keyBase = SubstanceCoreUtilities.getHashKey(fontSize,
+ checkMarkSize, fillPainter.getDisplayName(), borderPainter
+ .getDisplayName(),
+ baseFillColorScheme.getDisplayName(), baseMarkColorScheme
+ .getDisplayName(), baseBorderColorScheme
+ .getDisplayName(), visibility);
+ Icon iconBase = iconMap.get(keyBase);
+ if (iconBase == null) {
+ iconBase = new ImageIcon(SubstanceImageCreator.getRadioButton(
+ this.menuItem, fillPainter, borderPainter, checkMarkSize,
+ currState, 0, baseFillColorScheme, baseMarkColorScheme,
+ baseBorderColorScheme, visibility));
+ iconMap.put(keyBase, iconBase);
+ }
+ if (currState.isDisabled() || (activeStates.size() == 1)) {
+ return iconBase;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(iconBase
+ .getIconWidth(), iconBase.getIconHeight());
+ Graphics2D g2d = result.createGraphics();
+ // draw the base layer
+ iconBase.paintIcon(this.menuItem, g2d, 0, 0);
+
+ // draw other active layers
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ // System.out.println("Painting state " + activeState + "[curr is "
+ // + currState + "] with " + activeEntry.getValue());
+ if (activeState == currState)
+ continue;
+
+ float stateContribution = activeEntry.getValue().getContribution();
+ if (stateContribution > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(stateContribution));
+ SubstanceColorScheme fillColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.menuItem,
+ ColorSchemeAssociationKind.FILL, activeState);
+ SubstanceColorScheme markColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.menuItem,
+ ColorSchemeAssociationKind.MARK, activeState);
+ SubstanceColorScheme borderColorScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.menuItem,
+ ColorSchemeAssociationKind.BORDER, activeState);
+
+ HashMapKey keyLayer = SubstanceCoreUtilities.getHashKey(
+ fontSize, checkMarkSize, fillPainter.getDisplayName(),
+ borderPainter.getDisplayName(), fillColorScheme
+ .getDisplayName(), markColorScheme
+ .getDisplayName(), borderColorScheme
+ .getDisplayName(), visibility);
+ Icon iconLayer = iconMap.get(keyLayer);
+ if (iconLayer == null) {
+ iconLayer = new ImageIcon(SubstanceImageCreator
+ .getRadioButton(this.menuItem, fillPainter,
+ borderPainter, checkMarkSize, currState, 0,
+ fillColorScheme, markColorScheme,
+ borderColorScheme, visibility));
+ iconMap.put(keyLayer, iconLayer);
+ }
+
+ iconLayer.paintIcon(this.menuItem, g2d, 0, 0);
+ }
+ }
+
+ g2d.dispose();
+ return new ImageIcon(result);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ Icon iconToDraw = this.getIconToPaint();
+ if (iconToDraw != null)
+ iconToDraw.paintIcon(c, g, x, y);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.size + 2;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.size + 2;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/SubstanceIconFactory.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/SubstanceIconFactory.java
new file mode 100644
index 0000000..b0a058b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/SubstanceIconFactory.java
@@ -0,0 +1,1049 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.icon;
+
+import java.awt.AlphaComposite;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.GeneralPath;
+import java.awt.image.BufferedImage;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.ButtonModel;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JSlider;
+import javax.swing.JTree;
+import javax.swing.plaf.SliderUI;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.basic.BasicSliderUI;
+
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
+import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.ui.SubstanceSliderUI;
+import org.pushingpixels.substance.internal.ui.SubstanceTreeUI;
+import org.pushingpixels.substance.internal.utils.HashMapKey;
+import org.pushingpixels.substance.internal.utils.LazyResettableHashMap;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+import org.pushingpixels.substance.internal.utils.SubstanceOutlineUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+/**
+ * Icon factory for dynamically-changing icons. This class is <b>for internal
+ * use only</b>.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceIconFactory {
+ /**
+ * Icons for horizontal slider in {@link SubstanceSliderUI}.
+ */
+ private static LazyResettableHashMap<Icon> sliderHorizontalIcons = new LazyResettableHashMap<Icon>(
+ "SubstanceIconFactory.sliderHorizontalIcon");
+
+ /**
+ * Icons for horizontal slider in {@link SubstanceSliderUI}.
+ */
+ private static LazyResettableHashMap<Icon> sliderRoundIcons = new LazyResettableHashMap<Icon>(
+ "SubstanceIconFactory.sliderRoundIcon");
+
+ /**
+ * Icons for vertical slider in {@link SubstanceSliderUI}.
+ */
+ private static LazyResettableHashMap<Icon> sliderVerticalIcons = new LazyResettableHashMap<Icon>(
+ "SubstanceIconFactory.sliderVerticalIcon");
+
+ /**
+ * Icons for tree collapse / expand in {@link SubstanceTreeUI}.
+ */
+ private static LazyResettableHashMap<Icon> treeIcons = new LazyResettableHashMap<Icon>(
+ "SubstanceIconFactory.treeIcon");
+
+ /**
+ * Retrieves icon for horizontal slider in {@link SubstanceSliderUI}.
+ *
+ * @param size
+ * The size of the icon to retrieve.
+ * @param isMirrorred
+ * Indication whether the icon should be mirrored.
+ * @return Icon for horizontal slider in {@link SubstanceSliderUI}.
+ */
+ public static Icon getSliderHorizontalIcon(int size, boolean isMirrorred) {
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(size, isMirrorred);
+ if (SubstanceIconFactory.sliderHorizontalIcons.get(key) == null) {
+ Icon icon = new SliderHorizontalIcon(size, isMirrorred);
+ SubstanceIconFactory.sliderHorizontalIcons.put(key, icon);
+ }
+ return SubstanceIconFactory.sliderHorizontalIcons.get(key);
+ }
+
+ /**
+ * Retrieves round icon for slider in {@link SubstanceSliderUI}.
+ *
+ * @param size
+ * The size of the icon to retrieve.
+ * @return Round icon for slider in {@link SubstanceSliderUI}.
+ */
+ public static Icon getSliderRoundIcon(int size) {
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(size);
+ if (SubstanceIconFactory.sliderRoundIcons.get(key) == null) {
+ Icon icon = new SliderRoundIcon(size);
+ SubstanceIconFactory.sliderRoundIcons.put(key, icon);
+ }
+ return SubstanceIconFactory.sliderRoundIcons.get(key);
+ }
+
+ /**
+ * Retrieves icon for vertical slider in {@link SubstanceSliderUI}.
+ *
+ * @param size
+ * The size of the icon to retrieve.
+ * @param isMirrorred
+ * Indication whether the icon should be mirrored.
+ * @return Icon for vertical slider in {@link SubstanceSliderUI}.
+ */
+ public static Icon getSliderVerticalIcon(int size, boolean isMirrorred) {
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(size, isMirrorred);
+ if (SubstanceIconFactory.sliderVerticalIcons.get(key) == null) {
+ Icon icon = new SliderVerticalIcon(size, isMirrorred);
+ SubstanceIconFactory.sliderVerticalIcons.put(key, icon);
+ }
+ return SubstanceIconFactory.sliderVerticalIcons.get(key);
+ }
+
+ public static Icon getTreeIcon(JTree tree, boolean isCollapsed) {
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(tree);
+ int size = SubstanceSizeUtils.getTreeIconSize(fontSize);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(size, isCollapsed);
+ if (SubstanceIconFactory.treeIcons.get(key) == null) {
+ Icon icon = new TreeIcon(size, isCollapsed);
+ SubstanceIconFactory.treeIcons.put(key, icon);
+ }
+ return SubstanceIconFactory.treeIcons.get(key);
+ }
+
+ /**
+ * Trackable slider (for sliders that do not use {@link SubstanceSliderUI}
+ * as their UI (such as
+ * {@link org.pushingpixels.substance.internal.contrib.randelshofer.quaqua.colorchooser.ColorSliderUI}
+ * from Quaqua). Uses reflection to implement the {@link TransitionAwareUI}
+ * interface, fetching the value of {@link BasicSliderUI#thumbRect}field.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class TrackableSlider implements TransitionAwareUI {
+ /**
+ * The associated slider.
+ */
+ private JSlider slider;
+
+ /**
+ * Reflection reference to {@link BasicSliderUI#thumbRect}field. If
+ * reflection failed, or no such field (for example the custom UI
+ * implements {@link SliderUI}directly, <code>this</code> field is
+ * <code>null</code>.
+ */
+ private Field thumbRectField;
+
+ private ButtonModel transitionModel;
+
+ private StateTransitionTracker stateTransitionTracker;
+
+ /**
+ * Simple constructor.
+ *
+ * @param slider
+ * The associated slider.
+ */
+ public TrackableSlider(JSlider slider, ButtonModel transitionModel) {
+ this.slider = slider;
+ this.transitionModel = transitionModel;
+
+ SliderUI sliderUI = slider.getUI();
+ if (sliderUI instanceof BasicSliderUI) {
+ try {
+ this.thumbRectField = BasicSliderUI.class
+ .getDeclaredField("thumbRect");
+ this.thumbRectField.setAccessible(true);
+ } catch (Exception exc) {
+ this.thumbRectField = null;
+ }
+ }
+
+ this.stateTransitionTracker = new StateTransitionTracker(
+ this.slider, this.transitionModel);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.pushingpixels.substance.Trackable#isInside(java.awt.event.MouseEvent
+ * )
+ */
+ @Override
+ public boolean isInside(MouseEvent me) {
+ try {
+ Rectangle thumbB = (Rectangle) this.thumbRectField
+ .get(this.slider.getUI());
+ if (thumbB == null)
+ return false;
+ return thumbB.contains(me.getX(), me.getY());
+ } catch (Exception exc) {
+ return false;
+ }
+ }
+
+ @Override
+ public StateTransitionTracker getTransitionTracker() {
+ return this.stateTransitionTracker;
+ }
+ }
+
+ /**
+ * Icon for horizontal slider in {@link SubstanceSliderUI}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class SliderHorizontalIcon implements Icon, UIResource {
+ /**
+ * Icon hash.
+ */
+ private static LazyResettableHashMap<Icon> icons = new LazyResettableHashMap<Icon>(
+ "SubstanceIconFactory.SliderHorizontalIcon");
+
+ /**
+ * The size of <code>this</code> icon.
+ */
+ private int size;
+
+ /**
+ * Indication whether the icon is mirrorred.
+ */
+ private boolean isMirrorred;
+
+ /**
+ * Simple constructor.
+ *
+ * @param size
+ * The size of <code>this</code> icon.
+ * @param isMirrorred
+ * Indication whether the icon should be mirrored.
+ */
+ public SliderHorizontalIcon(int size, boolean isMirrorred) {
+ this.size = size;
+ this.isMirrorred = isMirrorred;
+ }
+
+ private Icon getIcon(JSlider slider,
+ StateTransitionTracker stateTransitionTracker) {
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ ComponentState currState = stateTransitionTracker
+ .getModelStateInfo().getCurrModelState();
+
+ float activeStrength = stateTransitionTracker.getActiveStrength();
+ int width = (int) (this.size * (2.0 + activeStrength) / 3.0);
+ width = Math.min(width, this.size - 2);
+ int delta = (this.size - width) / 2;
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(slider);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(slider);
+
+ SubstanceColorScheme baseFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(slider, currState);
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(slider, ColorSchemeAssociationKind.BORDER,
+ currState);
+
+ HashMapKey baseKey = SubstanceCoreUtilities.getHashKey(this.size,
+ width, baseFillScheme.getDisplayName(), baseBorderScheme
+ .getDisplayName(), fillPainter.getDisplayName(),
+ borderPainter.getDisplayName(), this.isMirrorred);
+
+ Icon baseLayer = SliderHorizontalIcon.icons.get(baseKey);
+ if (baseLayer == null) {
+ baseLayer = getSingleLayer(slider, width, delta, fillPainter,
+ borderPainter, baseFillScheme, baseBorderScheme);
+ SliderHorizontalIcon.icons.put(baseKey, baseLayer);
+ }
+
+ if (currState.isDisabled() || (activeStates.size() == 1))
+ return baseLayer;
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(
+ baseLayer.getIconWidth(), baseLayer.getIconHeight());
+ Graphics2D g2d = result.createGraphics();
+ baseLayer.paintIcon(slider, g2d, 0, 0);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(slider, activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(slider,
+ ColorSchemeAssociationKind.BORDER, activeState);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(this.size,
+ width, fillScheme.getDisplayName(), borderScheme
+ .getDisplayName(),
+ fillPainter.getDisplayName(), borderPainter
+ .getDisplayName(), this.isMirrorred);
+
+ Icon layer = SliderHorizontalIcon.icons.get(key);
+ if (layer == null) {
+ layer = getSingleLayer(slider, width, delta, fillPainter,
+ borderPainter, fillScheme, borderScheme);
+ SliderHorizontalIcon.icons.put(key, layer);
+ }
+
+ g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
+ layer.paintIcon(slider, g2d, 0, 0);
+ }
+
+ g2d.dispose();
+ return new ImageIcon(result);
+ }
+
+ private Icon getSingleLayer(JSlider slider, int width, int delta,
+ SubstanceFillPainter fillPainter,
+ SubstanceBorderPainter borderPainter,
+ SubstanceColorScheme fillScheme,
+ SubstanceColorScheme borderScheme) {
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(slider)) / 2.0);
+ Shape contour = SubstanceOutlineUtilities.getTriangleButtonOutline(
+ width, this.size - 1, 2, borderDelta);
+
+ BufferedImage stateImage = SubstanceCoreUtilities.getBlankImage(
+ this.size - 1, this.size - 1);
+ Graphics2D g2d = stateImage.createGraphics();
+ g2d.translate(delta, 0);
+
+ fillPainter.paintContourBackground(g2d, slider, width,
+ this.size - 1, contour, false, fillScheme, true);
+
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(slider));
+ GeneralPath contourInner = SubstanceOutlineUtilities
+ .getTriangleButtonOutline(width, this.size - 1, 2,
+ borderThickness + borderDelta);
+
+ borderPainter.paintBorder(g2d, slider, width, this.size - 1,
+ contour, contourInner, borderScheme);
+ g2d.translate(-delta, 0);
+
+ if (this.isMirrorred)
+ stateImage = SubstanceImageCreator.getRotated(stateImage, 2);
+
+ return new ImageIcon(stateImage);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component,
+ * java.awt.Graphics, int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ if (!(g instanceof Graphics2D)) {
+ return;
+ }
+
+ JSlider slider = (JSlider) c;
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) slider
+ .getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+ Icon iconToDraw = getIcon(slider, stateTransitionTracker);
+
+ iconToDraw.paintIcon(c, g, x, y);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.size - 1;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.size - 1;
+ }
+ }
+
+ /**
+ * Round icon for sliders in {@link SubstanceSliderUI}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class SliderRoundIcon implements Icon, UIResource {
+ /**
+ * Icon hash.
+ */
+ private static LazyResettableHashMap<Icon> icons = new LazyResettableHashMap<Icon>(
+ "SubstanceIconFactory.SliderRoundIcon");
+
+ /**
+ * The size of <code>this</code> icon.
+ */
+ private int size;
+
+ /**
+ * Simple constructor.
+ *
+ * @param size
+ * The size of <code>this</code> icon.
+ */
+ public SliderRoundIcon(int size) {
+ this.size = size;
+ }
+
+ /**
+ * Retrieves icon that matches the specified state of the slider thumb.
+ *
+ * @param slider
+ * The slider itself.
+ * @param stateTransitionTracker
+ * the state.
+ * @return Icon that matches the specified state of the slider thumb.
+ */
+ private Icon getIcon(JSlider slider,
+ StateTransitionTracker stateTransitionTracker) {
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ ComponentState currState = stateTransitionTracker
+ .getModelStateInfo().getCurrModelState();
+
+ float activeStrength = stateTransitionTracker.getActiveStrength();
+ int width = (int) (this.size * (2.0 + activeStrength) / 3.0);
+ width = Math.min(width, this.size - 2);
+ if (width % 2 == 0)
+ width--;
+ int delta = (this.size - width) / 2;
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(slider);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(slider);
+
+ SubstanceColorScheme baseFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(slider, currState);
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(slider, ColorSchemeAssociationKind.BORDER,
+ currState);
+
+ HashMapKey baseKey = SubstanceCoreUtilities.getHashKey(this.size,
+ width, baseFillScheme.getDisplayName(), baseBorderScheme
+ .getDisplayName(), fillPainter.getDisplayName(),
+ borderPainter.getDisplayName());
+
+ Icon baseLayer = SliderRoundIcon.icons.get(baseKey);
+ if (baseLayer == null) {
+ baseLayer = getSingleLayer(slider, width, delta, fillPainter,
+ borderPainter, baseFillScheme, baseBorderScheme);
+ SliderRoundIcon.icons.put(baseKey, baseLayer);
+ }
+
+ if (currState.isDisabled() || (activeStates.size() == 1))
+ return baseLayer;
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(
+ baseLayer.getIconWidth(), baseLayer.getIconHeight());
+ Graphics2D g2d = result.createGraphics();
+ baseLayer.paintIcon(slider, g2d, 0, 0);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(slider, activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(slider,
+ ColorSchemeAssociationKind.BORDER, activeState);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(this.size,
+ width, fillScheme.getDisplayName(), borderScheme
+ .getDisplayName(),
+ fillPainter.getDisplayName(), borderPainter
+ .getDisplayName());
+
+ Icon layer = SliderRoundIcon.icons.get(key);
+ if (layer == null) {
+ layer = getSingleLayer(slider, width, delta, fillPainter,
+ borderPainter, fillScheme, borderScheme);
+ SliderRoundIcon.icons.put(key, layer);
+ }
+
+ g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
+ layer.paintIcon(slider, g2d, 0, 0);
+ }
+
+ g2d.dispose();
+ return new ImageIcon(result);
+ }
+
+ private Icon getSingleLayer(JSlider slider, int width, int delta,
+ SubstanceFillPainter fillPainter,
+ SubstanceBorderPainter borderPainter,
+ SubstanceColorScheme fillScheme,
+ SubstanceColorScheme borderScheme) {
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(slider)) / 2.0);
+ Shape contour = new Ellipse2D.Float(borderDelta, borderDelta, width
+ - 2 * borderDelta - 1, width - 2 * borderDelta - 1);
+
+ BufferedImage stateImage = SubstanceCoreUtilities.getBlankImage(
+ this.size - 1, this.size - 1);
+ Graphics2D g2d = stateImage.createGraphics();
+ g2d.translate(delta, delta);
+
+ fillPainter.paintContourBackground(g2d, slider, width,
+ this.size - 1, contour, false, fillScheme, true);
+
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(slider));
+ Shape contourInner = new Ellipse2D.Float(borderDelta
+ + borderThickness, borderDelta + borderThickness, width - 2
+ * borderDelta - 2 * borderThickness - 1, width - 2
+ * borderDelta - 2 * borderThickness - 1);
+
+ borderPainter.paintBorder(g2d, slider, width, this.size - 1,
+ contour, contourInner, borderScheme);
+
+ return new ImageIcon(stateImage);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component,
+ * java.awt.Graphics, int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ if (!(g instanceof Graphics2D)) {
+ return;
+ }
+
+ JSlider slider = (JSlider) c;
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) slider
+ .getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+ Icon iconToDraw = getIcon(slider, stateTransitionTracker);
+
+ iconToDraw.paintIcon(c, g, x, y);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.size - 1;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.size - 1;
+ }
+ }
+
+ /**
+ * Icon for vertical slider in {@link SubstanceSliderUI}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class SliderVerticalIcon implements Icon, UIResource {
+ /**
+ * Icon hash.
+ */
+ private static LazyResettableHashMap<Icon> icons = new LazyResettableHashMap<Icon>(
+ "SubstanceIconFactory.SliderVerticalIcon");
+
+ /**
+ * The size of <code>this</code> icon.
+ */
+ private int size;
+
+ /**
+ * Indication whether the icon is mirrorred.
+ */
+ private boolean isMirrorred;
+
+ /**
+ * Simple constructor.
+ *
+ * @param size
+ * The size of <code>this</code> icon.
+ * @param isMirrorred
+ * Indication whether the icon should be mirrored.
+ */
+ public SliderVerticalIcon(int size, boolean isMirrorred) {
+ this.size = size;
+ this.isMirrorred = isMirrorred;
+ }
+
+ /**
+ * Retrieves icon that matches the specified state of the slider thumb.
+ *
+ * @param slider
+ * The slider itself.
+ * @param stateTransitionTracker
+ * state of the slider.
+ * @return Icon that matches the specified state of the slider thumb.
+ */
+ private Icon getIcon(JSlider slider,
+ StateTransitionTracker stateTransitionTracker) {
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+ ComponentState currState = stateTransitionTracker
+ .getModelStateInfo().getCurrModelState();
+
+ float activeStrength = stateTransitionTracker.getActiveStrength();
+ int height = (int) (this.size * (2.0 + activeStrength) / 3.0);
+ height = Math.min(height, this.size - 2);
+ int delta = (this.size - height) / 2 - 1;
+
+ SubstanceFillPainter fillPainter = SubstanceCoreUtilities
+ .getFillPainter(slider);
+ SubstanceBorderPainter borderPainter = SubstanceCoreUtilities
+ .getBorderPainter(slider);
+
+ SubstanceColorScheme baseFillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(slider, currState);
+ SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(slider, ColorSchemeAssociationKind.BORDER,
+ currState);
+
+ HashMapKey baseKey = SubstanceCoreUtilities.getHashKey(this.size,
+ height, slider.getComponentOrientation(), baseFillScheme
+ .getDisplayName(), baseBorderScheme
+ .getDisplayName(), fillPainter.getDisplayName(),
+ borderPainter.getDisplayName(), this.isMirrorred);
+
+ Icon baseLayer = SliderVerticalIcon.icons.get(baseKey);
+ if (baseLayer == null) {
+ baseLayer = getSingleLayer(slider, height, delta, fillPainter,
+ borderPainter, baseFillScheme, baseBorderScheme);
+ SliderVerticalIcon.icons.put(baseKey, baseLayer);
+ }
+
+ if (currState.isDisabled() || (activeStates.size() == 1))
+ return baseLayer;
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(
+ baseLayer.getIconWidth(), baseLayer.getIconHeight());
+ Graphics2D g2d = result.createGraphics();
+ baseLayer.paintIcon(slider, g2d, 0, 0);
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ if (activeState == currState)
+ continue;
+
+ float contribution = activeEntry.getValue().getContribution();
+ if (contribution == 0.0f)
+ continue;
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(slider, activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(slider,
+ ColorSchemeAssociationKind.BORDER, activeState);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(this.size,
+ height, slider.getComponentOrientation(), fillScheme
+ .getDisplayName(), borderScheme
+ .getDisplayName(),
+ fillPainter.getDisplayName(), borderPainter
+ .getDisplayName(), this.isMirrorred);
+
+ Icon layer = SliderVerticalIcon.icons.get(key);
+ if (layer == null) {
+ layer = getSingleLayer(slider, height, delta, fillPainter,
+ borderPainter, fillScheme, borderScheme);
+ SliderVerticalIcon.icons.put(key, layer);
+ }
+
+ g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
+ layer.paintIcon(slider, g2d, 0, 0);
+ }
+
+ g2d.dispose();
+ return new ImageIcon(result);
+ }
+
+ private Icon getSingleLayer(JSlider slider, int height, int delta,
+ SubstanceFillPainter fillPainter,
+ SubstanceBorderPainter borderPainter,
+ SubstanceColorScheme fillScheme,
+ SubstanceColorScheme borderScheme) {
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(slider)) / 2.0);
+ Shape contour = SubstanceOutlineUtilities.getTriangleButtonOutline(
+ height, this.size, 2, borderDelta);
+
+ BufferedImage stateImage = SubstanceCoreUtilities.getBlankImage(
+ this.size - 1, this.size - 1);
+ Graphics2D g2d = stateImage.createGraphics();
+ g2d.translate(delta, 0);
+
+ fillPainter.paintContourBackground(g2d, slider, height, this.size,
+ contour, false, fillScheme, true);
+
+ int borderThickness = (int) SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(slider));
+ GeneralPath contourInner = SubstanceOutlineUtilities
+ .getTriangleButtonOutline(height, this.size, 2,
+ borderThickness + borderDelta);
+
+ borderPainter.paintBorder(g2d, slider, height, this.size - 1,
+ contour, contourInner, borderScheme);
+ // bg2d.translate(-delta, 0);
+
+ if (this.isMirrorred)
+ stateImage = SubstanceImageCreator.getRotated(stateImage, 1);
+ else
+ stateImage = SubstanceImageCreator.getRotated(stateImage, 3);
+
+ if (!slider.getComponentOrientation().isLeftToRight()) {
+ stateImage = SubstanceImageCreator.getRotated(stateImage, 2);
+ }
+
+ return new ImageIcon(stateImage);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component,
+ * java.awt.Graphics, int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ if (!(g instanceof Graphics2D)) {
+ return;
+ }
+
+ JSlider slider = (JSlider) c;
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) slider
+ .getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+ Icon iconToDraw = getIcon(slider, stateTransitionTracker);
+
+ iconToDraw.paintIcon(c, g, x, y);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.size - 1;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.size - 1;
+ }
+ }
+
+ /**
+ * Collapse / expand icons for JTrees in {@link SubstanceTreeUI}.
+ *
+ * @author Kirill Grouchnikov
+ */
+ private static class TreeIcon implements Icon, UIResource {
+ /**
+ * Icon hash.
+ */
+ private static LazyResettableHashMap<Icon> icons = new LazyResettableHashMap<Icon>(
+ "SubstanceIconFactory.TreeIcon");
+
+ /**
+ * The collapsed indication of this icon
+ */
+ private boolean isCollapsed;
+
+ private int size;
+
+ /**
+ * Simple constructor.
+ *
+ * @param isCollapsed
+ * The collapsed indication of <code>this</code> icon.
+ */
+ public TreeIcon(int size, boolean isCollapsed) {
+ this.isCollapsed = isCollapsed;
+ this.size = size;
+ }
+
+ /**
+ * Retrieves icon that matches the specified state of the slider thumb.
+ *
+ * @return Icon that matches the specified state of the slider thumb.
+ */
+ private static Icon getIcon(JTree tree, boolean isCollapsed) {
+ ComponentState state = ((tree == null) || tree.isEnabled()) ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED;
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(tree, state);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(tree, ColorSchemeAssociationKind.BORDER,
+ state);
+
+ int fontSize = SubstanceSizeUtils.getComponentFontSize(tree);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(fontSize,
+ fillScheme.getDisplayName(), borderScheme.getDisplayName(),
+ isCollapsed);
+
+ Icon result = TreeIcon.icons.get(key);
+ if (result != null)
+ return result;
+
+ result = new ImageIcon(SubstanceImageCreator.getTreeIcon(tree,
+ fillScheme, borderScheme, isCollapsed));
+ TreeIcon.icons.put(key, result);
+
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component,
+ * java.awt.Graphics, int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ if (!(g instanceof Graphics2D)) {
+ return;
+ }
+
+ // The following check is here because some applications
+ // (like JIDE's ExpandablePanel) may decide to use the
+ // "Tree.collapsedIcon" and "Tree.expandedIcon" UIManager
+ // entries to paint on non-JTree components. Sigh.
+ JTree tree = (c instanceof JTree) ? (JTree) c : null;
+ Icon iconToDraw = TreeIcon.getIcon(tree, this.isCollapsed);
+
+ iconToDraw.paintIcon(c, g, x, y);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.size;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.size;
+ }
+ }
+
+ /**
+ * Icon kind of a title pane button.
+ *
+ * @author Kirill Grocuhnikov.
+ */
+ public enum IconKind {
+ /**
+ * Icon of a close button.
+ */
+ CLOSE,
+
+ /**
+ * Icon of a minimize button.
+ */
+ MINIMIZE,
+
+ /**
+ * Icon of a maximize button.
+ */
+ MAXIMIZE,
+
+ /**
+ * Icon of a restore button.
+ */
+ RESTORE;
+ }
+
+ /**
+ * Cache of title pane icons.
+ */
+ private static final Map<IconKind, LazyResettableHashMap<Icon>> titlePaneIcons = SubstanceIconFactory
+ .createTitlePaneIcons();
+
+ /**
+ * Creates an empty map of title pane icons.
+ *
+ * @return Empty map of title pane icons.
+ */
+ private static Map<IconKind, LazyResettableHashMap<Icon>> createTitlePaneIcons() {
+ Map<IconKind, LazyResettableHashMap<Icon>> result = new HashMap<IconKind, LazyResettableHashMap<Icon>>();
+
+ result.put(IconKind.CLOSE, new LazyResettableHashMap<Icon>(
+ "Close title pane icons"));
+ result.put(IconKind.MINIMIZE, new LazyResettableHashMap<Icon>(
+ "Minimize title pane icons"));
+ result.put(IconKind.MAXIMIZE, new LazyResettableHashMap<Icon>(
+ "Maximize title pane icons"));
+ result.put(IconKind.RESTORE, new LazyResettableHashMap<Icon>(
+ "Restore title pane icons"));
+ return result;
+ }
+
+ /**
+ * Returns title pane icon of the specified kind.
+ *
+ * @param iconKind
+ * Icon kind.
+ * @param scheme
+ * Color scheme.
+ * @return Title pane icon of the specified kind.
+ */
+ public static Icon getTitlePaneIcon(IconKind iconKind,
+ SubstanceColorScheme scheme, SubstanceColorScheme backgroundScheme) {
+
+ LazyResettableHashMap<Icon> kindMap = SubstanceIconFactory.titlePaneIcons
+ .get(iconKind);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(scheme
+ .getDisplayName(), backgroundScheme.getDisplayName());
+ Icon result = kindMap.get(key);
+ if (result != null)
+ return result;
+
+ switch (iconKind) {
+ case CLOSE:
+ result = SubstanceImageCreator.getCloseIcon(scheme,
+ backgroundScheme);
+ break;
+ case MINIMIZE:
+ result = SubstanceImageCreator.getMinimizeIcon(scheme,
+ backgroundScheme);
+ break;
+ case MAXIMIZE:
+ result = SubstanceImageCreator.getMaximizeIcon(scheme,
+ backgroundScheme);
+ break;
+ case RESTORE:
+ result = SubstanceImageCreator.getRestoreIcon(scheme,
+ backgroundScheme);
+ break;
+ }
+ kindMap.put(key, result);
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/TransitionAware.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/TransitionAware.java
new file mode 100644
index 0000000..ed2fe01
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/TransitionAware.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.icon;
+
+import java.lang.annotation.*;
+
+ at Target(ElementType.TYPE)
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface TransitionAware {
+
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/TransitionAwareIcon.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/TransitionAwareIcon.java
new file mode 100644
index 0000000..1ca707e
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/icon/TransitionAwareIcon.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.icon;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+import javax.swing.*;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.*;
+
+/**
+ * Icon with transition-aware capabilities. Has a delegate that does the actual
+ * painting based on the transition color schemes. This class is used heavily on
+ * Substance-provided icons, such as title pane button icons, arrow icons on
+ * scroll bars and combos etc.
+ *
+ * @author Kirill Grouchnikov
+ */
+ at TransitionAware
+public class TransitionAwareIcon implements Icon {
+ /**
+ * The delegate needs to implement the method in this interface based on the
+ * provided color scheme. The color scheme is computed based on the
+ * transitions that are happening on the associated button.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static interface Delegate {
+ /**
+ * Returns the icon that matches the specified scheme.
+ *
+ * @param scheme
+ * Color scheme.
+ * @return Icon that matches the specified scheme.
+ */
+ public Icon getColorSchemeIcon(SubstanceColorScheme scheme);
+ }
+
+ public static interface ColorSchemeAssociationKindDelegate {
+ public ColorSchemeAssociationKind getColorSchemeAssociationKind(
+ ComponentState state);
+ }
+
+ public static interface TransitionAwareUIDelegate {
+ public TransitionAwareUI getTransitionAwareUI();
+ }
+
+ /**
+ * The associated component.
+ */
+ private JComponent comp;
+
+ /**
+ * The associated model.
+ */
+ // private ButtonModel model;
+
+ private TransitionAwareUIDelegate transitionAwareUIDelegate;
+
+ /**
+ * Delegate to compute the actual icons.
+ */
+ protected Delegate delegate;
+
+ protected ColorSchemeAssociationKindDelegate colorSchemeAssociationKindDelegate;
+
+ protected String uniqueIconTypeId;
+
+ /**
+ * Icon cache to speed up the subsequent icon painting. The basic assumption
+ * is that the {@link #delegate} returns an icon that paints the same for
+ * the same parameters.
+ */
+ private static LazyResettableHashMap<Icon> iconMap = new LazyResettableHashMap<Icon>(
+ "TransitionAwareIcon");
+
+ private int iconWidth;
+
+ private int iconHeight;
+
+ public TransitionAwareIcon(final AbstractButton button, Delegate delegate,
+ String uniqueIconTypeId) {
+ this(button, (button == null) ? null : new TransitionAwareUIDelegate() {
+ @Override
+ public TransitionAwareUI getTransitionAwareUI() {
+ return (TransitionAwareUI) button.getUI();
+ }
+ }, delegate, null, uniqueIconTypeId);
+ }
+
+ /**
+ * Creates a new transition-aware icon.
+ *
+ * @param comp
+ * Associated component.
+ * @param delegate
+ * Delegate to compute the actual icons.
+ */
+ public TransitionAwareIcon(
+ JComponent comp,
+ TransitionAwareUIDelegate transitionAwareUIDelegate,
+ Delegate delegate,
+ ColorSchemeAssociationKindDelegate colorSchemeAssociationKindDelegate,
+ String uniqueIconTypeId) {
+ this.comp = comp;
+ this.transitionAwareUIDelegate = transitionAwareUIDelegate;
+ this.delegate = delegate;
+ this.colorSchemeAssociationKindDelegate = colorSchemeAssociationKindDelegate;
+ this.uniqueIconTypeId = uniqueIconTypeId;
+ this.iconWidth = this.delegate.getColorSchemeIcon(
+ SubstanceColorSchemeUtilities
+ .getColorScheme(comp, ColorSchemeAssociationKind.MARK,
+ ComponentState.ENABLED)).getIconWidth();
+ this.iconHeight = this.delegate.getColorSchemeIcon(
+ SubstanceColorSchemeUtilities
+ .getColorScheme(comp, ColorSchemeAssociationKind.MARK,
+ ComponentState.ENABLED)).getIconHeight();
+ }
+
+ /**
+ * Returns the current icon to paint.
+ *
+ * @return Icon to paint.
+ */
+ private synchronized Icon getIconToPaint() {
+ StateTransitionTracker stateTransitionTracker = this.transitionAwareUIDelegate
+ .getTransitionAwareUI().getTransitionTracker();
+ StateTransitionTracker.ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateContributionMap();
+
+ ComponentState currState = modelStateInfo.getCurrModelState();
+ boolean buttonNeverPainted = SubstanceCoreUtilities
+ .isButtonNeverPainted(this.comp);
+ if (buttonNeverPainted) {
+ if (currState.isFacetActive(ComponentStateFacet.ENABLE))
+ currState = ComponentState.ENABLED;
+ }
+
+ ColorSchemeAssociationKind baseAssociationKind = (this.colorSchemeAssociationKindDelegate == null) ? ColorSchemeAssociationKind.MARK
+ : this.colorSchemeAssociationKindDelegate
+ .getColorSchemeAssociationKind(currState);
+ SubstanceColorScheme baseScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.comp, baseAssociationKind, currState);
+ float baseAlpha = SubstanceColorSchemeUtilities.getAlpha(this.comp,
+ currState);
+
+ HashMapKey keyBase = SubstanceCoreUtilities.getHashKey(
+ this.uniqueIconTypeId, SubstanceSizeUtils
+ .getComponentFontSize(this.comp), baseScheme
+ .getDisplayName(), baseAlpha);
+ Icon layerBase = iconMap.get(keyBase);
+ if (layerBase == null) {
+ Icon baseFullOpacity = this.delegate.getColorSchemeIcon(baseScheme);
+ if (baseAlpha == 1.0f) {
+ layerBase = baseFullOpacity;
+ iconMap.put(keyBase, layerBase);
+ } else {
+ BufferedImage baseImage = SubstanceCoreUtilities.getBlankImage(
+ baseFullOpacity.getIconWidth(), baseFullOpacity
+ .getIconHeight());
+ Graphics2D g2base = baseImage.createGraphics();
+ g2base.setComposite(AlphaComposite.SrcOver.derive(baseAlpha));
+ baseFullOpacity.paintIcon(this.comp, g2base, 0, 0);
+ g2base.dispose();
+ layerBase = new ImageIcon(baseImage);
+ iconMap.put(keyBase, layerBase);
+ }
+ }
+ if (currState.isDisabled() || (activeStates.size() == 1)
+ || buttonNeverPainted) {
+ return layerBase;
+ }
+
+ BufferedImage result = SubstanceCoreUtilities.getBlankImage(layerBase
+ .getIconWidth(), layerBase.getIconHeight());
+ Graphics2D g2d = result.createGraphics();
+ // draw the base layer
+ layerBase.paintIcon(this.comp, g2d, 0, 0);
+
+ // draw the other active layers
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = activeEntry.getKey();
+ // System.out.println("Painting state " + activeState + "[curr is "
+ // + currState + "] with " + activeEntry.getValue());
+ if (activeState == currState)
+ continue;
+
+ float stateContribution = activeEntry.getValue().getContribution();
+ if (stateContribution > 0.0f) {
+ g2d.setComposite(AlphaComposite.SrcOver
+ .derive(stateContribution));
+
+ ColorSchemeAssociationKind associationKind = (this.colorSchemeAssociationKindDelegate == null) ? ColorSchemeAssociationKind.MARK
+ : this.colorSchemeAssociationKindDelegate
+ .getColorSchemeAssociationKind(activeState);
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(this.comp, associationKind, activeState);
+ float alpha = SubstanceColorSchemeUtilities.getAlpha(this.comp,
+ activeState);
+
+ HashMapKey key = SubstanceCoreUtilities.getHashKey(
+ this.uniqueIconTypeId, SubstanceSizeUtils
+ .getComponentFontSize(this.comp), scheme
+ .getDisplayName(), alpha);
+ Icon layer = iconMap.get(key);
+ if (layer == null) {
+ Icon fullOpacity = this.delegate.getColorSchemeIcon(scheme);
+ if (alpha == 1.0f) {
+ layer = fullOpacity;
+ iconMap.put(key, layer);
+ } else {
+ BufferedImage image = SubstanceCoreUtilities
+ .getBlankImage(fullOpacity.getIconWidth(),
+ fullOpacity.getIconHeight());
+ Graphics2D g2layer = image.createGraphics();
+ g2layer.setComposite(AlphaComposite.SrcOver
+ .derive(alpha));
+ fullOpacity.paintIcon(this.comp, g2layer, 0, 0);
+ g2layer.dispose();
+ layer = new ImageIcon(image);
+ iconMap.put(key, layer);
+ }
+ }
+ layer.paintIcon(this.comp, g2d, 0, 0);
+ }
+ }
+ g2d.dispose();
+ return new ImageIcon(result);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconHeight()
+ */
+ @Override
+ public int getIconHeight() {
+ return this.iconHeight;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#getIconWidth()
+ */
+ @Override
+ public int getIconWidth() {
+ return this.iconWidth;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
+ * int, int)
+ */
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ this.getIconToPaint().paintIcon(c, g, x, y);
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/menu/MenuUtilities.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/menu/MenuUtilities.java
new file mode 100644
index 0000000..944422b
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/menu/MenuUtilities.java
@@ -0,0 +1,1018 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.menu;
+
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.KeyEvent;
+import java.awt.geom.Area;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.ButtonModel;
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.JLayeredPane;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JRootPane;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.plaf.ButtonUI;
+import javax.swing.plaf.basic.BasicHTML;
+import javax.swing.text.View;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.ComponentStateFacet;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceConstants.MenuGutterFillKind;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceTextUtilities;
+
+/**
+ * Menu-related utilities.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class MenuUtilities {
+ /**
+ * Key to store the layout metrics. The value should be an instance of
+ * {@link MenuLayoutMetrics}.
+ */
+ private static final String LAYOUT_METRICS = "substancelaf.internal.menus.layoutMetrics";
+
+ /**
+ * Key to store the gutter X location. The value should be an instance of
+ * {@link Integer}.
+ */
+ private static final String GUTTER_X = "substancelaf.internal.menus.gutterX";
+
+ public static final String LAYOUT_INFO = "substancelaf.internal.menus.layoutInfo";
+
+ /**
+ * Listener to track changes in the menu items. Once any property has been
+ * changed, the popup layout metrics on the menu item and its parent popup
+ * menu are cleared.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class MenuPropertyListener implements PropertyChangeListener {
+ /**
+ * Associated menu item.
+ */
+ private JMenuItem menuItem;
+
+ /**
+ * Runnable instance to clean the layout metrics.
+ */
+ private Runnable cleanLayoutMetricsRunnable;
+
+ /**
+ * Creates a new listener.
+ *
+ * @param menuItem
+ * Menu item.
+ */
+ public MenuPropertyListener(final JMenuItem menuItem) {
+ this.menuItem = menuItem;
+ this.cleanLayoutMetricsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ MenuUtilities.cleanPopupLayoutMetrics(menuItem);
+ }
+ };
+ }
+
+ /**
+ * Installs the property change listener on the associated menu item.
+ */
+ public void install() {
+ this.menuItem.addPropertyChangeListener(this);
+ }
+
+ /**
+ * Uninstalls the property change listener from the associated menu
+ * item.
+ */
+ public void uninstall() {
+ this.menuItem.removePropertyChangeListener(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @seejava.beans.PropertyChangeListener#propertyChange(java.beans.
+ * PropertyChangeEvent)
+ */
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (!evt.getPropertyName().equals(MenuUtilities.LAYOUT_METRICS)) {
+ SwingUtilities.invokeLater(cleanLayoutMetricsRunnable);
+ }
+ }
+ }
+
+ /**
+ * Layout information for a single menu item.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class MenuLayoutInfo {
+ /**
+ * View rectangle.
+ */
+ public Rectangle viewRect;
+
+ /**
+ * Icon rectangle.
+ */
+ public Rectangle iconRect;
+
+ /**
+ * Check icon rectangle.
+ */
+ public Rectangle checkIconRect;
+
+ /**
+ * Text rectangle.
+ */
+ public Rectangle textRect;
+
+ /**
+ * Accelerator rectangle.
+ */
+ public Rectangle acceleratorRect;
+
+ /**
+ * Arrow icon rectangle.
+ */
+ public Rectangle arrowIconRect;
+
+ /**
+ * Menu item text.
+ */
+ public String text;
+ }
+
+ /**
+ * Layout metrics for a single popup menu. All menu items in a popup menu
+ * share the same metrics so that different parts (icons, check icons,
+ * texts, accelerator texts and arrow icons) are vertically aligned.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public static class MenuLayoutMetrics {
+ /**
+ * Maximum width of icons of the menu items of this popup menu.
+ */
+ public int maxIconWidth;
+
+ /**
+ * Maximum width of check icons of the menu items of this popup menu.
+ */
+ public int maxCheckIconWidth;
+
+ /**
+ * Maximum width of texts of the menu items of this popup menu.
+ */
+ public int maxTextWidth;
+
+ /**
+ * Maximum width of accelerator texts of the menu items of this popup
+ * menu.
+ */
+ public int maxAcceleratorWidth;
+
+ /**
+ * Maximum width of arrow icons of the menu items of this popup menu.
+ */
+ public int maxArrowIconWidth;
+
+ /**
+ * Maximum gap between icon and text of the menu items of this popup
+ * menu.
+ */
+ public int maxIconTextGap;
+ }
+
+ /**
+ * Returns the layout info for the specified menu item.
+ *
+ * @param menuItem
+ * Menu item.
+ * @param acceleratorFont
+ * Font for the accelerator text.
+ * @param checkIcon
+ * Check icon.
+ * @param arrowIcon
+ * Arrow icon.
+ * @param defaultTextIconGap
+ * Gap between the icon and the text.
+ * @return Layout info for the specified menu item.
+ */
+ public static MenuLayoutInfo getMenuLayoutInfo(boolean forPainting,
+ JMenuItem menuItem, Font acceleratorFont, Icon checkIcon,
+ Icon arrowIcon, int defaultTextIconGap) {
+
+ Insets i = menuItem.getInsets();
+
+ Rectangle iconRect = new Rectangle(0, 0, 0, 0);
+ Rectangle textRect = new Rectangle(0, 0, 0, 0);
+ Rectangle acceleratorRect = new Rectangle(0, 0, 0, 0);
+ Rectangle checkIconRect = new Rectangle(0, 0, 0, 0);
+ Rectangle arrowIconRect = new Rectangle(0, 0, 0, 0);
+ Rectangle viewRect = new Rectangle(Short.MAX_VALUE, Short.MAX_VALUE);
+
+ if (forPainting) {
+ // fix for issue 379 - setting the available
+ // bounds only during the painting.
+ int menuWidth = menuItem.getWidth();
+ int menuHeight = menuItem.getHeight();
+ if ((menuWidth > 0) && (menuHeight > 0))
+ viewRect.setBounds(0, 0, menuWidth, menuHeight);
+
+ viewRect.x += i.left;
+ viewRect.y += i.top;
+ viewRect.width -= (i.right + viewRect.x);
+ viewRect.height -= (i.bottom + viewRect.y);
+ }
+
+ FontMetrics fm = menuItem.getFontMetrics(menuItem.getFont());
+ FontMetrics fmAccel = menuItem.getFontMetrics(acceleratorFont);
+
+ // get Accelerator text
+ KeyStroke accelerator = menuItem.getAccelerator();
+ String acceleratorText = "";
+ if (accelerator != null) {
+ int modifiers = accelerator.getModifiers();
+ if (modifiers > 0) {
+ acceleratorText = KeyEvent.getKeyModifiersText(modifiers);
+ // acceleratorText += "-";
+ acceleratorText += UIManager
+ .getString("MenuItem.acceleratorDelimiter");
+ }
+
+ int keyCode = accelerator.getKeyCode();
+ if (keyCode != 0) {
+ acceleratorText += KeyEvent.getKeyText(keyCode);
+ } else {
+ acceleratorText += accelerator.getKeyChar();
+ }
+ }
+
+ // layout the text and icon
+ String text = layoutMenuItem(menuItem, fm, menuItem.getText(), fmAccel,
+ acceleratorText, menuItem.getIcon(), checkIcon, arrowIcon,
+ menuItem.getVerticalAlignment(), menuItem
+ .getHorizontalAlignment(), menuItem
+ .getVerticalTextPosition(), menuItem
+ .getHorizontalTextPosition(), viewRect, iconRect,
+ textRect, acceleratorRect, checkIconRect, arrowIconRect,
+ menuItem.getText() == null ? 0 : defaultTextIconGap,
+ defaultTextIconGap);
+
+ MenuLayoutInfo mlInfo = new MenuLayoutInfo();
+ mlInfo.checkIconRect = checkIconRect;
+ mlInfo.iconRect = iconRect;
+ mlInfo.textRect = textRect;
+ mlInfo.viewRect = viewRect;
+ mlInfo.acceleratorRect = acceleratorRect;
+ mlInfo.arrowIconRect = arrowIconRect;
+ mlInfo.text = text;
+
+ return mlInfo;
+ }
+
+ /**
+ * Compute and return the location of the icons origin, the location of
+ * origin of the text baseline, and a possibly clipped version of the
+ * compound labels string. Locations are computed relative to the viewRect
+ * rectangle.
+ */
+ private static String layoutMenuItem(JMenuItem menuItem, FontMetrics fm,
+ String text, FontMetrics fmAccel, String acceleratorText,
+ Icon icon, Icon checkIcon, Icon arrowIcon, int verticalAlignment,
+ int horizontalAlignment, int verticalTextPosition,
+ int horizontalTextPosition, Rectangle viewRect, Rectangle iconRect,
+ Rectangle textRect, Rectangle acceleratorRect,
+ Rectangle checkIconRect, Rectangle arrowIconRect, int textIconGap,
+ int menuItemGap) {
+
+ SwingUtilities.layoutCompoundLabel(menuItem, fm, text, icon,
+ verticalAlignment, horizontalAlignment, verticalTextPosition,
+ horizontalTextPosition, viewRect, iconRect, textRect,
+ textIconGap);
+
+ /*
+ * Initialize the acceelratorText bounds rectangle textRect. If a null
+ * or and empty String was specified we substitute "" here and use
+ * 0,0,0,0 for acceleratorTextRect.
+ */
+ if ((acceleratorText == null) || acceleratorText.equals("")) {
+ acceleratorRect.width = acceleratorRect.height = 0;
+ acceleratorText = "";
+ } else {
+ acceleratorRect.width = fmAccel.stringWidth(acceleratorText);
+ acceleratorRect.height = fmAccel.getHeight();
+ }
+
+ /*
+ * Initialize the checkIcon bounds rectangle's width & height.
+ */
+
+ if (useCheckAndArrow(menuItem)) {
+ if (checkIcon != null) {
+ checkIconRect.width = checkIcon.getIconWidth();
+ checkIconRect.height = checkIcon.getIconHeight();
+ } else {
+ checkIconRect.width = checkIconRect.height = 0;
+ }
+
+ /*
+ * Initialize the arrowIcon bounds rectangle width & height.
+ */
+
+ if (arrowIcon != null) {
+ arrowIconRect.width = arrowIcon.getIconWidth();
+ arrowIconRect.height = arrowIcon.getIconHeight();
+ } else {
+ arrowIconRect.width = arrowIconRect.height = 0;
+ }
+ }
+
+ Rectangle labelRect = iconRect.union(textRect);
+ if (menuItem.getComponentOrientation().isLeftToRight()) {
+ textRect.x += menuItemGap;
+ iconRect.x += menuItemGap;
+
+ // Position the Accelerator text rect
+ acceleratorRect.x = viewRect.x + viewRect.width
+ - arrowIconRect.width - menuItemGap - acceleratorRect.width;
+
+ // Position the Check and Arrow Icons
+ if (useCheckAndArrow(menuItem)) {
+ checkIconRect.x = viewRect.x + menuItemGap;
+ textRect.x += menuItemGap + checkIconRect.width;
+ iconRect.x += menuItemGap + checkIconRect.width;
+ arrowIconRect.x = viewRect.x + viewRect.width - menuItemGap
+ - arrowIconRect.width;
+ }
+ } else {
+ textRect.x -= menuItemGap;
+ iconRect.x -= menuItemGap;
+
+ // Position the Accelerator text rect
+ acceleratorRect.x = viewRect.x + arrowIconRect.width + menuItemGap;
+
+ // Position the Check and Arrow Icons
+ if (useCheckAndArrow(menuItem)) {
+ checkIconRect.x = viewRect.x + viewRect.width - menuItemGap
+ - checkIconRect.width;
+ textRect.x -= menuItemGap + checkIconRect.width;
+ iconRect.x -= menuItemGap + checkIconRect.width;
+ arrowIconRect.x = viewRect.x + menuItemGap;
+ }
+ }
+
+ // Align the accelertor text and the check and arrow icons vertically
+ // with the center of the label rect.
+ acceleratorRect.y = labelRect.y + (labelRect.height / 2)
+ - (acceleratorRect.height / 2);
+ if (useCheckAndArrow(menuItem)) {
+ arrowIconRect.y = labelRect.y + (labelRect.height / 2)
+ - (arrowIconRect.height / 2);
+ checkIconRect.y = labelRect.y + (labelRect.height / 2)
+ - (checkIconRect.height / 2);
+ }
+
+ /*
+ * System.out.println("Layout: text="+menuItem.getText()+"\n\tv="
+ * +viewRect+"\n\tc="+checkIconRect+"\n\ti="
+ * +iconRect+"\n\tt="+textRect+"\n\tacc="
+ * +acceleratorRect+"\n\ta="+arrowIconRect+"\n");
+ */
+
+ return text;
+ }
+
+ /*
+ * Returns false if the component is a JMenu and it is a top level menu (on
+ * the menubar).
+ */
+ private static boolean useCheckAndArrow(JMenuItem menuItem) {
+ boolean b = true;
+ if ((menuItem instanceof JMenu)
+ && (((JMenu) menuItem).isTopLevelMenu())) {
+ b = false;
+ }
+ return b;
+ }
+
+ /**
+ * Paints the specified menu item.
+ *
+ * @param g
+ * Graphics context.
+ * @param menuItem
+ * Menu item.
+ * @param checkIcon
+ * Check icon.
+ * @param arrowIcon
+ * Arrow icon.
+ * @param defaultTextIconGap
+ * Gap between the icon and the text.
+ */
+ public static void paintMenuItem(Graphics g, JMenuItem menuItem,
+ Icon checkIcon, Icon arrowIcon, int defaultTextIconGap) {
+ if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
+ return;
+
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ // special handling of menu items covered by lightweight popups
+ // for the defect 297
+ if (Boolean.TRUE
+ .equals(menuItem
+ .getClientProperty(SubstanceCoreUtilities.IS_COVERED_BY_LIGHTWEIGHT_POPUPS))) {
+ JRootPane rootPane = SwingUtilities.getRootPane(menuItem);
+ if (rootPane != null) {
+ Component[] popups = rootPane.getLayeredPane()
+ .getComponentsInLayer(JLayeredPane.POPUP_LAYER);
+ if (popups != null) {
+ int popupIndexToStartWith = SubstanceCoreUtilities
+ .getPopupParentIndexOf(menuItem, popups) - 1;
+ Area clip = new Area(g2d.getClip());
+ for (int i = popupIndexToStartWith; i >= 0; i--) {
+ Component popup = popups[i];
+ // convert the popup bounds to the menu item coordinate
+ // system
+ Rectangle popupArea = SwingUtilities.convertRectangle(
+ rootPane.getLayeredPane(), popup.getBounds(),
+ menuItem);
+ // and subtract this area from the clip
+ clip.subtract(new Area(popupArea));
+ }
+ // at this point we have the original clip minus
+ // all the overlapping lightweight popups that are painted
+ // after this menu item
+ g2d.setClip(clip);
+ }
+ }
+ }
+
+ ButtonModel model = menuItem.getModel();
+ SubstanceMenu menuUi = (SubstanceMenu) menuItem.getUI();
+
+ Font f = menuItem.getFont();
+ g2d.setFont(f);
+
+ // get Accelerator text
+ KeyStroke accelerator = menuItem.getAccelerator();
+ String acceleratorText = "";
+ if (accelerator != null) {
+ int modifiers = accelerator.getModifiers();
+ if (modifiers > 0) {
+ acceleratorText = KeyEvent.getKeyModifiersText(modifiers);
+ acceleratorText += UIManager
+ .getString("MenuItem.acceleratorDelimiter");
+ }
+
+ int keyCode = accelerator.getKeyCode();
+ if (keyCode != 0) {
+ acceleratorText += KeyEvent.getKeyText(keyCode);
+ } else {
+ acceleratorText += accelerator.getKeyChar();
+ }
+ }
+ Icon icon = menuItem.getIcon();
+ // MenuLayoutInfo mli = MenuUtilities.getMenuLayoutInfo(menuItem, menuUi
+ // .getAcceleratorFont(), menuUi.getCheckIcon(), menuUi
+ // .getArrowIcon(), menuUi.getDefaultTextIconGap());
+
+ MenuLayoutInfo mli = MenuUtilities.getMenuLayoutInfo(true, menuItem,
+ menuUi.getAcceleratorFont(), menuUi.getCheckIcon(), menuUi
+ .getArrowIcon(), menuUi.getDefaultTextIconGap());
+ MenuLayoutMetrics popupMetrics = MenuUtilities.getPopupLayoutMetrics(
+ menuItem, true);
+ Insets i = menuItem.getInsets();
+ if (popupMetrics != null) {
+ MenuGutterFillKind gutterFillKind = SubstanceCoreUtilities
+ .getMenuGutterFillKind();
+ boolean needExtraIconTextGap = (gutterFillKind != null)
+ && (gutterFillKind != MenuGutterFillKind.NONE);
+ int gap = popupMetrics.maxIconTextGap;
+ if (menuItem.getComponentOrientation().isLeftToRight()) {
+ int currX = i.left + gap / 2;
+ if (checkIcon != null) {
+ mli.checkIconRect = new Rectangle(currX, i.top, checkIcon
+ .getIconWidth(), checkIcon.getIconHeight());
+ int bump = (popupMetrics.maxCheckIconWidth - checkIcon
+ .getIconWidth()) / 2;
+ mli.checkIconRect.x += bump;
+ }
+ if (popupMetrics.maxCheckIconWidth > 0) {
+ currX += (popupMetrics.maxCheckIconWidth + gap);
+ }
+ if (icon != null) {
+ mli.iconRect = new Rectangle(currX, i.top, icon
+ .getIconWidth(), icon.getIconHeight());
+ int bump = (popupMetrics.maxIconWidth - icon.getIconWidth()) / 2;
+ mli.iconRect.x += bump;
+ }
+ if (popupMetrics.maxIconWidth > 0) {
+ currX += (popupMetrics.maxIconWidth + gap);
+ }
+ menuItem.putClientProperty(GUTTER_X, currX + gap / 2);
+ if (needExtraIconTextGap)
+ currX += gap;
+ if (menuItem.getText() != null) {
+ mli.textRect = new Rectangle(currX, mli.textRect.y,
+ popupMetrics.maxTextWidth, mli.textRect.height);
+ mli.text = menuItem.getText();
+ }
+ currX += (popupMetrics.maxTextWidth + gap);
+ if (popupMetrics.maxAcceleratorWidth > 0) {
+ currX += 5 * gap;
+ // accelerator text is right-aligned
+ mli.acceleratorRect = new Rectangle(currX
+ + popupMetrics.maxAcceleratorWidth
+ - mli.acceleratorRect.width, mli.textRect.y,
+ mli.acceleratorRect.width, mli.textRect.height);
+ }
+ if (popupMetrics.maxAcceleratorWidth > 0) {
+ currX += (popupMetrics.maxAcceleratorWidth + gap);
+ }
+
+ if (arrowIcon != null) {
+ mli.arrowIconRect = new Rectangle(currX, i.top,
+ popupMetrics.maxArrowIconWidth, arrowIcon
+ .getIconHeight());
+ }
+
+ Rectangle labelRect = new Rectangle(0, 0, menuItem.getWidth(),
+ menuItem.getHeight());
+ if (mli.textRect != null)
+ labelRect = mli.textRect;
+ if (mli.iconRect != null) {
+ mli.iconRect.y = labelRect.y + (labelRect.height / 2)
+ - (mli.iconRect.height / 2);
+ }
+ if (mli.arrowIconRect != null) {
+ mli.arrowIconRect.y = labelRect.y + (labelRect.height / 2)
+ - (mli.arrowIconRect.height / 2);
+ }
+ if (mli.checkIconRect != null) {
+ mli.checkIconRect.y = labelRect.y + (labelRect.height / 2)
+ - (mli.checkIconRect.height / 2);
+ }
+ } else {
+ int currX = menuItem.getWidth() - i.right - gap / 2;
+ if (checkIcon != null) {
+ mli.checkIconRect = new Rectangle(currX
+ - popupMetrics.maxCheckIconWidth, i.top, checkIcon
+ .getIconWidth(), checkIcon.getIconHeight());
+ int bump = (popupMetrics.maxCheckIconWidth - checkIcon
+ .getIconWidth()) / 2;
+ mli.checkIconRect.x += bump;
+ }
+ if (popupMetrics.maxCheckIconWidth > 0) {
+ currX -= (popupMetrics.maxCheckIconWidth + gap);
+ }
+ if (icon != null) {
+ // icons are center-aligned
+ mli.iconRect = new Rectangle(currX
+ - popupMetrics.maxIconWidth, i.top, icon
+ .getIconWidth(), icon.getIconHeight());
+ int bump = (popupMetrics.maxIconWidth - icon.getIconWidth()) / 2;
+ mli.iconRect.x += bump;
+ }
+ if (popupMetrics.maxIconWidth > 0) {
+ currX -= (popupMetrics.maxIconWidth + gap);
+ }
+ menuItem.putClientProperty(GUTTER_X, currX + gap / 2);
+ if (needExtraIconTextGap)
+ currX -= gap;
+ if (menuItem.getText() != null) {
+ // text is right-aligned
+ mli.textRect = new Rectangle(currX - mli.textRect.width,
+ mli.textRect.y, popupMetrics.maxTextWidth,
+ mli.textRect.height);
+ mli.text = menuItem.getText();
+ }
+ currX -= (popupMetrics.maxTextWidth + gap);
+ if (popupMetrics.maxAcceleratorWidth > 0) {
+ currX -= 5 * gap;
+ // accelerator text is left-aligned
+ mli.acceleratorRect = new Rectangle(currX
+ - popupMetrics.maxAcceleratorWidth, mli.textRect.y,
+ mli.acceleratorRect.width, mli.textRect.height);
+ }
+ if (popupMetrics.maxAcceleratorWidth > 0) {
+ currX -= (popupMetrics.maxAcceleratorWidth + gap);
+ }
+
+ if (arrowIcon != null) {
+ mli.arrowIconRect = new Rectangle(currX
+ - popupMetrics.maxArrowIconWidth, i.top,
+ popupMetrics.maxArrowIconWidth, arrowIcon
+ .getIconHeight());
+ }
+
+ Rectangle labelRect = new Rectangle(0, 0, menuItem.getWidth(),
+ menuItem.getHeight());
+ if (mli.textRect != null)
+ labelRect = mli.textRect;
+ if (mli.iconRect != null) {
+ mli.iconRect.y = labelRect.y + (labelRect.height / 2)
+ - (mli.iconRect.height / 2);
+ }
+ if (mli.arrowIconRect != null) {
+ mli.arrowIconRect.y = labelRect.y + (labelRect.height / 2)
+ - (mli.arrowIconRect.height / 2);
+ }
+ if (mli.checkIconRect != null) {
+ mli.checkIconRect.y = labelRect.y + (labelRect.height / 2)
+ - (mli.checkIconRect.height / 2);
+ }
+ }
+ }
+ menuItem.putClientProperty(LAYOUT_INFO, mli);
+ Component parent = menuItem.getParent();
+ if (parent instanceof JPopupMenu) {
+ ((JPopupMenu) parent).putClientProperty(GUTTER_X, menuItem
+ .getClientProperty(GUTTER_X));
+ }
+
+ // paint menu background
+ paintBackground(g2d, menuItem);
+ // paint menu highlight
+ SubstanceMenuBackgroundDelegate.paintHighlights(g, menuItem, 0.5f);
+
+ Graphics2D graphics = (Graphics2D) g2d.create();
+ if (mli.text != null) {
+ View v = (View) menuItem.getClientProperty(BasicHTML.propertyKey);
+ if (v != null) {
+ v.paint(graphics, mli.textRect);
+ } else {
+ SubstanceTextUtilities.paintText(graphics, menuItem,
+ mli.textRect, mli.text, menuItem
+ .getDisplayedMnemonicIndex());
+ }
+ }
+ // Draw the Accelerator Text
+ if (acceleratorText != null && !acceleratorText.equals("")) {
+ SubstanceTextUtilities.paintText(graphics, menuItem,
+ mli.acceleratorRect, acceleratorText, -1);
+ }
+
+ float textAlpha = SubstanceColorSchemeUtilities.getAlpha(menuItem,
+ ComponentState.getState(menuItem.getModel(), menuItem, true));
+
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(menuItem,
+ textAlpha, g2d));
+ // Paint the Check
+ if (checkIcon != null) {
+ if (useCheckAndArrow(menuItem))
+ checkIcon.paintIcon(menuItem, graphics, mli.checkIconRect.x,
+ mli.checkIconRect.y);
+ }
+
+ // Paint the Icon
+ if (icon != null) {
+ if (!model.isEnabled()) {
+ icon = menuItem.getDisabledIcon();
+ } else if (model.isPressed() && model.isArmed()) {
+ icon = menuItem.getPressedIcon();
+ if (icon == null) {
+ // Use default icon
+ icon = menuItem.getIcon();
+ }
+ } else {
+ icon = menuItem.getIcon();
+ }
+
+ if (icon != null) {
+ boolean useThemed = SubstanceCoreUtilities
+ .useThemedDefaultIcon(menuItem);
+
+ if (!useThemed) {
+ icon.paintIcon(menuItem, g2d, mli.iconRect.x,
+ mli.iconRect.y);
+ } else {
+ Icon themed = SubstanceCoreUtilities.getThemedIcon(
+ menuItem, icon);
+ boolean useRegularVersion = model.isPressed()
+ || model.isSelected();
+ if (useRegularVersion) {
+ icon.paintIcon(menuItem, g2d, mli.iconRect.x,
+ mli.iconRect.y);
+ } else {
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) menuItem
+ .getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+ float rolloverAmount = Math
+ .max(
+ stateTransitionTracker
+ .getFacetStrength(ComponentStateFacet.ROLLOVER),
+ stateTransitionTracker
+ .getFacetStrength(ComponentStateFacet.ARM));
+ if (rolloverAmount > 0) {
+ themed.paintIcon(menuItem, g2d, mli.iconRect.x,
+ mli.iconRect.y);
+ g2d.setComposite(LafWidgetUtilities
+ .getAlphaComposite(menuItem,
+ rolloverAmount, g));
+ icon.paintIcon(menuItem, g2d, mli.iconRect.x,
+ mli.iconRect.y);
+ g2d.setComposite(LafWidgetUtilities
+ .getAlphaComposite(menuItem, g));
+ } else {
+ // if (model.isRollover() || model.isArmed()) {
+ // icon.paintIcon(menuItem, g2d, mli.iconRect.x,
+ // mli.iconRect.y);
+ // } else {
+ themed.paintIcon(menuItem, g2d, mli.iconRect.x,
+ mli.iconRect.y);
+ // }
+ }
+ }
+ }
+ }
+ }
+
+ // Paint the Arrow
+ if (arrowIcon != null) {
+ if (useCheckAndArrow(menuItem))
+ arrowIcon.paintIcon(menuItem, graphics, mli.arrowIconRect.x,
+ mli.arrowIconRect.y);
+ }
+ graphics.dispose();
+
+ // g2d.setColor(Color.red);
+ // if (mli.iconRect != null)
+ // g2d.draw(mli.iconRect);
+ // if (mli.checkIconRect != null)
+ // g2d.draw(mli.checkIconRect);
+ // if (mli.textRect != null)
+ // g2d.draw(mli.textRect);
+
+ // g2d.setColor(Color.red);
+ // g2d.drawRect(mli.checkIconRect.x, mli.checkIconRect.y,
+ // mli.checkIconRect.width, mli.checkIconRect.height);
+ // g2d.drawRect(mli.iconRect.x, mli.iconRect.y, mli.iconRect.width,
+ // mli.iconRect.height);
+ // g2d.drawRect(mli.textRect.x, mli.textRect.y, mli.textRect.width,
+ // mli.textRect.height);
+ // g2d.drawRect(mli.arrowIconRect.x, mli.arrowIconRect.y,
+ // mli.arrowIconRect.width, mli.arrowIconRect.height);
+ // g2d.drawRect(mli.acceleratorRect.x, mli.acceleratorRect.y,
+ // mli.acceleratorRect.width, mli.acceleratorRect.height);
+
+ g2d.dispose();
+ }
+
+ /**
+ * Paints the background of the specified menu item.
+ *
+ * @param g
+ * Graphics context.
+ * @param menuItem
+ * Menu item.
+ */
+ private static void paintBackground(Graphics g, JMenuItem menuItem) {
+ int textOffset = MenuUtilities.getTextOffset(menuItem, menuItem
+ .getParent());
+ SubstanceMenuBackgroundDelegate
+ .paintBackground(g, menuItem, textOffset);
+ }
+
+ /**
+ * Returns the layout metrics of the specified popup menu.
+ *
+ * @param popupMenu
+ * Popup menu.
+ * @return Layout metrics of the specified popup menu.
+ */
+ protected static MenuLayoutMetrics getMetrics(JPopupMenu popupMenu,
+ boolean forPainting) {
+ MenuLayoutMetrics metrics = new MenuLayoutMetrics();
+ for (int i = 0; i < popupMenu.getComponentCount(); i++) {
+ Component comp = popupMenu.getComponent(i);
+ if (comp instanceof JMenuItem) {
+ JMenuItem childMenuItem = (JMenuItem) comp;
+ ButtonUI bui = childMenuItem.getUI();
+ if (!(bui instanceof SubstanceMenu))
+ continue;
+ SubstanceMenu ui = (SubstanceMenu) bui;
+ MenuLayoutInfo mli = MenuUtilities.getMenuLayoutInfo(
+ forPainting, childMenuItem, ui.getAcceleratorFont(), ui
+ .getCheckIcon(), ui.getArrowIcon(), ui
+ .getDefaultTextIconGap());
+ metrics.maxIconWidth = Math.max(metrics.maxIconWidth,
+ mli.iconRect.width);
+ metrics.maxCheckIconWidth = Math.max(metrics.maxCheckIconWidth,
+ mli.checkIconRect.width);
+ metrics.maxTextWidth = Math.max(metrics.maxTextWidth,
+ mli.textRect.width);
+ metrics.maxAcceleratorWidth = Math.max(
+ metrics.maxAcceleratorWidth, mli.acceleratorRect.width);
+ metrics.maxArrowIconWidth = Math.max(metrics.maxArrowIconWidth,
+ mli.arrowIconRect.width);
+ metrics.maxIconTextGap = Math.max(metrics.maxIconTextGap, ui
+ .getDefaultTextIconGap());
+ }
+ }
+ return metrics;
+ }
+
+ /**
+ * Returns the layout metrics of the popup menu of the specified menu item.
+ *
+ * @param menuItem
+ * Menu item.
+ * @return Layout metrics of the popup menu of the specified menu item.
+ */
+ public static MenuLayoutMetrics getPopupLayoutMetrics(JMenuItem menuItem,
+ boolean forPainting) {
+ Component comp = menuItem.getParent();
+ if (comp instanceof JPopupMenu) {
+ JPopupMenu popupMenu = (JPopupMenu) comp;
+ return getPopupLayoutMetrics(popupMenu, forPainting);
+ }
+ // fix for issue 347 - menu item in menu bar is not
+ // covered by the code above
+ if (!(comp instanceof JMenu)) {
+ ButtonUI bui = menuItem.getUI();
+ if (bui instanceof SubstanceMenu) {
+ SubstanceMenu ui = (SubstanceMenu) bui;
+
+ MenuLayoutInfo mli = MenuUtilities.getMenuLayoutInfo(
+ forPainting, menuItem, ui.getAcceleratorFont(), ui
+ .getCheckIcon(), ui.getArrowIcon(), ui
+ .getDefaultTextIconGap());
+ MenuLayoutMetrics metrics = new MenuLayoutMetrics();
+ metrics.maxIconWidth = mli.iconRect.width;
+ metrics.maxCheckIconWidth = mli.checkIconRect.width;
+ metrics.maxTextWidth = mli.textRect.width;
+ metrics.maxAcceleratorWidth = mli.acceleratorRect.width;
+ metrics.maxArrowIconWidth = mli.arrowIconRect.width;
+ metrics.maxIconTextGap = ui.getDefaultTextIconGap();
+ return metrics;
+ }
+ }
+
+ // If here, the parent is not popup menu - this is a top-level menu
+ // in a menu bar. No need to align the different rectangles
+ return null;
+ }
+
+ public static MenuLayoutMetrics getPopupLayoutMetrics(JPopupMenu popupMenu,
+ boolean forPainting) {
+ Object prop = popupMenu.getClientProperty(LAYOUT_METRICS);
+ if (prop instanceof MenuLayoutMetrics) {
+ return (MenuLayoutMetrics) prop;
+ }
+ MenuLayoutMetrics metrics = getMetrics(popupMenu, forPainting);
+ popupMenu.putClientProperty(LAYOUT_METRICS, metrics);
+ return metrics;
+ }
+
+ /**
+ * Cleans the layout metrics of the popup menu of the specified menu item.
+ *
+ * @param menuItem
+ * Menu item.
+ */
+ private static void cleanPopupLayoutMetrics(JMenuItem menuItem) {
+ Component comp = menuItem.getParent();
+ if (comp instanceof JPopupMenu) {
+ JPopupMenu popupMenu = (JPopupMenu) comp;
+ cleanPopupLayoutMetrics(popupMenu);
+ }
+ }
+
+ /**
+ * Cleans the layout metrics of the specified popup menu.
+ *
+ * @param popupMenu
+ * Popup menu.
+ */
+ public static void cleanPopupLayoutMetrics(JPopupMenu popupMenu) {
+ if (popupMenu != null) {
+ popupMenu.putClientProperty(LAYOUT_METRICS, null);
+ }
+ }
+
+ /**
+ * Returns the preferred width of the specified menu item. The preferred
+ * width depends on the layout metrics of the entire popup menu of this menu
+ * item.
+ *
+ * @param menuItem
+ * Menu item.
+ * @return Preferred width of the specified menu item
+ */
+ public static int getPreferredWidth(JMenuItem menuItem) {
+ Insets ins = menuItem.getInsets();
+ MenuLayoutMetrics popupMetrics = MenuUtilities.getPopupLayoutMetrics(
+ menuItem, false);
+
+ int width = popupMetrics.maxCheckIconWidth + popupMetrics.maxIconWidth
+ + popupMetrics.maxTextWidth + popupMetrics.maxAcceleratorWidth
+ + popupMetrics.maxArrowIconWidth + ins.left + ins.right;
+
+ int gapsToAdd = 0;
+ if (popupMetrics.maxCheckIconWidth > 0)
+ gapsToAdd++;
+ if (popupMetrics.maxIconWidth > 0)
+ gapsToAdd++;
+ if (popupMetrics.maxAcceleratorWidth > 0)
+ gapsToAdd++;
+ if (popupMetrics.maxArrowIconWidth > 0)
+ gapsToAdd++;
+
+ int gap = popupMetrics.maxIconTextGap;
+ width += ((1 + gapsToAdd) * gap);
+
+ if (popupMetrics.maxAcceleratorWidth > 0) {
+ // at least one menu item has accelerator text
+ width += 5 * gap;
+ }
+
+ MenuGutterFillKind gutterFillKind = SubstanceCoreUtilities
+ .getMenuGutterFillKind();
+ boolean needExtraIconTextGap = (gutterFillKind != null)
+ && (gutterFillKind != MenuGutterFillKind.NONE);
+ if (needExtraIconTextGap)
+ width += gap;
+
+ return width;
+ }
+
+ /**
+ * Returns the text offset of the specified menu item.
+ *
+ * @param menuItem
+ * Menu item.
+ * @param menuItemParent
+ * Menu item parent.
+ * @return Text offset of the specified menu item.
+ */
+ public static int getTextOffset(JComponent menuItem,
+ Component menuItemParent) {
+ if (!(menuItemParent instanceof JPopupMenu)) {
+ return 0;
+ }
+
+ Object itemProp = menuItem.getClientProperty(GUTTER_X);
+ if (itemProp instanceof Integer) {
+ return (Integer) itemProp;
+ }
+
+ JPopupMenu popupMenu = (JPopupMenu) menuItemParent;
+ Object parentProp = popupMenu.getClientProperty(GUTTER_X);
+ if (parentProp instanceof Integer) {
+ return (Integer) parentProp;
+ }
+ return 0;
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/menu/SubstanceMenu.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/menu/SubstanceMenu.java
new file mode 100644
index 0000000..af02d56
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/menu/SubstanceMenu.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.menu;
+
+import java.awt.Font;
+
+import javax.swing.Icon;
+import javax.swing.JMenuItem;
+
+/**
+ * Base interface for all menu-related UI delegates.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface SubstanceMenu {
+ /**
+ * Name for internal client property that holds the cached text offset.
+ */
+ // public static final String SINGLE_MENU_TEXT_OFFSET =
+ // "substancelaf.internal.singleMenuTextOffset";
+ /**
+ * Returns the font of the accelerator string of <code>this</code> UI
+ * delegate.
+ *
+ * @return The font of the accelerator string of <code>this</code> UI
+ * delegate.
+ */
+ public Font getAcceleratorFont();
+
+ /**
+ * Returns the checkmark icon of <code>this</code> UI delegate.
+ *
+ * @return The checkmark icon of <code>this</code> UI delegate.
+ */
+ public Icon getCheckIcon();
+
+ /**
+ * Returns the arrow icon of <code>this</code> UI delegate.
+ *
+ * @return The arrow icon of <code>this</code> UI delegate.
+ */
+ public Icon getArrowIcon();
+
+ /**
+ * Returns the default gap between the icon and the text for
+ * <code>this</code> delegate.
+ *
+ * @return The default gap between the icon and the text for
+ * <code>this</code> delegate.
+ */
+ public int getDefaultTextIconGap();
+
+ /**
+ * Returns the associated menu item.
+ *
+ * @return The associated menu item.
+ */
+ public JMenuItem getAssociatedMenuItem();
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/menu/SubstanceMenuBackgroundDelegate.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/menu/SubstanceMenuBackgroundDelegate.java
new file mode 100644
index 0000000..cd9a0be
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/menu/SubstanceMenuBackgroundDelegate.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.menu;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.LinearGradientPaint;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.MultipleGradientPaint.CycleMethod;
+import java.util.Map;
+
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+
+import org.pushingpixels.lafwidget.LafWidgetUtilities;
+import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceConstants.MenuGutterFillKind;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
+import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
+import org.pushingpixels.substance.internal.animation.StateTransitionTracker.ModelStateInfo;
+import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
+import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+/**
+ * Delegate for painting background of menu items.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceMenuBackgroundDelegate {
+ /**
+ * Updates the specified menu item with the background that matches the
+ * provided parameters.
+ *
+ * @param g
+ * Graphic context.
+ * @param menuItem
+ * Menu item.
+ * @param textOffset
+ * The offset of the menu item text.
+ */
+ public static void paintBackground(Graphics g, Component menuItem,
+ int textOffset) {
+ if (!menuItem.isShowing())
+ return;
+ int menuWidth = menuItem.getWidth();
+ int menuHeight = menuItem.getHeight();
+
+ Graphics2D graphics = (Graphics2D) g.create();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+
+ BackgroundPaintingUtils.update(graphics, menuItem, false);
+
+ if (textOffset == 0) {
+ // issue 465 - the value can be 0 - leading to
+ // IllegalArgumenException on LinearGradientPaint below.
+ return;
+ }
+ if (menuItem.getParent() instanceof JPopupMenu) {
+ if (menuItem.getComponentOrientation().isLeftToRight()) {
+ MenuGutterFillKind fillKind = SubstanceCoreUtilities
+ .getMenuGutterFillKind();
+ if (fillKind != MenuGutterFillKind.NONE) {
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(menuItem, ComponentState.ENABLED);
+ Color leftColor = ((fillKind == MenuGutterFillKind.SOFT_FILL) || (fillKind == MenuGutterFillKind.HARD)) ? scheme
+ .getUltraLightColor()
+ : scheme.getLightColor();
+ Color rightColor = ((fillKind == MenuGutterFillKind.SOFT_FILL) || (fillKind == MenuGutterFillKind.SOFT)) ? scheme
+ .getUltraLightColor()
+ : scheme.getLightColor();
+ LinearGradientPaint gp = new LinearGradientPaint(0, 0,
+ textOffset, 0, new float[] { 0.0f, 1.0f },
+ new Color[] { leftColor, rightColor },
+ CycleMethod.REPEAT);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ menuItem, 0.7f, g));
+
+ // System.out.println(menuItem.getText()
+ // + "["
+ // + menuItem.isEnabled()
+ // + "] : "
+ // + ((AlphaComposite) graphics.getComposite())
+ // .getAlpha() + ", " + leftColor + "->"
+ // + rightColor);
+ //
+ graphics.setPaint(gp);
+ graphics.fillRect(0, 0, textOffset - 2, menuHeight);
+ }
+ } else {
+ // fix for defect 125 - support of RTL menus
+ MenuGutterFillKind fillKind = SubstanceCoreUtilities
+ .getMenuGutterFillKind();
+ if (fillKind != MenuGutterFillKind.NONE) {
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(menuItem, ComponentState.ENABLED);
+ Color leftColor = ((fillKind == MenuGutterFillKind.HARD_FILL) || (fillKind == MenuGutterFillKind.HARD)) ? scheme
+ .getLightColor()
+ : scheme.getUltraLightColor();
+ Color rightColor = ((fillKind == MenuGutterFillKind.HARD_FILL) || (fillKind == MenuGutterFillKind.SOFT)) ? scheme
+ .getLightColor()
+ : scheme.getUltraLightColor();
+
+ LinearGradientPaint gp = new LinearGradientPaint(
+ textOffset, 0, menuWidth, 0, new float[] { 0.0f,
+ 1.0f },
+ new Color[] { leftColor, rightColor },
+ CycleMethod.REPEAT);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ menuItem, 0.7f, g));
+ graphics.setPaint(gp);
+ graphics.fillRect(textOffset - 2, 0, menuWidth, menuHeight);
+ }
+ }
+ }
+ // }
+
+ graphics.dispose();
+ }
+
+ /**
+ * Paints menu highlights.
+ *
+ * @param g
+ * Graphics context.
+ * @param menuItem
+ * Menu item.
+ * @param borderAlpha
+ * Alpha channel for painting the border.
+ */
+ public static void paintHighlights(Graphics g, JMenuItem menuItem,
+ float borderAlpha) {
+ Graphics2D graphics = (Graphics2D) g.create();
+
+ TransitionAwareUI transitionAwareUI = (TransitionAwareUI) menuItem
+ .getUI();
+ StateTransitionTracker stateTransitionTracker = transitionAwareUI
+ .getTransitionTracker();
+ ModelStateInfo modelStateInfo = stateTransitionTracker
+ .getModelStateInfo();
+
+ ComponentState currState = modelStateInfo
+ .getCurrModelStateNoSelection();
+
+ if (currState.isDisabled()) {
+ // no highlights on disabled menus
+ return;
+ }
+ Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
+ .getStateNoSelectionContributionMap();
+ // if ("Check enabled unselected".equals(menuItem.getText())) {
+ // System.out.println("New contribution map");
+ // for (Map.Entry<ComponentState,
+ // StateTransitionTracker.StateContributionInfo> existing : activeStates
+ // .entrySet()) {
+ // System.out.println("\t" + existing.getKey() + " in ["
+ // + existing.getValue().start + ":"
+ // + existing.getValue().end + "] -> "
+ // + existing.getValue().curr);
+ // }
+ // }
+
+ if ((currState == ComponentState.ENABLED) && (activeStates.size() == 1)) {
+ // default state - no highlights
+ return;
+ }
+
+ for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
+ .entrySet()) {
+ ComponentState activeState = stateEntry.getKey();
+ float alpha = SubstanceColorSchemeUtilities.getHighlightAlpha(
+ menuItem, activeState)
+ * stateEntry.getValue().getContribution();
+ if (alpha == 0.0f)
+ continue;
+
+ SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(menuItem,
+ ColorSchemeAssociationKind.HIGHLIGHT, activeState);
+ SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
+ .getColorScheme(menuItem,
+ ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
+ activeState);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ menuItem, alpha, g));
+ HighlightPainterUtils.paintHighlight(graphics, null, menuItem,
+ new Rectangle(0, 0, menuItem.getWidth(), menuItem
+ .getHeight()), borderAlpha, null, fillScheme,
+ borderScheme);
+ graphics.setComposite(LafWidgetUtilities.getAlphaComposite(
+ menuItem, g));
+ }
+
+ graphics.dispose();
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/scroll/SubstanceScrollButton.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/scroll/SubstanceScrollButton.java
new file mode 100644
index 0000000..cab1874
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/scroll/SubstanceScrollButton.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.scroll;
+
+import java.awt.Insets;
+
+import javax.swing.JButton;
+import javax.swing.SwingConstants;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.AnimationFacet;
+import org.pushingpixels.substance.api.SubstanceConstants;
+import org.pushingpixels.substance.internal.utils.Sideable;
+import org.pushingpixels.substance.internal.utils.SubstanceInternalArrowButton;
+
+/**
+ * Scroll bar button in <b>Substance</b> look and feel.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceScrollButton extends JButton implements UIResource,
+ Sideable, SubstanceInternalArrowButton {
+ static {
+ AnimationConfigurationManager.getInstance().disallowAnimations(
+ AnimationFacet.GHOSTING_BUTTON_PRESS,
+ SubstanceScrollButton.class);
+ AnimationConfigurationManager.getInstance().disallowAnimations(
+ AnimationFacet.GHOSTING_ICON_ROLLOVER,
+ SubstanceScrollButton.class);
+ }
+ /**
+ * Button orientation.
+ */
+ private int orientation;
+
+ /**
+ * Simple constructor.
+ *
+ * @param orientation
+ * The button icon arrow orientation.
+ */
+ public SubstanceScrollButton(int orientation) {
+ super();
+ this.setRequestFocusEnabled(false);
+ this.setMargin(new Insets(0, 0, 0, 2));
+ this.orientation = orientation;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.Component#isFocusable()
+ */
+ @Override
+ public boolean isFocusable() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.utils.Sideable#getSide()
+ */
+ @Override
+ public SubstanceConstants.Side getSide() {
+ switch (this.orientation) {
+ case SwingConstants.NORTH:
+ return SubstanceConstants.Side.BOTTOM;
+ case SwingConstants.WEST:
+ return SubstanceConstants.Side.RIGHT;
+ case SwingConstants.SOUTH:
+ return SubstanceConstants.Side.TOP;
+ case SwingConstants.EAST:
+ return SubstanceConstants.Side.LEFT;
+ default:
+ return null;
+ }
+ }
+}
diff --git a/substance/src/main/java/org/pushingpixels/substance/internal/utils/scroll/SubstanceScrollPaneBorder.java b/substance/src/main/java/org/pushingpixels/substance/internal/utils/scroll/SubstanceScrollPaneBorder.java
new file mode 100644
index 0000000..ea89bc2
--- /dev/null
+++ b/substance/src/main/java/org/pushingpixels/substance/internal/utils/scroll/SubstanceScrollPaneBorder.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.substance.internal.utils.scroll;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.UIResource;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.painter.border.StandardBorderPainter;
+import org.pushingpixels.substance.internal.painter.SimplisticSoftBorderPainter;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
+
+/**
+ * Default border on {@link JScrollPane}s. Provides continuous appearance of the
+ * border + scroll bars.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SubstanceScrollPaneBorder implements Border, UIResource {
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#getBorderInsets(java.awt.Component)
+ */
+ @Override
+ public Insets getBorderInsets(Component c) {
+ float borderStrokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ int ins = (int) borderStrokeWidth;
+ return new Insets(ins, ins, ins, ins);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#isBorderOpaque()
+ */
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.swing.border.Border#paintBorder(java.awt.Component,
+ * java.awt.Graphics, int, int, int, int)
+ */
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height) {
+ if (!(c instanceof JScrollPane)) {
+ // Applications (such as NetBeans RCP) may incorrectly assume
+ // that scroll pane border specified by the ""ScrollPane.border"
+ // UIManager key by a look-and-feel can be installed on any
+ // component. In case this component is not JScrollPane, do
+ // nothing.
+ return;
+ }
+
+ JScrollPane scrollPane = (JScrollPane) c;
+ JScrollBar vertical = scrollPane.getVerticalScrollBar();
+ JScrollBar horizontal = scrollPane.getHorizontalScrollBar();
+ JViewport columnHeader = scrollPane.getColumnHeader();
+
+ StandardBorderPainter painter = new SimplisticSoftBorderPainter();
+
+ SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
+ .getColorScheme(c, ColorSchemeAssociationKind.BORDER, c
+ .isEnabled() ? ComponentState.ENABLED
+ : ComponentState.DISABLED_UNSELECTED);
+
+ float borderStrokeWidth = SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(c));
+ int borderDelta = (int) Math.floor(SubstanceSizeUtils
+ .getBorderStrokeWidth(SubstanceSizeUtils
+ .getComponentFontSize(c)) / 2.0);
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setStroke(new BasicStroke(borderStrokeWidth,
+ BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ x += borderDelta;
+ y += borderDelta;
+ width -= 2 * borderDelta;
+ height -= 2 * borderDelta;
+
+ boolean horizontalVisible = (horizontal != null)
+ && horizontal.isVisible();
+ boolean verticalVisible = (vertical != null) && vertical.isVisible();
+ boolean hasRowHeader = (scrollPane.getRowHeader() != null);
+
+ if (scrollPane.getComponentOrientation().isLeftToRight()) {
+ // top portion
+ g2d.setColor(painter.getTopBorderColor(scheme));
+ if (verticalVisible && (columnHeader == null)) {
+ g2d.drawLine(x, y, x + width - vertical.getWidth(), y);
+ } else {
+ g2d.drawLine(x, y, x + width, y);
+ }
+
+ // left portion
+ g2d.setColor(painter.getTopBorderColor(scheme));
+ if (horizontalVisible && !hasRowHeader) {
+ g2d.drawLine(x, y, x, y + height - horizontal.getHeight());
+ } else {
+ g2d.drawLine(x, y, x, y + height);
+ }
+
+ // bottom portion
+ g2d.setColor(painter.getBottomBorderColor(scheme));
+ if (horizontalVisible) {
+ if (hasRowHeader) {
+ g2d.drawLine(x, y + height - 1, x
+ + scrollPane.getRowHeader().getSize().width, y
+ + height - 1);
+ }
+ } else {
+ if (verticalVisible) {
+ g2d.drawLine(x, y + height - 1, x + width
+ - vertical.getWidth(), y + height - 1);
+ } else {
+ g2d.drawLine(x, y + height - 1, x + width, y + height - 1);
+ }
+ }
+
+ // right portion
+ g2d.setColor(painter.getBottomBorderColor(scheme));
+ if (verticalVisible) {
+ // g.drawLine(x + width - 1, y + vertical.getHeight(), x + width
+ // - 1, y + height);
+
+ if (columnHeader != null) {
+ g2d.drawLine(x + width - 1, y, x + width - 1, y
+ + columnHeader.getHeight());
+ }
+ } else {
+ if (horizontalVisible)
+ g2d.drawLine(x + width - 1, y, x + width - 1, y + height
+ - horizontal.getHeight());
+ else
+ g2d.drawLine(x + width - 1, y, x + width - 1, y + height);
+ }
+ } else {
+ // top portion
+ g2d.setColor(painter.getTopBorderColor(scheme));
+ if (verticalVisible && (columnHeader == null)) {
+ g2d.drawLine(x + vertical.getWidth(), y, x + width, y);
+ } else {
+ g2d.drawLine(x, y, x + width, y);
+ }
+
+ // left portion
+ g2d.setColor(painter.getBottomBorderColor(scheme));
+ if (verticalVisible) {
+ // g.drawLine(x, y, x, y + height - horizontal.getHeight());
+ if (columnHeader != null) {
+ g2d.drawLine(x, y, x, y + columnHeader.getHeight());
+ }
+ } else {
+ if (horizontalVisible) {
+ g2d.drawLine(x, y, x, y + height - horizontal.getHeight());
+ } else {
+ g2d.drawLine(x, y, x, y + height - 1);
+ }
+ }
+
+ // bottom portion
+ g2d.setColor(painter.getBottomBorderColor(scheme));
+ if (horizontalVisible) {
+ if (hasRowHeader) {
+ g2d.drawLine(x + width
+ - scrollPane.getRowHeader().getSize().width, y
+ + height - 1, x + width - 1, y + height - 1);
+ }
+ } else {
+ if (verticalVisible) {
+ g2d.drawLine(x + vertical.getWidth(), y + height - 1, x
+ + width, y + height - 1);
+ } else {
+ g2d.drawLine(x, y + height - 1, x + width, y + height - 1);
+ }
+ }
+
+ // right portion
+ g2d.setColor(painter.getTopBorderColor(scheme));
+ if (horizontalVisible && !hasRowHeader) {
+ g2d.drawLine(x + width - 1, y, x + width - 1, y + height
+ - horizontal.getHeight());
+ } else {
+ g2d.drawLine(x + width - 1, y, x + width - 1, y + height);
+ }
+ }
+ g2d.dispose();
+ }
+}
diff --git a/substance/src/main/resources/Looks.license b/substance/src/main/resources/Looks.license
new file mode 100644
index 0000000..94a30e8
--- /dev/null
+++ b/substance/src/main/resources/Looks.license
@@ -0,0 +1,26 @@
+Copyright (c) 2001-2005 JGoodies Karsten Lentzsch and contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+ * Neither the name of the JGoodies Karsten Lentzsch and contributors nor
+the names of its contributors may be used to endorse or promote products
+derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/substance/src/main/resources/META-INF/substance-plugin.xml b/substance/src/main/resources/META-INF/substance-plugin.xml
new file mode 100644
index 0000000..086aaf4
--- /dev/null
+++ b/substance/src/main/resources/META-INF/substance-plugin.xml
@@ -0,0 +1,4 @@
+<laf-plugin>
+ <component-plugin-class>org.pushingpixels.substance.internal.plugin.BasePlugin</component-plugin-class>
+ <skin-plugin-class>org.pushingpixels.substance.internal.plugin.BaseSkinPlugin</skin-plugin-class>
+</laf-plugin>
\ No newline at end of file
diff --git a/substance/src/main/resources/META-INF/substance.highlight.properties b/substance/src/main/resources/META-INF/substance.highlight.properties
new file mode 100644
index 0000000..bcd6a6b
--- /dev/null
+++ b/substance/src/main/resources/META-INF/substance.highlight.properties
@@ -0,0 +1,6 @@
+javax.swing.JList
+javax.swing.JTable
+javax.swing.JTree
+javax.swing.JMenuItem
+javax.swing.table.JTableHeader
+
diff --git a/substance/src/main/resources/Quaqua.license b/substance/src/main/resources/Quaqua.license
new file mode 100644
index 0000000..084c361
--- /dev/null
+++ b/substance/src/main/resources/Quaqua.license
@@ -0,0 +1,632 @@
+Use of the Quaqua Look and Feel is entirely at your own risk.
+I will not be liable for any data loss, hardware damage or
+whatever this program might cause.
+
+Permission to use this release of the Quaqua Look and Feel is
+granted provided you agree with its license terms, that the
+license fee is paid and the copyright notice and this license
+notice appear in all copies and in supporting documentation.
+
+The license terms applies to the version of the Quaqua Look and
+Feel, which is stated at the top of this document. Different
+versions may have different license terms.
+
+The license terms of the Quaqua Look and Feel do not apply to
+third party libraries that are included with the Quaqua Look and
+Feel. See section License Terms of Third Party Libraries.
+License Terms
+
+Source code, documentation and binaries of the Quaqua Look and
+Feel (also called "this software") are subject to the GNU Lesser
+General Public License (LGPL).
+
+Alternatively, this software may be used under the terms of the
+Modified BSD License.
+
+Third party software used by the Quaqua Look and Feel come with
+their own license restrictions.
+
+You may not use or disclose this software except in compliance
+with these license term.
+Modified BSD License
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ 1. Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ 3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+GNU Lesser General Public License
+Table of Contents
+
+ * GNU LESSER GENERAL PUBLIC LICENSE
+ o Preamble
+ o TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+ o How to Apply These Terms to Your New Libraries
+
+GNU LESSER GENERAL PUBLIC LICENSE
+
+Version 2.1, February 1999
+
+Copyright (C) 1991, 1999 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. [This is the first released version
+of the Lesser GPL. It also counts as the successor of the GNU
+Library Public License, version 2, hence the version number 2.1.]
+Preamble
+
+The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General
+Public Licenses are intended to guarantee your freedom to share
+and change free software--to make sure the software is free for
+all its users.
+
+This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of
+the Free Software Foundation and other authors who decide to use
+it. You can use it too, but we suggest you first think carefully
+about whether this license or the ordinary General Public License
+is the better strategy to use in any particular case, based on
+the explanations below.
+
+When we speak of free software, we are referring to freedom of
+use, 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 and use pieces of it in new free programs; and that you
+are informed that you can do these things.
+
+To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender
+these rights. These restrictions translate to certain
+responsibilities for you if you distribute copies of the library
+or if you modify it.
+
+For example, if you distribute copies of the library, whether
+gratis or for a fee, you must give the recipients all the rights
+that we gave you. You must make sure that they, too, receive or
+can get the source code. If you link other code with the library,
+you must provide complete object files to the recipients, so that
+they can relink them with the library after making changes to the
+library and recompiling it. And you must show them these terms so
+they know their rights.
+
+We protect your rights with a two-step method: (1) we copyright
+the library, and (2) we offer you this license, which gives you
+legal permission to copy, distribute and/or modify the library.
+
+To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library
+is modified by someone else and passed on, the recipients should
+know that what they have is not the original version, so that the
+original author's reputation will not be affected by problems
+that might be introduced by others.
+
+Finally, software patents pose a constant threat to the existence
+of any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist
+that any patent license obtained for a version of the library must
+be consistent with the full freedom of use specified in this license.
+
+Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries,
+and is quite different from the ordinary General Public License.
+We use this license for certain libraries in order to permit
+linking those libraries into non-free programs.
+
+When a program is linked with a library, whether statically or
+using a shared library, the combination of the two is legally
+speaking a combined work, a derivative of the original library.
+The ordinary General Public License therefore permits such linking
+only if the entire combination fits its criteria of freedom. The
+Lesser General Public License permits more lax criteria for linking
+other code with the library.
+
+We call this license the "Lesser" General Public License because
+it does Less to protect the user's freedom than the ordinary
+General Public License. It also provides other free software
+developers Less of an advantage over competing non-free programs.
+These disadvantages are the reason we use the ordinary General
+Public License for many libraries. However, the Lesser license
+provides advantages in certain special circumstances.
+
+For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that
+it becomes a de-facto standard. To achieve this, non-free programs
+must be allowed to use the library. A more frequent case is that
+a free library does the same job as widely used non-free libraries.
+In this case, there is little to gain by limiting the free library
+to free software only, so we use the Lesser General Public License.
+
+In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body
+of free software. For example, permission to use the GNU C Library
+in non-free programs enables many more people to use the whole
+GNU operating system, as well as its variant, the GNU/Linux
+operating system.
+
+Although the Lesser General Public License is Less protective
+of the users' freedom, it does ensure that the user of a program
+that is linked with the Library has the freedom and the wherewithal
+to run that program using a modified version of the Library.
+
+The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between
+a "work based on the library" and a "work that uses the library".
+The former contains code derived from the library, whereas the
+latter must be combined with the library in order to run.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License Agreement applies to any software library or
+other program which contains a notice placed by the copyright
+holder or other authorized party saying it may be distributed
+under the terms of this Lesser General Public License (also
+called "this License"). Each licensee is addressed as "you".
+
+A "library" means a collection of software functions and/or
+data prepared so as to be conveniently linked with application
+programs (which use some of those functions and data) to form
+executables.
+
+The "Library", below, refers to any such software library or
+work which has been distributed under these terms. A "work based
+on the Library" means either the Library or any derivative work
+under copyright law: that is to say, a work containing the Library
+or a portion of it, either verbatim or with modifications and/or
+translated straightforwardly into another language. (Hereinafter,
+translation is included without limitation in the term
+"modification".)
+
+"Source code" for a work means the preferred form of the work
+for making modifications to it. For a library, 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 library.
+
+Activities other than copying, distribution and modification
+are not covered by this License; they are outside its scope.
+The act of running a program using the Library is not restricted,
+and output from such a program is covered only if its contents
+constitute a work based on the Library (independent of the use
+of the Library in a tool for writing it). Whether that is true
+depends on what the Library does and what the program that uses
+the Library does.
+
+1. You may copy and distribute verbatim copies of the Library's
+complete 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 distribute a copy of this License
+along with the Library.
+
+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 Library or any
+portion of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
+ * b) You must cause the files modified to carry prominent
+ notices stating that you changed the files and the date of
+ any change.
+ * c) You must cause the whole of the work to be licensed at
+ no charge to all third parties under the terms of this License.
+ * d) If a facility in the modified Library refers to a
+ function or a table of data to be supplied by an application
+ program that uses the facility, other than as an argument
+ passed when the facility is invoked, then you must make a
+ good faith effort to ensure that, in the event an application
+ does not supply such function or table, the facility still
+ operates, and performs whatever part of its purpose remains
+ meaningful.
+
+ (For example, a function in a library to compute square
+ roots has a purpose that is entirely well-defined independent
+ of the application. Therefore, Subsection 2d requires that
+ any application-supplied function or table used by this
+ function must be optional: if the application does not supply
+ it, the square root function must still compute square roots.)
+
+ These requirements apply to the modified work as a whole.
+ If identifiable sections of that work are not derived from
+ the Library, 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
+ Library, 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
+ Library.
+
+ In addition, mere aggregation of another work not based
+ on the Library with the Library (or with a work based on
+ the Library) on a volume of a storage or distribution medium
+ does not bring the other work under the scope of this License.
+
+3. You may opt to apply the terms of the ordinary GNU General
+Public License instead of this License to a given copy of the
+Library. To do this, you must alter all the notices that refer
+to this License, so that they refer to the ordinary GNU General
+Public License, version 2, instead of to this License. (If a
+newer version than version 2 of the ordinary GNU General Public
+License has appeared, then you can specify that version instead
+if you wish.) Do not make any other change in these notices.
+
+Once this change is made in a given copy, it is irreversible
+for that copy, so the ordinary GNU General Public License applies
+to all subsequent copies and derivative works made from that copy.
+
+This option is useful when you wish to copy part of the code
+of the Library into a program that is not a library.
+
+4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable
+form under the terms of Sections 1 and 2 above provided that
+you 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.
+
+If distribution of 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 satisfies the
+requirement to distribute the source code, even though third
+parties are not compelled to copy the source along with the
+object code.
+
+5. A program that contains no derivative of any portion of
+the Library, but is designed to work with the Library by being
+compiled or linked with it, is called a "work that uses the
+Library". Such a work, in isolation, is not a derivative work
+of the Library, and therefore falls outside the scope of this
+License.
+
+However, linking a "work that uses the Library" with the
+Library creates an executable that is a derivative of the
+Library (because it contains portions of the Library), rather
+than a "work that uses the library". The executable is therefore
+covered by this License. Section 6 states terms for distribution
+of such executables.
+
+When a "work that uses the Library" uses material from a
+header file that is part of the Library, the object code
+for the work may be a derivative work of the Library even
+though the source code is not. Whether this is true is especially
+significant if the work can be linked without the Library,
+or if the work is itself a library. The threshold for this to
+be true is not precisely defined by law.
+
+If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small
+inline functions (ten lines or less in length), then the use
+of the object file is unrestricted, regardless of whether it
+is legally a derivative work. (Executables containing this
+object code plus portions of the Library will still fall
+under Section 6.)
+
+Otherwise, if the work is a derivative of the Library, you
+may distribute the object code for the work under the terms
+of Section 6. Any executables containing that work also fall
+under Section 6, whether or not they are linked directly with
+the Library itself.
+
+6. As an exception to the Sections above, you may also combine
+or link a "work that uses the Library" with the Library to
+produce a work containing portions of the Library, and distribute
+that work under terms of your choice, provided that the terms
+permit modification of the work for the customer's own use and
+reverse engineering for debugging such modifications.
+
+You must give prominent notice with each copy of the work that
+the Library is used in it and that the Library and its use are
+covered by this License. You must supply a copy of this License.
+If the work during execution displays copyright notices, you must
+include the copyright notice for the Library among them, as well
+as a reference directing the user to the copy of this License.
+Also, you must do one of these things:
+
+ * a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including
+ whatever changes were used in the work (which must be
+ distributed under Sections 1 and 2 above); and, if the
+ work is an executable linked with the Library, with the
+ complete machine-readable "work that uses the Library", as
+ object code and/or source code, so that the user can modify
+ the Library and then relink to produce a modified executable
+ containing the modified Library. (It is understood that the
+ user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+ * b) Use a suitable shared library mechanism for linking
+ with the Library. A suitable mechanism is one that (1) uses
+ at run time a copy of the library already present on the
+ user's computer system, rather than copying library functions
+ into the executable, and (2) will operate properly with a
+ modified version of the library, if the user installs one,
+ as long as the modified version is interface-compatible with
+ the version that the work was made with.
+ * c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more than
+ the cost of performing this distribution.
+ * d) If distribution of the work is made by offering access
+ to copy from a designated place, offer equivalent access to
+ copy the above specified materials from the same place.
+ * e) Verify that the user has already received a copy of
+ these materials or that you have already sent this user a
+ copy.
+
+For an executable, the required form of the "work that uses
+the Library" must include any data and utility programs needed
+for reproducing the executable from it. However, as a special
+exception, the materials to be 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.
+
+It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means
+you cannot use both them and the Library together in an executable
+that you distribute.
+
+7. You may place library facilities that are a work based on
+the Library side-by-side in a single library together with other
+library facilities not covered by this License, and distribute
+such a combined library, provided that the separate distribution
+of the work based on the Library and of the other library
+facilities is otherwise permitted, and provided that you do
+these two things:
+
+ * a) Accompany the combined library with a copy of the
+ same work based on the Library, uncombined with any other
+ library facilities. This must be distributed under the terms
+ of the Sections above.
+ * b) Give prominent notice with the combined library of the
+ fact that part of it is a work based on the Library, and
+ explaining where to find the accompanying uncombined form
+ of the same work.
+
+8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.
+Any attempt otherwise to copy, modify, sublicense, link with,
+or distribute the Library 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.
+
+9. 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 Library or its derivative works. These
+actions are prohibited by law if you do not accept this License.
+Therefore, by modifying or distributing the Library (or any work
+based on the Library), you indicate your acceptance of this
+License to do so, and all its terms and conditions for copying,
+distributing or modifying the Library or works based on it.
+
+10. Each time you redistribute the Library (or any work based
+on the Library), the recipient automatically receives a license
+from the original licensor to copy, distribute, link with or
+modify the Library 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 with this License.
+
+11. 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 Library at all. For example, if a patent license
+would not permit royalty-free redistribution of the Library 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 Library.
+
+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.
+
+12. If the distribution and/or use of the Library is restricted
+in certain countries either by patents or by copyrighted interfaces,
+the original copyright holder who places the Library 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.
+
+13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser 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
+Library 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 Library
+does not specify a license version number, you may choose any
+version ever published by the Free Software Foundation.
+
+14. If you wish to incorporate parts of the Library into other
+free programs whose distribution conditions are incompatible with
+these, 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
+
+15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE
+LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS
+AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE,
+YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+16. 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 LIBRARY 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
+LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN
+IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGES.
+
+License Terms of Third Party Libraries
+NanoXML
+
+NanoXML is distributed under the zlib/libpng license, which is
+OSS (Open Source Software) compliant. It is not GPL or LGPL and
+it will never be part of the GNU project.
+
+Copyrighted �2000-2002 Marc De Scheemaecker, All Rights Reserved
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute
+it freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you
+ must not claim that you wrote the original software. If you use
+ this software in a product, an acknowledgment in the product
+ documentation would be appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and
+ must not be misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source
+ distribution.
+
+Base64
+
+Copyright � Robert Harder. All Rights reserved.
+
+This software is in the public domain. Permission to use, copy,
+modify, and distribute this software and its documentation for
+any purpose and without fee is hereby granted, without any conditions
+or restrictions. This software is provided "as is" without express
+or implied warranty.
+JBrowser
+
+Copyright � Steve Roy. All Rights reserved.
+
+You can do whatever you like with it. As usual with this kind of
+stuff, if you have success with it, great, if you manage to create
+a catastrophy with it, such as render your computer useless or
+crash the entire Internet, too bad, although rather unlikely.
+Swing-Layout
+
+Copyright (C) 2005 Sun Microsystems, Inc. All rights reserved.
+Use is subject to license terms.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the Lesser GNU General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+
+Copyright
+Quaqua Look And Feel � 2003-2006
+
+Werner Randelshofer, Staldenmattweg 2, Immensee, CH-6405, Switzerland
+http://www.randelshofer.ch/
+werner.randelshofer at bluewin.ch
+All Rights Reserved.
+Contributors
+
+ *
+
+ Christopher Atlan
+ * Steve Roy
+
+Third Party Libraries
+
+ * NanoXML � 2000-2002
+ Marc De Scheemaecker
+ All Rights Reserved.
+ * Base64 �
+ Robert Harder
+ * Swing-Layout
+ Sun Microsystems
+ https://swing-layout.dev.java.net/
+ All Rights Reserved.
+
+Artwork
+
+The artwork used by the Quaqua Look and Feel is copyright
+Apple Computer Inc. Use of the artwork is only licensed for
+Apple hardware running an Apple operating system.
\ No newline at end of file
diff --git a/substance/src/main/resources/Substance.license b/substance/src/main/resources/Substance.license
new file mode 100644
index 0000000..5e05324
--- /dev/null
+++ b/substance/src/main/resources/Substance.license
@@ -0,0 +1,33 @@
+Copyright (c) 2005-2010, Kirill Grouchnikov and contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+ * Neither the name of the Kirill Grouchnikov and contributors nor
+the names of its contributors may be used to endorse or promote products
+derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
+
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+The original artwork used in the Quaqua color chooser implementation
+has been replaced by images from Famfam Silk collection available
+under Creative Commons Attribution-ShareAlike 2.5 license and
+images created dynamically by the Substance core code.
diff --git a/substance/src/main/resources/XUI.license b/substance/src/main/resources/XUI.license
new file mode 100644
index 0000000..7714141
--- /dev/null
+++ b/substance/src/main/resources/XUI.license
@@ -0,0 +1,470 @@
+ MOZILLA PUBLIC LICENSE
+ Version 1.1
+
+ ---------------
+
+1. Definitions.
+
+ 1.0.1. "Commercial Use" means distribution or otherwise making the
+ Covered Code available to a third party.
+
+ 1.1. "Contributor" means each entity that creates or contributes to
+ the creation of Modifications.
+
+ 1.2. "Contributor Version" means the combination of the Original
+ Code, prior Modifications used by a Contributor, and the Modifications
+ made by that particular Contributor.
+
+ 1.3. "Covered Code" means the Original Code or Modifications or the
+ combination of the Original Code and Modifications, in each case
+ including portions thereof.
+
+ 1.4. "Electronic Distribution Mechanism" means a mechanism generally
+ accepted in the software development community for the electronic
+ transfer of data.
+
+ 1.5. "Executable" means Covered Code in any form other than Source
+ Code.
+
+ 1.6. "Initial Developer" means the individual or entity identified
+ as the Initial Developer in the Source Code notice required by Exhibit
+ A.
+
+ 1.7. "Larger Work" means a work which combines Covered Code or
+ portions thereof with code not governed by the terms of this License.
+
+ 1.8. "License" means this document.
+
+ 1.8.1. "Licensable" means having the right to grant, to the maximum
+ extent possible, whether at the time of the initial grant or
+ subsequently acquired, any and all of the rights conveyed herein.
+
+ 1.9. "Modifications" means any addition to or deletion from the
+ substance or structure of either the Original Code or any previous
+ Modifications. When Covered Code is released as a series of files, a
+ Modification is:
+ A. Any addition to or deletion from the contents of a file
+ containing Original Code or previous Modifications.
+
+ B. Any new file that contains any part of the Original Code or
+ previous Modifications.
+
+ 1.10. "Original Code" means Source Code of computer software code
+ which is described in the Source Code notice required by Exhibit A as
+ Original Code, and which, at the time of its release under this
+ License is not already Covered Code governed by this License.
+
+ 1.10.1. "Patent Claims" means any patent claim(s), now owned or
+ hereafter acquired, including without limitation, method, process,
+ and apparatus claims, in any patent Licensable by grantor.
+
+ 1.11. "Source Code" means the preferred form of the Covered Code for
+ making modifications to it, including all modules it contains, plus
+ any associated interface definition files, scripts used to control
+ compilation and installation of an Executable, or source code
+ differential comparisons against either the Original Code or another
+ well known, available Covered Code of the Contributor's choice. The
+ Source Code can be in a compressed or archival form, provided the
+ appropriate decompression or de-archiving software is widely available
+ for no charge.
+
+ 1.12. "You" (or "Your") means an individual or a legal entity
+ exercising rights under, and complying with all of the terms of, this
+ License or a future version of this License issued under Section 6.1.
+ For legal entities, "You" includes any entity which controls, is
+ controlled by, or is under common control with You. For purposes of
+ this definition, "control" means (a) the power, direct or indirect,
+ to cause the direction or management of such entity, whether by
+ contract or otherwise, or (b) ownership of more than fifty percent
+ (50%) of the outstanding shares or beneficial ownership of such
+ entity.
+
+2. Source Code License.
+
+ 2.1. The Initial Developer Grant.
+ The Initial Developer hereby grants You a world-wide, royalty-free,
+ non-exclusive license, subject to third party intellectual property
+ claims:
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Initial Developer to use, reproduce,
+ modify, display, perform, sublicense and distribute the Original
+ Code (or portions thereof) with or without Modifications, and/or
+ as part of a Larger Work; and
+
+ (b) under Patents Claims infringed by the making, using or
+ selling of Original Code, to make, have made, use, practice,
+ sell, and offer for sale, and/or otherwise dispose of the
+ Original Code (or portions thereof).
+
+ (c) the licenses granted in this Section 2.1(a) and (b) are
+ effective on the date Initial Developer first distributes
+ Original Code under the terms of this License.
+
+ (d) Notwithstanding Section 2.1(b) above, no patent license is
+ granted: 1) for code that You delete from the Original Code; 2)
+ separate from the Original Code; or 3) for infringements caused
+ by: i) the modification of the Original Code or ii) the
+ combination of the Original Code with other software or devices.
+
+ 2.2. Contributor Grant.
+ Subject to third party intellectual property claims, each Contributor
+ hereby grants You a world-wide, royalty-free, non-exclusive license
+
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Contributor, to use, reproduce, modify,
+ display, perform, sublicense and distribute the Modifications
+ created by such Contributor (or portions thereof) either on an
+ unmodified basis, with other Modifications, as Covered Code
+ and/or as part of a Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using, or
+ selling of Modifications made by that Contributor either alone
+ and/or in combination with its Contributor Version (or portions
+ of such combination), to make, use, sell, offer for sale, have
+ made, and/or otherwise dispose of: 1) Modifications made by that
+ Contributor (or portions thereof); and 2) the combination of
+ Modifications made by that Contributor with its Contributor
+ Version (or portions of such combination).
+
+ (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
+ effective on the date Contributor first makes Commercial Use of
+ the Covered Code.
+
+ (d) Notwithstanding Section 2.2(b) above, no patent license is
+ granted: 1) for any code that Contributor has deleted from the
+ Contributor Version; 2) separate from the Contributor Version;
+ 3) for infringements caused by: i) third party modifications of
+ Contributor Version or ii) the combination of Modifications made
+ by that Contributor with other software (except as part of the
+ Contributor Version) or other devices; or 4) under Patent Claims
+ infringed by Covered Code in the absence of Modifications made by
+ that Contributor.
+
+3. Distribution Obligations.
+
+ 3.1. Application of License.
+ The Modifications which You create or to which You contribute are
+ governed by the terms of this License, including without limitation
+ Section 2.2. The Source Code version of Covered Code may be
+ distributed only under the terms of this License or a future version
+ of this License released under Section 6.1, and You must include a
+ copy of this License with every copy of the Source Code You
+ distribute. You may not offer or impose any terms on any Source Code
+ version that alters or restricts the applicable version of this
+ License or the recipients' rights hereunder. However, You may include
+ an additional document offering the additional rights described in
+ Section 3.5.
+
+ 3.2. Availability of Source Code.
+ Any Modification which You create or to which You contribute must be
+ made available in Source Code form under the terms of this License
+ either on the same media as an Executable version or via an accepted
+ Electronic Distribution Mechanism to anyone to whom you made an
+ Executable version available; and if made available via Electronic
+ Distribution Mechanism, must remain available for at least twelve (12)
+ months after the date it initially became available, or at least six
+ (6) months after a subsequent version of that particular Modification
+ has been made available to such recipients. You are responsible for
+ ensuring that the Source Code version remains available even if the
+ Electronic Distribution Mechanism is maintained by a third party.
+
+ 3.3. Description of Modifications.
+ You must cause all Covered Code to which You contribute to contain a
+ file documenting the changes You made to create that Covered Code and
+ the date of any change. You must include a prominent statement that
+ the Modification is derived, directly or indirectly, from Original
+ Code provided by the Initial Developer and including the name of the
+ Initial Developer in (a) the Source Code, and (b) in any notice in an
+ Executable version or related documentation in which You describe the
+ origin or ownership of the Covered Code.
+
+ 3.4. Intellectual Property Matters
+ (a) Third Party Claims.
+ If Contributor has knowledge that a license under a third party's
+ intellectual property rights is required to exercise the rights
+ granted by such Contributor under Sections 2.1 or 2.2,
+ Contributor must include a text file with the Source Code
+ distribution titled "LEGAL" which describes the claim and the
+ party making the claim in sufficient detail that a recipient will
+ know whom to contact. If Contributor obtains such knowledge after
+ the Modification is made available as described in Section 3.2,
+ Contributor shall promptly modify the LEGAL file in all copies
+ Contributor makes available thereafter and shall take other steps
+ (such as notifying appropriate mailing lists or newsgroups)
+ reasonably calculated to inform those who received the Covered
+ Code that new knowledge has been obtained.
+
+ (b) Contributor APIs.
+ If Contributor's Modifications include an application programming
+ interface and Contributor has knowledge of patent licenses which
+ are reasonably necessary to implement that API, Contributor must
+ also include this information in the LEGAL file.
+
+ (c) Representations.
+ Contributor represents that, except as disclosed pursuant to
+ Section 3.4(a) above, Contributor believes that Contributor's
+ Modifications are Contributor's original creation(s) and/or
+ Contributor has sufficient rights to grant the rights conveyed by
+ this License.
+
+ 3.5. Required Notices.
+ You must duplicate the notice in Exhibit A in each file of the Source
+ Code. If it is not possible to put such notice in a particular Source
+ Code file due to its structure, then You must include such notice in a
+ location (such as a relevant directory) where a user would be likely
+ to look for such a notice. If You created one or more Modification(s)
+ You may add your name as a Contributor to the notice described in
+ Exhibit A. You must also duplicate this License in any documentation
+ for the Source Code where You describe recipients' rights or ownership
+ rights relating to Covered Code. You may choose to offer, and to
+ charge a fee for, warranty, support, indemnity or liability
+ obligations to one or more recipients of Covered Code. However, You
+ may do so only on Your own behalf, and not on behalf of the Initial
+ Developer or any Contributor. You must make it absolutely clear than
+ any such warranty, support, indemnity or liability obligation is
+ offered by You alone, and You hereby agree to indemnify the Initial
+ Developer and every Contributor for any liability incurred by the
+ Initial Developer or such Contributor as a result of warranty,
+ support, indemnity or liability terms You offer.
+
+ 3.6. Distribution of Executable Versions.
+ You may distribute Covered Code in Executable form only if the
+ requirements of Section 3.1-3.5 have been met for that Covered Code,
+ and if You include a notice stating that the Source Code version of
+ the Covered Code is available under the terms of this License,
+ including a description of how and where You have fulfilled the
+ obligations of Section 3.2. The notice must be conspicuously included
+ in any notice in an Executable version, related documentation or
+ collateral in which You describe recipients' rights relating to the
+ Covered Code. You may distribute the Executable version of Covered
+ Code or ownership rights under a license of Your choice, which may
+ contain terms different from this License, provided that You are in
+ compliance with the terms of this License and that the license for the
+ Executable version does not attempt to limit or alter the recipient's
+ rights in the Source Code version from the rights set forth in this
+ License. If You distribute the Executable version under a different
+ license You must make it absolutely clear that any terms which differ
+ from this License are offered by You alone, not by the Initial
+ Developer or any Contributor. You hereby agree to indemnify the
+ Initial Developer and every Contributor for any liability incurred by
+ the Initial Developer or such Contributor as a result of any such
+ terms You offer.
+
+ 3.7. Larger Works.
+ You may create a Larger Work by combining Covered Code with other code
+ not governed by the terms of this License and distribute the Larger
+ Work as a single product. In such a case, You must make sure the
+ requirements of this License are fulfilled for the Covered Code.
+
+4. Inability to Comply Due to Statute or Regulation.
+
+ If it is impossible for You to comply with any of the terms of this
+ License with respect to some or all of the Covered Code due to
+ statute, judicial order, or regulation then You must: (a) comply with
+ the terms of this License to the maximum extent possible; and (b)
+ describe the limitations and the code they affect. Such description
+ must be included in the LEGAL file described in Section 3.4 and must
+ be included with all distributions of the Source Code. Except to the
+ extent prohibited by statute or regulation, such description must be
+ sufficiently detailed for a recipient of ordinary skill to be able to
+ understand it.
+
+5. Application of this License.
+
+ This License applies to code to which the Initial Developer has
+ attached the notice in Exhibit A and to related Covered Code.
+
+6. Versions of the License.
+
+ 6.1. New Versions.
+ Netscape Communications Corporation ("Netscape") may publish revised
+ and/or new versions of the License from time to time. Each version
+ will be given a distinguishing version number.
+
+ 6.2. Effect of New Versions.
+ Once Covered Code has been published under a particular version of the
+ License, You may always continue to use it under the terms of that
+ version. You may also choose to use such Covered Code under the terms
+ of any subsequent version of the License published by Netscape. No one
+ other than Netscape has the right to modify the terms applicable to
+ Covered Code created under this License.
+
+ 6.3. Derivative Works.
+ If You create or use a modified version of this License (which you may
+ only do in order to apply it to code which is not already Covered Code
+ governed by this License), You must (a) rename Your license so that
+ the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
+ "MPL", "NPL" or any confusingly similar phrase do not appear in your
+ license (except to note that your license differs from this License)
+ and (b) otherwise make it clear that Your version of the license
+ contains terms which differ from the Mozilla Public License and
+ Netscape Public License. (Filling in the name of the Initial
+ Developer, Original Code or Contributor in the notice described in
+ Exhibit A shall not of themselves be deemed to be modifications of
+ this License.)
+
+7. DISCLAIMER OF WARRANTY.
+
+ COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+ WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
+ DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+ THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
+ IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
+ YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
+ COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
+ OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+ ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+8. TERMINATION.
+
+ 8.1. This License and the rights granted hereunder will terminate
+ automatically if You fail to comply with terms herein and fail to cure
+ such breach within 30 days of becoming aware of the breach. All
+ sublicenses to the Covered Code which are properly granted shall
+ survive any termination of this License. Provisions which, by their
+ nature, must remain in effect beyond the termination of this License
+ shall survive.
+
+ 8.2. If You initiate litigation by asserting a patent infringement
+ claim (excluding declatory judgment actions) against Initial Developer
+ or a Contributor (the Initial Developer or Contributor against whom
+ You file such action is referred to as "Participant") alleging that:
+
+ (a) such Participant's Contributor Version directly or indirectly
+ infringes any patent, then any and all rights granted by such
+ Participant to You under Sections 2.1 and/or 2.2 of this License
+ shall, upon 60 days notice from Participant terminate prospectively,
+ unless if within 60 days after receipt of notice You either: (i)
+ agree in writing to pay Participant a mutually agreeable reasonable
+ royalty for Your past and future use of Modifications made by such
+ Participant, or (ii) withdraw Your litigation claim with respect to
+ the Contributor Version against such Participant. If within 60 days
+ of notice, a reasonable royalty and payment arrangement are not
+ mutually agreed upon in writing by the parties or the litigation claim
+ is not withdrawn, the rights granted by Participant to You under
+ Sections 2.1 and/or 2.2 automatically terminate at the expiration of
+ the 60 day notice period specified above.
+
+ (b) any software, hardware, or device, other than such Participant's
+ Contributor Version, directly or indirectly infringes any patent, then
+ any rights granted to You by such Participant under Sections 2.1(b)
+ and 2.2(b) are revoked effective as of the date You first made, used,
+ sold, distributed, or had made, Modifications made by that
+ Participant.
+
+ 8.3. If You assert a patent infringement claim against Participant
+ alleging that such Participant's Contributor Version directly or
+ indirectly infringes any patent where such claim is resolved (such as
+ by license or settlement) prior to the initiation of patent
+ infringement litigation, then the reasonable value of the licenses
+ granted by such Participant under Sections 2.1 or 2.2 shall be taken
+ into account in determining the amount or value of any payment or
+ license.
+
+ 8.4. In the event of termination under Sections 8.1 or 8.2 above,
+ all end user license agreements (excluding distributors and resellers)
+ which have been validly granted by You or any distributor hereunder
+ prior to termination shall survive termination.
+
+9. LIMITATION OF LIABILITY.
+
+ UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+ (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+ DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
+ OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
+ ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
+ CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
+ WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+ COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+ INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+ LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+ RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+ PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+ EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
+ THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+10. U.S. GOVERNMENT END USERS.
+
+ The Covered Code is a "commercial item," as that term is defined in
+ 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+ software" and "commercial computer software documentation," as such
+ terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
+ C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+ all U.S. Government End Users acquire Covered Code with only those
+ rights set forth herein.
+
+11. MISCELLANEOUS.
+
+ This License represents the complete agreement concerning subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. This License shall be governed by
+ California law provisions (except to the extent applicable law, if
+ any, provides otherwise), excluding its conflict-of-law provisions.
+ With respect to disputes in which at least one party is a citizen of,
+ or an entity chartered or registered to do business in the United
+ States of America, any litigation relating to this License shall be
+ subject to the jurisdiction of the Federal Courts of the Northern
+ District of California, with venue lying in Santa Clara County,
+ California, with the losing party responsible for costs, including
+ without limitation, court costs and reasonable attorneys' fees and
+ expenses. The application of the United Nations Convention on
+ Contracts for the International Sale of Goods is expressly excluded.
+ Any law or regulation which provides that the language of a contract
+ shall be construed against the drafter shall not apply to this
+ License.
+
+12. RESPONSIBILITY FOR CLAIMS.
+
+ As between Initial Developer and the Contributors, each party is
+ responsible for claims and damages arising, directly or indirectly,
+ out of its utilization of rights under this License and You agree to
+ work with Initial Developer and Contributors to distribute such
+ responsibility on an equitable basis. Nothing herein is intended or
+ shall be deemed to constitute any admission of liability.
+
+13. MULTIPLE-LICENSED CODE.
+
+ Initial Developer may designate portions of the Covered Code as
+ "Multiple-Licensed". "Multiple-Licensed" means that the Initial
+ Developer permits you to utilize portions of the Covered Code under
+ Your choice of the NPL or the alternative licenses, if any, specified
+ by the Initial Developer in the file described in Exhibit A.
+
+EXHIBIT A -Mozilla Public License.
+
+ ``The contents of this file are subject to the Mozilla Public License
+ Version 1.1 (the "License"); you may not use this file except in
+ compliance with the License. You may obtain a copy of the License at
+ http://www.mozilla.org/MPL/
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ License for the specific language governing rights and limitations
+ under the License.
+
+ The Original Code is ______________________________________.
+
+ The Initial Developer of the Original Code is ________________________.
+ Portions created by ______________________ are Copyright (C) ______
+ _______________________. All Rights Reserved.
+
+ Contributor(s): ______________________________________.
+
+ Alternatively, the contents of this file may be used under the terms
+ of the _____ license (the "[___] License"), in which case the
+ provisions of [______] License are applicable instead of those
+ above. If you wish to allow use of your version of this file only
+ under the terms of the [____] License and not to allow others to use
+ your version of this file under the MPL, indicate your decision by
+ deleting the provisions above and replace them with the notice and
+ other provisions required by the [___] License. If you do not delete
+ the provisions above, a recipient may use your version of this file
+ under either the MPL or the [___] License."
+
+ [NOTE: The text of this Exhibit A may differ slightly from the text of
+ the notices in the Source Code files of the Original Code. You should
+ use the text of this Exhibit A rather than the text found in the
+ Original Code Source Code for Your Modifications.]
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/api/skin/autumn.colorschemes b/substance/src/main/resources/org/pushingpixels/substance/api/skin/autumn.colorschemes
new file mode 100644
index 0000000..e079893
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/api/skin/autumn.colorschemes
@@ -0,0 +1,33 @@
+Autumn Active {
+ kind=Light
+ colorUltraLight=#FDDDAC
+ colorExtraLight=#FCEF9F
+ colorLight=#FCD592
+ colorMid=#F9BE84
+ colorDark=#F8B87A
+ colorUltraDark=#AC623B
+ colorForeground=#AC623B
+}
+
+Autumn Enabled {
+ kind=Light
+ colorUltraLight=#FFF2DF
+ colorExtraLight=#FFE3C5
+ colorLight=#FDD1A4
+ colorMid=#FBCD9C
+ colorDark=#FCC896
+ colorUltraDark=#AC623B
+ colorForeground=#AC623B
+}
+
+Autumn Watermark {
+ kind=Light
+ colorUltraLight=#FFF2DF
+ colorExtraLight=#FFE3C5
+ colorLight=#FEDAB5
+ colorMid=#FED8B2
+ colorDark=#FDD4AA
+ colorUltraDark=#FDD1A5
+ colorForeground=#AC623B
+}
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/api/skin/coffee-active.colorscheme b/substance/src/main/resources/org/pushingpixels/substance/api/skin/coffee-active.colorscheme
new file mode 100644
index 0000000..b6dbc2e
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/api/skin/coffee-active.colorscheme
@@ -0,0 +1,9 @@
+name=Coffee Active
+kind=Light
+colorUltraLight=#F4E5C0
+colorExtraLight=#F0DEB3
+colorLight=#EBD7A6
+colorMid=#C7A67B
+colorDark=#AA8363
+colorUltraDark=#946F4A
+colorForeground=#32220F
diff --git a/substance/src/main/resources/org/pushingpixels/substance/api/skin/dust.colorschemes b/substance/src/main/resources/org/pushingpixels/substance/api/skin/dust.colorschemes
new file mode 100644
index 0000000..05c76e6
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/api/skin/dust.colorschemes
@@ -0,0 +1,131 @@
+Dust Active {
+ kind=Light
+ colorUltraLight=#B4ADA1
+ colorExtraLight=#B4ADA1
+ colorLight=#AEA79A
+ colorMid=#A8A094
+ colorDark=#A29B8E
+ colorUltraDark=#9E978A
+ colorForeground=#222629
+}
+
+Dust Border Active {
+ kind=Dark
+ colorUltraLight=#625B4E
+ colorExtraLight=#615A4D
+ colorLight=#5E574A
+ colorMid=#5E574A
+ colorDark=#5E574A
+ colorUltraDark=#5E574A
+ colorForeground=#B4ADA1
+}
+
+Dust Border Enabled {
+ kind=Light
+ colorUltraLight=#A29C8F
+ colorExtraLight=#8F887A
+ colorLight=#827A6C
+ colorMid=#756D5E
+ colorDark=#6F6859
+ colorUltraDark=#6F6858
+ colorForeground=#5E574A
+}
+
+Dust Header Border {
+ kind=Dark
+ colorUltraLight=#2D251E
+ colorExtraLight=#2D241E
+ colorLight=#2C241E
+ colorMid=#2C231D
+ colorDark=#271F1A
+ colorUltraDark=#271F1A
+ colorForeground=#C1C2AA
+}
+
+Dust Enabled {
+ kind=Light
+ colorUltraLight=#EEECE8
+ colorExtraLight=#EAE7E2
+ colorLight=#E8E5DF
+ colorMid=#DCD7CE
+ colorDark=#CFC9BC
+ colorUltraDark=#CDC7B9
+ colorForeground=#3B3639
+}
+
+Dust Header Active {
+ kind=Dark
+ colorUltraLight=#604539
+ colorExtraLight=#604539
+ colorLight=#5E4438
+ colorMid=#594235
+ colorDark=#513D30
+ colorUltraDark=#4F3C2F
+ colorForeground=#C1C2AA
+}
+
+Dust Header Enabled {
+ kind=Dark
+ colorUltraLight=#3C3B37
+ colorExtraLight=#3B3A36
+ colorLight=#393834
+ colorMid=#3B3A36
+ colorDark=#33322E
+ colorUltraDark=#2F2E2C
+ colorForeground=#C1C2C2
+}
+
+Dust Header Separator {
+ kind=Dark
+ colorUltraLight=#3F3D3A
+ colorExtraLight=#3F3E3C
+ colorLight=#3B3936
+ colorMid=#363532
+ colorDark=#2F2C2A
+ colorUltraDark=#2F2C2A
+ colorForeground=#21201D
+}
+
+Dust Header Watermark {
+ kind=Dark
+ colorUltraLight=#2C2B29
+ colorExtraLight=#2C2B29
+ colorLight=#2B2A28
+ colorMid=#292827
+ colorDark=#292827
+ colorUltraDark=#292827
+ colorForeground=#999999
+}
+
+Dust Coffee Enabled {
+ kind=Light
+ colorUltraLight=#E0D3B3
+ colorExtraLight=#DDD4B3
+ colorLight=#DCD7C1
+ colorMid=#D3CAA9
+ colorDark=#C9BF9B
+ colorUltraDark=#C5BB97
+ colorForeground=#433C32
+}
+
+Dust Coffee Watermark {
+ kind=Light
+ colorUltraLight=#E8D9B9
+ colorExtraLight=#E8D9B9
+ colorLight=#E8D9B9
+ colorMid=#E8D9B9
+ colorDark=#E8D9B9
+ colorUltraDark=#E8D9B9
+ colorForeground=#433C32
+}
+
+Dust Coffee Text Highlight {
+ kind=Light
+ colorUltraLight=#E2C39D
+ colorExtraLight=#DFBE95
+ colorLight=#DBBA8E
+ colorMid=#C09A73
+ colorDark=#AA8363
+ colorUltraDark=#9A7554
+ colorForeground=#32220F
+}
diff --git a/substance/src/main/resources/org/pushingpixels/substance/api/skin/gemini.colorschemes b/substance/src/main/resources/org/pushingpixels/substance/api/skin/gemini.colorschemes
new file mode 100644
index 0000000..1bbe9b9
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/api/skin/gemini.colorschemes
@@ -0,0 +1,121 @@
+Gemini Dark Blue Background {
+ kind=Light
+ colorUltraLight=#3B4859
+ colorExtraLight=#3A475A
+ colorLight=#324158
+ colorMid=#2E3A50
+ colorDark=#2C384E
+ colorUltraDark=#28324B
+ colorForeground=#CAD9D2
+}
+
+Gemini White Background {
+ kind=Light
+ colorUltraLight=#EBF1F1
+ colorExtraLight=#D2E0E0
+ colorLight=#D3DEDE
+ colorMid=#CFDEDE
+ colorDark=#CCD8D8
+ colorUltraDark=#C9D6D6
+ colorForeground=#7B7B79
+}
+
+Gemini Dark Blue {
+ kind=Dark
+ colorUltraLight=#424C67
+ colorExtraLight=#38445C
+ colorLight=#2D3D4D
+ colorMid=#212E41
+ colorDark=#1B2435
+ colorUltraDark=#121B22
+ colorForeground=#B3C3DC
+}
+
+Gemini Black {
+ kind=Dark
+ colorUltraLight=#17242C
+ colorExtraLight=#162125
+ colorLight=#121C1E
+ colorMid=#111B1D
+ colorDark=#0D1313
+ colorUltraDark=#0C100F
+ colorForeground=#C9C9CB
+}
+
+Gemini Gray {
+ kind=Light
+ colorUltraLight=#C7CFD1
+ colorExtraLight=#B6C1C2
+ colorLight=#B3BDB9
+ colorMid=#A6ADAC
+ colorDark=#606B69
+ colorUltraDark=#485250
+ colorForeground=#000000
+}
+
+Gemini Gray Border {
+ kind=Dark
+ colorUltraLight=#6E6D72
+ colorExtraLight=#6C6B70
+ colorLight=#6A696E
+ colorMid=#585D60
+ colorDark=#575C5F
+ colorUltraDark=#51595B
+ colorForeground=#F8FFFF
+}
+
+Gemini Light Gray {
+ kind=Light
+ colorUltraLight=#E7ECF0
+ colorExtraLight=#E3EAF0
+ colorLight=#DFE8EF
+ colorMid=#DBE4EB
+ colorDark=#C5CED7
+ colorUltraDark=#AEB5BD
+ colorForeground=#636B6E
+}
+
+Gemini Light Gray Border {
+ kind=Light
+ colorUltraLight=#939997
+ colorExtraLight=#8D928E
+ colorLight=#8B908C
+ colorMid=#858B87
+ colorDark=#828886
+ colorUltraDark=#7F8583
+ colorForeground=#050704
+}
+
+Gemini Light Gray Separator {
+ kind=Light
+ colorUltraLight=#F5F9F9
+ colorExtraLight=#E6F1F2
+ colorLight=#DCE8EA
+ colorMid=#B3C3BE
+ colorDark=#A6B7B4
+ colorUltraDark=#9CA8AB
+ colorForeground=#000000
+}
+
+Gemini Highlight {
+ kind=Light
+ colorUltraLight=#FFEC97
+ colorExtraLight=#FFE86F
+ colorLight=#FFE34F
+ colorMid=#FFC73C
+ colorDark=#F2A926
+ colorUltraDark=#E29E0B
+ colorForeground=#70430B
+}
+
+Gemini Highlight Border {
+ kind=Light
+ colorUltraLight=#FFE769
+ colorExtraLight=#FFE454
+ colorLight=#FFDF4C
+ colorMid=#FFD747
+ colorDark=#FCB326
+ colorUltraDark=#FDA607
+ colorForeground=#70430B
+}
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/api/skin/graphite.colorschemes b/substance/src/main/resources/org/pushingpixels/substance/api/skin/graphite.colorschemes
new file mode 100644
index 0000000..4a708a3
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/api/skin/graphite.colorschemes
@@ -0,0 +1,186 @@
+Graphite Enabled {
+ kind=Dark
+ colorUltraLight=#4A4A4A
+ colorExtraLight=#464646
+ colorLight=#424242
+ colorMid=#3F3F3F
+ colorDark=#353535
+ colorUltraDark=#313131
+ colorForeground=#B4B4B4
+}
+
+Graphite Border {
+ kind=Dark
+ colorUltraLight=#616161
+ colorExtraLight=#5A5A5A
+ colorLight=#4F4F4F
+ colorMid=#414141
+ colorDark=#323232
+ colorUltraDark=#2B2B2B
+ colorForeground=#EDEDED
+}
+
+Graphite Background {
+ kind=Dark
+ colorUltraLight=#5F5F5F
+ colorExtraLight=#595959
+ colorLight=#4F4F4F
+ colorMid=#424242
+ colorDark=#363636
+ colorUltraDark=#2F2F2F
+ colorForeground=#B4B4B4
+}
+
+Graphite Active {
+ kind=Dark
+ colorUltraLight=#777777
+ colorExtraLight=#6F6F6F
+ colorLight=#636363
+ colorMid=#535353
+ colorDark=#434343
+ colorUltraDark=#3B3B3B
+ colorForeground=#C8C8C8
+}
+
+Graphite Selected {
+ kind=Dark
+ colorUltraLight=#727272
+ colorExtraLight=#696969
+ colorLight=#606060
+ colorMid=#505050
+ colorDark=#404040
+ colorUltraDark=#383838
+ colorForeground=#C8C8C8
+}
+
+Graphite Selected Disabled {
+ kind=Dark
+ colorUltraLight=#777777
+ colorExtraLight=#6F6F6F
+ colorLight=#636363
+ colorMid=#535353
+ colorDark=#434343
+ colorUltraDark=#3B3B3B
+ colorForeground=#202020
+}
+
+Graphite Disabled {
+ kind=Dark
+ colorUltraLight=#646464
+ colorExtraLight=#5E5E5E
+ colorLight=#545454
+ colorMid=#474747
+ colorDark=#3A3A3A
+ colorUltraDark=#333333
+ colorForeground=#202020
+}
+
+Graphite Highlight {
+ kind=Light
+ colorUltraLight=#FBFCFF
+ colorExtraLight=#F2F6FB
+ colorLight=#CED7E0
+ colorMid=#BCC0C5
+ colorDark=#62666B
+ colorUltraDark=#363B3F
+ colorForeground=#1B2025
+}
+
+Graphite Tab Highlight {
+ kind=Light
+ colorUltraLight=#FBFCFF
+ colorExtraLight=#F2F6FB
+ colorLight=#CED7E0
+ colorMid=#BCC0C5
+ colorDark=#62666B
+ colorUltraDark=#363B3F
+ colorForeground=#EDEDED
+}
+
+Graphite Highlight Mark {
+ kind=Dark
+ colorUltraLight=#616161
+ colorExtraLight=#5A5A5A
+ colorLight=#4F4F4F
+ colorMid=#414141
+ colorDark=#323232
+ colorUltraDark=#2B2B2B
+ colorForeground=#2B2B2B
+}
+
+Graphite Text Highlight {
+ kind=Light
+ colorUltraLight=#BDBEC0
+ colorExtraLight=#B8BBBD
+ colorLight=#A4A9AE
+ colorMid=#9A9D9F
+ colorDark=#686B6D
+ colorUltraDark=#505355
+ colorForeground=#2B2F33
+}
+
+Graphite Separator {
+ kind=Dark
+ colorUltraLight=#565656
+ colorExtraLight=#525252
+ colorLight=#4F4F4F
+ colorMid=#383838
+ colorDark=#353535
+ colorUltraDark=#333333
+ colorForeground=#B4B4B4
+}
+
+Graphite Aqua {
+ kind=Light
+ colorUltraLight=#6383FF
+ colorExtraLight=#577EFF
+ colorLight=#4A6FFF
+ colorMid=#355FFB
+ colorDark=#1A3DE6
+ colorUltraDark=#153BE1
+ colorForeground=#FFFFFF
+}
+
+Raven Selected Mark {
+ kind=Dark
+ colorUltraLight=#000000
+ colorExtraLight=#000000
+ colorLight=#000000
+ colorMid=#000000
+ colorDark=#000000
+ colorUltraDark=#000000
+ colorForeground=#000000
+}
+
+Raven Highlight Mark {
+ kind=Dark
+ colorUltraLight=#1E1E1E
+ colorExtraLight=#1E1E1E
+ colorLight=#1E1E1E
+ colorMid=#1E1E1E
+ colorDark=#1E1E1E
+ colorUltraDark=#1E1E1E
+ colorForeground=#1E1E1E
+}
+
+Raven Disabled {
+ kind=Dark
+ colorUltraLight=#2B2B2B
+ colorExtraLight=#262626
+ colorLight=#1E1E1E
+ colorMid=#141414
+ colorDark=#0A0A0A
+ colorUltraDark=#050505
+ colorForeground=#505050
+}
+
+Raven Selected Disabled {
+ kind=Dark
+ colorUltraLight=#777777
+ colorExtraLight=#6F6F6F
+ colorLight=#636363
+ colorMid=#535353
+ colorDark=#434343
+ colorUltraDark=#3B3B3B
+ colorForeground=#C8C8C8
+}
diff --git a/substance/src/main/resources/org/pushingpixels/substance/api/skin/kitchen-sink.colorschemes b/substance/src/main/resources/org/pushingpixels/substance/api/skin/kitchen-sink.colorschemes
new file mode 100644
index 0000000..a5b2002
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/api/skin/kitchen-sink.colorschemes
@@ -0,0 +1,55 @@
+Coffee Active {
+ kind=Light
+ colorUltraLight=#F4E5C0
+ colorExtraLight=#F0DEB3
+ colorLight=#EBD7A6
+ colorMid=#C7A67B
+ colorDark=#AA8363
+ colorUltraDark=#946F4A
+ colorForeground=#32220F
+}
+
+LightGray General Watermark {
+ kind=Light
+ colorUltraLight=#E9EEF7
+ colorExtraLight=#E5E8ED
+ colorLight=#DADCDF
+ colorMid=#CACDD1
+ colorDark=#BCC4CC
+ colorUltraDark=#BAC1CA
+ colorForeground=#4C535C
+}
+
+Business Blue Steel Highlight {
+ kind=Light
+ colorUltraLight=#FDEAB9
+ colorExtraLight=#F9E3A1
+ colorLight=#F9E07D
+ colorMid=#F3C96D
+ colorDark=#AC6C4D
+ colorUltraDark=#8F5D4D
+ colorForeground=#1A1A1A
+}
+
+Business Highlight {
+ kind=Light
+ colorUltraLight=#FDEAB9
+ colorExtraLight=#F9E3A1
+ colorLight=#F9E07D
+ colorMid=#F3C96D
+ colorDark=#AC6C4D
+ colorUltraDark=#8F5D4D
+ colorForeground=#1A1A1A
+}
+
+Moderate Highlight {
+ kind=Light
+ colorUltraLight=#FDE5A7
+ colorExtraLight=#F8DC89
+ colorLight=#F7D85D
+ colorMid=#F0BB48
+ colorDark=#974720
+ colorUltraDark=#733520
+ colorForeground=#000000
+}
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/api/skin/lightgray-general-watermark.colorscheme b/substance/src/main/resources/org/pushingpixels/substance/api/skin/lightgray-general-watermark.colorscheme
new file mode 100644
index 0000000..a1c001f
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/api/skin/lightgray-general-watermark.colorscheme
@@ -0,0 +1,9 @@
+name=LightGray General Watermark
+kind=Light
+colorUltraLight=#E9EEF7
+colorExtraLight=#E5E8ED
+colorLight=#DADCDF
+colorMid=#CACDD1
+colorDark=#BCC4CC
+colorUltraDark=#BAC1CA
+colorForeground=#4C535C
diff --git a/substance/src/main/resources/org/pushingpixels/substance/api/skin/magellan.colorschemes b/substance/src/main/resources/org/pushingpixels/substance/api/skin/magellan.colorschemes
new file mode 100644
index 0000000..685ea05
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/api/skin/magellan.colorschemes
@@ -0,0 +1,242 @@
+Magellan Dark Blue Background {
+ kind=Dark
+ colorUltraLight=#063162
+ colorExtraLight=#063263
+ colorLight=#063263
+ colorMid=#063263
+ colorDark=#031E42
+ colorUltraDark=#031A3B
+ colorForeground=#DADADA
+}
+
+Magellan Medium Blue Background {
+ kind=Dark
+ colorUltraLight=#0F5AAF
+ colorExtraLight=#0F58AA
+ colorLight=#0D519E
+ colorMid=#0A478D
+ colorDark=#083D7A
+ colorUltraDark=#053163
+ colorForeground=#EBEBEB
+}
+
+Magellan Light Blue Background {
+ kind=Light
+ colorUltraLight=#0C5AB0
+ colorExtraLight=#0C5AB0
+ colorLight=#0C5AB0
+ colorMid=#0C5AB1
+ colorDark=#0C5AB0
+ colorUltraDark=#0C5AB1
+ colorForeground=#021C3A
+}
+
+Magellan Ultralight Blue Background {
+ kind=Light
+ colorUltraLight=#B2D6FF
+ colorExtraLight=#AFD4FE
+ colorLight=#AAD1FE
+ colorMid=#A7CFFD
+ colorDark=#A0CBFB
+ colorUltraDark=#9DC9FB
+ colorForeground=#003269
+}
+
+Magellan Blue Controls Enabled {
+ kind=Dark
+ colorUltraLight=#0C61AC
+ colorExtraLight=#0B60AA
+ colorLight=#064E92
+ colorMid=#003977
+ colorDark=#003977
+ colorUltraDark=#003977
+ colorForeground=#8FC1F9
+}
+
+Magellan Blue Controls Enabled Border {
+ kind=Dark
+ colorUltraLight=#0B498F
+ colorExtraLight=#0A4487
+ colorLight=#093F7D
+ colorMid=#083C77
+ colorDark=#06284D
+ colorUltraDark=#042040
+ colorForeground=#FFFFFF
+}
+
+Magellan Blue Controls Pressed {
+ kind=Dark
+ colorUltraLight=#033568
+ colorExtraLight=#033568
+ colorLight=#053B6E
+ colorMid=#084275
+ colorDark=#084275
+ colorUltraDark=#084275
+ colorForeground=#8FC1F9
+}
+
+Magellan Blue Controls Pressed Border {
+ kind=Dark
+ colorUltraLight=#062547
+ colorExtraLight=#062241
+ colorLight=#062547
+ colorMid=#062547
+ colorDark=#042244
+ colorUltraDark=#042040
+ colorForeground=#FFFFFF
+}
+
+Magellan Blue Controls Active {
+ kind=Dark
+ colorUltraLight=#1074E4
+ colorExtraLight=#1072DF
+ colorLight=#0F6FD8
+ colorMid=#0E67CA
+ colorDark=#0D5FBB
+ colorUltraDark=#0C5AB0
+ colorForeground=#021C3A
+}
+
+Magellan Blue Controls Active Border {
+ kind=Dark
+ colorUltraLight=#074A8C
+ colorExtraLight=#074787
+ colorLight=#094480
+ colorMid=#0C457A
+ colorDark=#093F75
+ colorUltraDark=#0C3E70
+ colorForeground=#B7D8FC
+}
+
+Magellan Light Blue Controls Active {
+ kind=Light
+ colorUltraLight=#438EE9
+ colorExtraLight=#428CE5
+ colorLight=#4189E0
+ colorMid=#3F83D4
+ colorDark=#3C7BC8
+ colorUltraDark=#3A77C0
+ colorForeground=#021C3A
+}
+
+Magellan Light Blue Controls Enabled {
+ kind=Light
+ colorUltraLight=#B0DFFE
+ colorExtraLight=#B0DCF7
+ colorLight=#AEDAF5
+ colorMid=#A8CCF0
+ colorDark=#96BFEB
+ colorUltraDark=#8EBEE6
+ colorForeground=#15428B
+}
+
+Magellan Light Blue Borders Enabled {
+ kind=Light
+ colorUltraLight=#97C8F0
+ colorExtraLight=#91C3EF
+ colorLight=#87BAE9
+ colorMid=#80B5E6
+ colorDark=#70A7DF
+ colorUltraDark=#5691D3
+ colorForeground=#15428B
+}
+
+Magellan Green Controls {
+ kind=Light
+ colorUltraLight=#63CA15
+ colorExtraLight=#63CA15
+ colorLight=#5BBD12
+ colorMid=#51AC0D
+ colorDark=#40750B
+ colorUltraDark=#335E0D
+ colorForeground=#143000
+}
+
+Magellan Green Controls Mark {
+ kind=Light
+ colorUltraLight=#5FB315
+ colorExtraLight=#57A314
+ colorLight=#509C13
+ colorMid=#44870C
+ colorDark=#1F3806
+ colorUltraDark=#1B3008
+ colorForeground=#0D1C00
+}
+
+Magellan Green Controls Border {
+ kind=Dark
+ colorUltraLight=#1E4600
+ colorExtraLight=#1E4600
+ colorLight=#1E4600
+ colorMid=#1E4600
+ colorDark=#1E4600
+ colorUltraDark=#1E4600
+ colorForeground=#3E7A0D
+}
+
+Magellan Light Green Controls {
+ kind=Light
+ colorUltraLight=#DDFF66
+ colorExtraLight=#DDFF66
+ colorLight=#C6E857
+ colorMid=#9FC23E
+ colorDark=#809630
+ colorUltraDark=#5D7023
+ colorForeground=#333333
+}
+
+Magellan Light Green Controls Border {
+ kind=Light
+ colorUltraLight=#DDFF66
+ colorExtraLight=#DDFF66
+ colorLight=#DDFF66
+ colorMid=#DDFF66
+ colorDark=#DDFF66
+ colorUltraDark=#DDFF66
+ colorForeground=#333333
+}
+
+Magellan Ochre Controls {
+ kind=Light
+ colorUltraLight=#FEC66F
+ colorExtraLight=#FEC66F
+ colorLight=#FCA445
+ colorMid=#DD8932
+ colorDark=#DD8932
+ colorUltraDark=#DD8932
+ colorForeground=#010100
+}
+
+Magellan Ochre Controls Border {
+ kind=Light
+ colorUltraLight=#9A835F
+ colorExtraLight=#9A835F
+ colorLight=#9A835F
+ colorMid=#998058
+ colorDark=#A1875A
+ colorUltraDark=#A1875A
+ colorForeground=#000000
+}
+
+Magellan Light Blue Separator {
+ kind=Light
+ colorUltraLight=#B0EFFF
+ colorExtraLight=#A8D0FD
+ colorLight=#A5CEFC
+ colorMid=#7197C2
+ colorDark=#7197C2
+ colorUltraDark=#7197C2
+ colorForeground=#003269
+}
+
+Magellan Uneditable Controls {
+ kind=Light
+ colorUltraLight=#0556A4
+ colorExtraLight=#0556A4
+ colorLight=#0556A4
+ colorMid=#0556A4
+ colorDark=#0556A4
+ colorUltraDark=#0555A3
+ colorForeground=#8ABDF6
+}
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/api/skin/mariner.colorschemes b/substance/src/main/resources/org/pushingpixels/substance/api/skin/mariner.colorschemes
new file mode 100644
index 0000000..c7f3ecd
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/api/skin/mariner.colorschemes
@@ -0,0 +1,187 @@
+Mariner Enabled {
+ kind=Light
+ colorUltraLight=#EAEEF1
+ colorExtraLight=#ECF0F3
+ colorLight=#D8D7D3
+ colorMid=#CDCDCF
+ colorDark=#B8B6B7
+ colorUltraDark=#A5A5A7
+ colorForeground=#000000
+}
+
+Mariner Disabled {
+ kind=Light
+ colorUltraLight=#EAEEF1
+ colorExtraLight=#ECF0F3
+ colorLight=#D8D7D3
+ colorMid=#CDCDCF
+ colorDark=#B8B6B7
+ colorUltraDark=#A5A5A7
+ colorForeground=#666666
+}
+
+Mariner Enabled Border {
+ kind=Light
+ colorUltraLight=#9599A4
+ colorExtraLight=#9598A1
+ colorLight=#9599A4
+ colorMid=#7A7D90
+ colorDark=#74768B
+ colorUltraDark=#626473
+ colorForeground=#1F1B29
+}
+
+Mariner Enabled Mark {
+ kind=Light
+ colorUltraLight=#9599A4
+ colorExtraLight=#9598A1
+ colorLight=#9599A4
+ colorMid=#7A7D90
+ colorDark=#74768B
+ colorUltraDark=#626473
+ colorForeground=#626473
+}
+
+Mariner Active {
+ kind=Light
+ colorUltraLight=#FFECBA
+ colorExtraLight=#FEE4B1
+ colorLight=#F5DEA3
+ colorMid=#EECD82
+ colorDark=#E2C384
+ colorUltraDark=#E1B575
+ colorForeground=#4A1903
+}
+
+Mariner Active Border {
+ kind=Dark
+ colorUltraLight=#9E6030
+ colorExtraLight=#9C5F27
+ colorLight=#834C18
+ colorMid=#72451B
+ colorDark=#633E26
+ colorUltraDark=#573620
+ colorForeground=#E4B765
+}
+
+Mariner Active Mark {
+ kind=Dark
+ colorUltraLight=#9E6030
+ colorExtraLight=#9C5F27
+ colorLight=#834C18
+ colorMid=#72451B
+ colorDark=#633E26
+ colorUltraDark=#573620
+ colorForeground=#573620
+}
+
+Mariner Watermark {
+ kind=Dark
+ colorUltraLight=#7D7C81
+ colorExtraLight=#777279
+ colorLight=#5B535E
+ colorMid=#443D45
+ colorDark=#29242A
+ colorUltraDark=#171314
+ colorForeground=#26252D
+}
+
+Mariner Header {
+ kind=Dark
+ colorUltraLight=#453A38
+ colorExtraLight=#3E3433
+ colorLight=#2E2425
+ colorMid=#201718
+ colorDark=#181317
+ colorUltraDark=#181214
+ colorForeground=#FFF8F6
+}
+
+Mariner Header Border {
+ kind=Dark
+ colorUltraLight=#18161B
+ colorExtraLight=#18171C
+ colorLight=#110D0E
+ colorMid=#070004
+ colorDark=#050004
+ colorUltraDark=#000000
+ colorForeground=#686262
+}
+
+Mariner Uneditable {
+ kind=Light
+ colorUltraLight=#CACFD3
+ colorExtraLight=#CBD0D4
+ colorLight=#B3B5C1
+ colorMid=#B2B5BA
+ colorDark=#AEB3B9
+ colorUltraDark=#A5A9AC
+ colorForeground=#000000
+}
+
+Mariner Footer Enabled {
+ kind=Light
+ colorUltraLight=#DADEE1
+ colorExtraLight=#DCE0E3
+ colorLight=#C8C7C3
+ colorMid=#BDBDBF
+ colorDark=#A8A6A7
+ colorUltraDark=#959597
+ colorForeground=#000000
+}
+
+Mariner Footer Disabled {
+ kind=Light
+ colorUltraLight=#DADEE1
+ colorExtraLight=#DCE0E3
+ colorLight=#C8C7C3
+ colorMid=#BDBDBF
+ colorDark=#A8A6A7
+ colorUltraDark=#959597
+ colorForeground=#555555
+}
+
+Mariner Footer Enabled Border {
+ kind=Light
+ colorUltraLight=#858994
+ colorExtraLight=#858891
+ colorLight=#858994
+ colorMid=#6A6D80
+ colorDark=#64667B
+ colorUltraDark=#525463
+ colorForeground=#1F1B29
+}
+
+Mariner Footer Enabled Mark {
+ kind=Light
+ colorUltraLight=#858994
+ colorExtraLight=#858891
+ colorLight=#858994
+ colorMid=#6A6D80
+ colorDark=#64667B
+ colorUltraDark=#525463
+ colorForeground=#525463
+}
+
+Mariner Footer Separator {
+ kind=Light
+ colorUltraLight=#CECECE
+ colorExtraLight=#CCCCCC
+ colorLight=#B7B7B7
+ colorMid=#ACACAC
+ colorDark=#979797
+ colorUltraDark=#848484
+ colorForeground=#000000
+}
+
+Mariner Footer Watermark {
+ kind=Light
+ colorUltraLight=#CCCCCE
+ colorExtraLight=#C4C4C6
+ colorLight=#BBB9BA
+ colorMid=#B8B6B9
+ colorDark=#AFAAAE
+ colorUltraDark=#9FA2A2
+ colorForeground=#000000
+}
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/api/skin/nebula.colorschemes b/substance/src/main/resources/org/pushingpixels/substance/api/skin/nebula.colorschemes
new file mode 100644
index 0000000..6786f6d
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/api/skin/nebula.colorschemes
@@ -0,0 +1,110 @@
+Nebula Active {
+ kind=Light
+ colorUltraLight=#F6F8FA
+ colorExtraLight=#DFE6ED
+ colorLight=#C1D6E9
+ colorMid=#A3B8CB
+ colorDark=#62778A
+ colorUltraDark=#42576A
+ colorForeground=#000000
+}
+
+Nebula Enabled {
+ kind=Light
+ colorUltraLight=#FBFBFC
+ colorExtraLight=#F4F7FC
+ colorLight=#F1F2F4
+ colorMid=#D6D9DF
+ colorDark=#95989E
+ colorUltraDark=#75787E
+ colorForeground=#2A2E36
+}
+
+Nebula Disabled {
+ kind=Light
+ colorUltraLight=#E3EFE9
+ colorExtraLight=#DFE2E6
+ colorLight=#DADDE3
+ colorMid=#D6D9DF
+ colorDark=#C9CCD2
+ colorUltraDark=#BCBFC5
+ colorForeground=#848B98
+}
+
+Nebula Pressed {
+ kind=Dark
+ colorUltraLight=#8FA9C0
+ colorExtraLight=#7695B2
+ colorLight=#5B89B4
+ colorMid=#33628C
+ colorDark=#1C3851
+ colorUltraDark=#000000
+ colorForeground=#FFFFFF
+}
+
+Nebula Rollover Selected {
+ kind=Light
+ colorUltraLight=#F8FAFC
+ colorExtraLight=#E8FDFF
+ colorLight=#D4E9FC
+ colorMid=#B6CBDE
+ colorDark=#3B556D
+ colorUltraDark=#00051D
+ colorForeground=#000000
+}
+
+Nebula Rollover Unselected {
+ kind=Light
+ colorUltraLight=#FFFFFF
+ colorExtraLight=#FDFDFE
+ colorLight=#F7F8FA
+ colorMid=#E9ECF2
+ colorDark=#7A7E86
+ colorUltraDark=#55585E
+ colorForeground=#2A2E36
+}
+
+Nebula Determinate {
+ kind=Light
+ colorUltraLight=#ECD1B3
+ colorExtraLight=#DEAE77
+ colorLight=#CF8838
+ colorMid=#D97F19
+ colorDark=#CA700A
+ colorUltraDark=#C26802
+ colorForeground=#18130E
+}
+
+Nebula Determinate Disabled {
+ kind=Light
+ colorUltraLight=#DCD7D3
+ colorExtraLight=#D8CDC2
+ colorLight=#D6C7B9
+ colorMid=#D5BEA6
+ colorDark=#D1BAA2
+ colorUltraDark=#D1BAA2
+ colorForeground=#8E8F91
+}
+
+Nebula Determinate Border {
+ kind=Dark
+ colorUltraLight=#A74E04
+ colorExtraLight=#A74D00
+ colorLight=#A94F00
+ colorMid=#A94F00
+ colorDark=#983E00
+ colorUltraDark=#862E0B
+ colorForeground=#ECD1B3
+}
+
+Nebula Determinate Disabled Border {
+ kind=Dark
+ colorUltraLight=#CAB3A1
+ colorExtraLight=#CAB3A1
+ colorLight=#CAB3A1
+ colorMid=#CAB3A1
+ colorDark=#C5AEA1
+ colorUltraDark=#C0AAA4
+ colorForeground=#DCD7D3
+}
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/api/skin/office2007.colorschemes b/substance/src/main/resources/org/pushingpixels/substance/api/skin/office2007.colorschemes
new file mode 100644
index 0000000..02e6f82
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/api/skin/office2007.colorschemes
@@ -0,0 +1,494 @@
+Office Blue Watermark {
+ kind=Light
+ colorUltraLight=#DEE8F5
+ colorExtraLight=#D2DFF0
+ colorLight=#D0E0F0
+ colorMid=#CAD9ED
+ colorDark=#BACAD9
+ colorUltraDark=#AEC2D5
+ colorForeground=#15428B
+}
+
+Office Blue Header Watermark {
+ kind=Light
+ colorUltraLight=#BFDBFF
+ colorExtraLight=#BFDBFF
+ colorLight=#BFDBFF
+ colorMid=#BDD8FC
+ colorDark=#B9D3F4
+ colorUltraDark=#ADC7E7
+ colorForeground=#15428B
+}
+
+Office Blue Active {
+ kind=Light
+ colorUltraLight=#9DBDE7
+ colorExtraLight=#BBD5F8
+ colorLight=#ABCCF6
+ colorMid=#AACBF6
+ colorDark=#8CB8F3
+ colorUltraDark=#79ADF1
+ colorForeground=#15428B
+}
+
+Office Blue Title Watermark {
+ kind=Light
+ colorUltraLight=#E3EEFD
+ colorExtraLight=#DBE9FB
+ colorLight=#D7E6FA
+ colorMid=#D3E4F9
+ colorDark=#CCDFF8
+ colorUltraDark=#CADEF7
+ colorForeground=#15428B
+}
+
+Office Blue Border Active {
+ kind=Light
+ colorUltraLight=#7F96BA
+ colorExtraLight=#63779E
+ colorLight=#576890
+ colorMid=#596991
+ colorDark=#576890
+ colorUltraDark=#566890
+ colorForeground=#15428B
+}
+
+Office Blue Enabled {
+ kind=Light
+ colorUltraLight=#D0E1F7
+ colorExtraLight=#C8DBEE
+ colorLight=#C5D7F0
+ colorMid=#C1D3EC
+ colorDark=#C0D4ED
+ colorUltraDark=#BCD0E9
+ colorForeground=#15428B
+}
+
+Office Blue Border Enabled {
+ kind=Light
+ colorUltraLight=#9EBAE1
+ colorExtraLight=#8AADDB
+ colorLight=#84A8D7
+ colorMid=#81A3D0
+ colorDark=#6E90B8
+ colorUltraDark=#537BAE
+ colorForeground=#15428B
+}
+
+Office Blue Pressed {
+ kind=Light
+ colorUltraLight=#FDA661
+ colorExtraLight=#FC963C
+ colorLight=#FD9D3E
+ colorMid=#FC923C
+ colorDark=#FC8F3C
+ colorUltraDark=#FC923D
+ colorForeground=#15428B
+}
+
+Office Blue Pressed Selected {
+ kind=Light
+ colorUltraLight=#FEB96C
+ colorExtraLight=#FC963C
+ colorLight=#FDA962
+ colorMid=#FD953C
+ colorDark=#FC923D
+ colorUltraDark=#FC8F3D
+ colorForeground=#15428B
+}
+
+Office Blue Rollover {
+ kind=Light
+ colorUltraLight=#FEF7D5
+ colorExtraLight=#FCEFC3
+ colorLight=#FFE59F
+ colorMid=#FFD560
+ colorDark=#FFD14E
+ colorUltraDark=#FFD048
+ colorForeground=#15428B
+}
+
+Office Blue Rollover Selected {
+ kind=Light
+ colorUltraLight=#FDBD79
+ colorExtraLight=#FEE069
+ colorLight=#FFBD79
+ colorMid=#FFC64C
+ colorDark=#FEAA38
+ colorUltraDark=#FEA335
+ colorForeground=#15428B
+}
+
+Office Blue Selected {
+ kind=Light
+ colorUltraLight=#FBDBB5
+ colorExtraLight=#FDEB9F
+ colorLight=#FEC778
+ colorMid=#FDD07C
+ colorDark=#FDB759
+ colorUltraDark=#FEB456
+ colorForeground=#15428B
+}
+
+Office Blue Separator {
+ kind=Light
+ colorUltraLight=#D5E3F1
+ colorExtraLight=#D0E1F7
+ colorLight=#C2D5ED
+ colorMid=#C1D3EC
+ colorDark=#8DACD3
+ colorUltraDark=#7797C1
+ colorForeground=#F8FAFC
+}
+
+Office Blue Tab Rollover {
+ kind=Light
+ colorUltraLight=#E0E3DC
+ colorExtraLight=#E4E6E0
+ colorLight=#D8DBD0
+ colorMid=#E0D4A8
+ colorDark=#E2D19E
+ colorUltraDark=#E3D198
+ colorForeground=#15428B
+}
+
+Office Blue Tab Selected {
+ kind=Light
+ colorUltraLight=#F5FAFF
+ colorExtraLight=#EBF3FE
+ colorLight=#EAF2FD
+ colorMid=#E9F1FA
+ colorDark=#E8F0F9
+ colorUltraDark=#E1EAF6
+ colorForeground=#15428B
+}
+
+Office Silver Watermark {
+ kind=Light
+ colorUltraLight=#EEF4F4
+ colorExtraLight=#EBF1F1
+ colorLight=#E9EEEE
+ colorMid=#E5E8EA
+ colorDark=#E1E5E9
+ colorUltraDark=#DFE3E9
+ colorForeground=#4C535C
+}
+
+Office Silver Header Watermark {
+ kind=Light
+ colorUltraLight=#D0D4DD
+ colorExtraLight=#D0D4DD
+ colorLight=#D0D4DD
+ colorMid=#D0D4DD
+ colorDark=#D0D4DD
+ colorUltraDark=#D0D4DD
+ colorForeground=#4C535C
+}
+
+Office Silver Title Watermark {
+ kind=Light
+ colorUltraLight=#E3E7ED
+ colorExtraLight=#E5E6E9
+ colorLight=#DEE0E3
+ colorMid=#CFCFD0
+ colorDark=#C7CBD1
+ colorUltraDark=#BCC1CA
+ colorForeground=#4C535C
+}
+
+Office Silver Active {
+ kind=Light
+ colorUltraLight=#DFE2E8
+ colorExtraLight=#DBDFE4
+ colorLight=#CED3D9
+ colorMid=#C6CACF
+ colorDark=#C2C6CC
+ colorUltraDark=#BBC1C9
+ colorForeground=#4C535C
+}
+
+Office Silver Border Active {
+ kind=Light
+ colorUltraLight=#A9B1B8
+ colorExtraLight=#A9B1B8
+ colorLight=#A9B1B8
+ colorMid=#A9B1B8
+ colorDark=#A9B0B9
+ colorUltraDark=#A9B1B8
+ colorForeground=#15428B
+}
+
+Office Silver Enabled {
+ kind=Light
+ colorUltraLight=#F0F2F2
+ colorExtraLight=#F3F5F5
+ colorLight=#F1F3F3
+ colorMid=#EFF1F3
+ colorDark=#E9ECEF
+ colorUltraDark=#E7EAEE
+ colorForeground=#4C535C
+}
+
+Office Silver Border Enabled {
+ kind=Light
+ colorUltraLight=#C6C7CA
+ colorExtraLight=#B8C4C8
+ colorLight=#A0ACB3
+ colorMid=#B1BBC1
+ colorDark=#949FA4
+ colorUltraDark=#939FA4
+ colorForeground=#7C7C7C
+}
+
+Office Silver Mark Enabled {
+ kind=Light
+ colorUltraLight=#7C7C7C
+ colorExtraLight=#7C7C7C
+ colorLight=#7C7C7C
+ colorMid=#7C7C7C
+ colorDark=#7C7C7C
+ colorUltraDark=#7C7C7C
+ colorForeground=#7C7C7C
+}
+
+Office Silver Pressed {
+ kind=Light
+ colorUltraLight=#FDA661
+ colorExtraLight=#FC963C
+ colorLight=#FD9D3E
+ colorMid=#FC923C
+ colorDark=#FC8F3C
+ colorUltraDark=#FC923D
+ colorForeground=#4C535C
+}
+
+Office Silver Pressed Selected {
+ kind=Light
+ colorUltraLight=#FEB96C
+ colorExtraLight=#FC963C
+ colorLight=#FDA962
+ colorMid=#FD953C
+ colorDark=#FC923D
+ colorUltraDark=#FC8F3D
+ colorForeground=#4C535C
+}
+
+Office Silver Rollover {
+ kind=Light
+ colorUltraLight=#FEF7D5
+ colorExtraLight=#FCEFC3
+ colorLight=#FFE59F
+ colorMid=#FFD560
+ colorDark=#FFD14E
+ colorUltraDark=#FFD048
+ colorForeground=#4C535C
+}
+
+Office Silver Rollover Selected {
+ kind=Light
+ colorUltraLight=#FDBD79
+ colorExtraLight=#FEE069
+ colorLight=#FFBD79
+ colorMid=#FFC64C
+ colorDark=#FEAA38
+ colorUltraDark=#FEA335
+ colorForeground=#4C535C
+}
+
+Office Silver Selected {
+ kind=Light
+ colorUltraLight=#FBDBB5
+ colorExtraLight=#FDEB9F
+ colorLight=#FEC778
+ colorMid=#FDD07C
+ colorDark=#FDB759
+ colorUltraDark=#FEB456
+ colorForeground=#4C535C
+}
+
+Office Silver Separator {
+ kind=Light
+ colorUltraLight=#EAECEF
+ colorExtraLight=#D5DBE7
+ colorLight=#B3B7BA
+ colorMid=#AFB4B8
+ colorDark=#ADB1B5
+ colorUltraDark=#ADB1B5
+ colorForeground=#7C7C7C
+}
+
+Office Silver Tab Rollover {
+ kind=Light
+ colorUltraLight=#E0E3DC
+ colorExtraLight=#E4E6E0
+ colorLight=#D8DBD0
+ colorMid=#E0D4A8
+ colorDark=#E2D19E
+ colorUltraDark=#E3D198
+ colorForeground=#15428B
+}
+
+Office Silver Tab Selected {
+ kind=Light
+ colorUltraLight=#F7F8FA
+ colorExtraLight=#F5F5F6
+ colorLight=#EFF1F5
+ colorMid=#EEF1F5
+ colorDark=#E8ECF2
+ colorUltraDark=#E5EAF1
+ colorForeground=#4C535C
+}
+
+Office Border Selected {
+ kind=Light
+ colorUltraLight=#C6C0B1
+ colorExtraLight=#AEA590
+ colorLight=#8E8165
+ colorMid=#8E8064
+ colorDark=#9A7057
+ colorUltraDark=#9F7559
+ colorForeground=#FCE59C
+}
+
+Office Border Pressed {
+ kind=Light
+ colorUltraLight=#C8BAA4
+ colorExtraLight=#BDB19E
+ colorLight=#A6957B
+ colorMid=#8B7654
+ colorDark=#8B7654
+ colorUltraDark=#8B7654
+ colorForeground=#FEBE64
+}
+
+Office Border Rollover {
+ kind=Light
+ colorUltraLight=#DAC378
+ colorExtraLight=#D9C176
+ colorLight=#D8BB6D
+ colorMid=#D6B664
+ colorDark=#D7B867
+ colorUltraDark=#CFBA73
+ colorForeground=#FFE798
+}
+
+Office Border Rollover Selected {
+ kind=Light
+ colorUltraLight=#B1A389
+ colorExtraLight=#AE9F7D
+ colorLight=#A19476
+ colorMid=#9C8F73
+ colorDark=#9C8864
+ colorUltraDark=#8E8165
+ colorForeground=#F7C05B
+}
+
+Office Black Watermark {
+ kind=Light
+ colorUltraLight=#C1C8D6
+ colorExtraLight=#BDC5D1
+ colorLight=#B9C0C8
+ colorMid=#B5BEC7
+ colorDark=#B0B2C3
+ colorUltraDark=#A6A9B0
+ colorForeground=#4C535C
+}
+
+Office Black Header Watermark {
+ kind=Dark
+ colorUltraLight=#404040
+ colorExtraLight=#404040
+ colorLight=#404040
+ colorMid=#404040
+ colorDark=#404040
+ colorUltraDark=#404040
+ colorForeground=#FFFFFF
+}
+
+Office Black Enabled {
+ kind=Light
+ colorUltraLight=#E2E8E8
+ colorExtraLight=#D0D5D9
+ colorLight=#C3CAD0
+ colorMid=#B9C0C8
+ colorDark=#B5BEC7
+ colorUltraDark=#B4BBC5
+ colorForeground=#282828
+}
+
+Office Black Mark Enabled {
+ kind=Light
+ colorUltraLight=#595959
+ colorExtraLight=#595959
+ colorLight=#595959
+ colorMid=#595959
+ colorDark=#595959
+ colorUltraDark=#595959
+ colorForeground=#595959
+}
+
+Office Black Header Active {
+ kind=Dark
+ colorUltraLight=#515151
+ colorExtraLight=#4A4A4A
+ colorLight=#3D3D3D
+ colorMid=#2D2D2D
+ colorDark=#1D1D1D
+ colorUltraDark=#151515
+ colorForeground=#FFFFFF
+}
+
+Office Black Header Enabled {
+ kind=Dark
+ colorUltraLight=#5E5E5E
+ colorExtraLight=#545454
+ colorLight=#464646
+ colorMid=#333333
+ colorDark=#202020
+ colorUltraDark=#161616
+ colorForeground=#FFFFFF
+}
+
+Office Black Header Mark Enabled {
+ kind=Light
+ colorUltraLight=#B4BBC5
+ colorExtraLight=#B4BBC5
+ colorLight=#B4BBC5
+ colorMid=#B4BBC5
+ colorDark=#B4BBC5
+ colorUltraDark=#B4BBC5
+ colorForeground=#B4BBC5
+}
+
+Office Black Footer Enabled {
+ kind=Dark
+ colorUltraLight=#575757
+ colorExtraLight=#545454
+ colorLight=#505050
+ colorMid=#444444
+ colorDark=#404040
+ colorUltraDark=#323232
+ colorForeground=#FFFFFF
+}
+
+Office Black Footer Border Enabled {
+ kind=Dark
+ colorUltraLight=#373737
+ colorExtraLight=#343434
+ colorLight=#303030
+ colorMid=#242424
+ colorDark=#202020
+ colorUltraDark=#121212
+ colorForeground=#FFFFFF
+}
+
+Office Black Footer Separator {
+ kind=Dark
+ colorUltraLight=#444444
+ colorExtraLight=#424242
+ colorLight=#404040
+ colorMid=#383838
+ colorDark=#151515
+ colorUltraDark=#111111
+ colorForeground=#B4B4B4
+}
diff --git a/substance/src/main/resources/org/pushingpixels/substance/api/skin/twilight.colorschemes b/substance/src/main/resources/org/pushingpixels/substance/api/skin/twilight.colorschemes
new file mode 100644
index 0000000..3f08708
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/api/skin/twilight.colorschemes
@@ -0,0 +1,133 @@
+Twilight Enabled {
+ kind=Dark
+ colorUltraLight=#3E3C35
+ colorExtraLight=#3D3B34
+ colorLight=#3B3A33
+ colorMid=#393731
+ colorDark=#35342E
+ colorUltraDark=#35332D
+ colorForeground=#B9B49E
+}
+
+Twilight Active {
+ kind=Light
+ colorUltraLight=#969280
+ colorExtraLight=#95917F
+ colorLight=#8F8B7A
+ colorMid=#8C8877
+ colorDark=#35342E
+ colorUltraDark=#35332D
+ colorForeground=#000000
+}
+
+Twilight Selected Disabled Border {
+ kind=Dark
+ colorUltraLight=#3B3932
+ colorExtraLight=#3B3932
+ colorLight=#3B3932
+ colorMid=#3B3932
+ colorDark=#3B3932
+ colorUltraDark=#3B3932
+ colorForeground=#B9B49E
+}
+
+Twilight Border {
+ kind=Dark
+ colorUltraLight=#292823
+ colorExtraLight=#292823
+ colorLight=#292823
+ colorMid=#292823
+ colorDark=#292823
+ colorUltraDark=#292823
+ colorForeground=#B9B49E
+}
+
+Twilight Mark Active {
+ kind=Dark
+ colorUltraLight=#292823
+ colorExtraLight=#292823
+ colorLight=#292823
+ colorMid=#292823
+ colorDark=#292823
+ colorUltraDark=#292823
+ colorForeground=#292823
+}
+
+Twilight Highlight {
+ kind=Light
+ colorUltraLight=#C7AD81
+ colorExtraLight=#C7AE81
+ colorLight=#C7AE82
+ colorMid=#C7AE81
+ colorDark=#C7AE82
+ colorUltraDark=#C7AE81
+ colorForeground=#28201B
+}
+
+Twilight Watermark {
+ kind=Light
+ colorUltraLight=#4C4A41
+ colorExtraLight=#4C4A41
+ colorLight=#4C4A41
+ colorMid=#4C4A41
+ colorDark=#4C4A41
+ colorUltraDark=#4C4A41
+ colorForeground=#24231F
+}
+
+Twilight Decorations Watermark {
+ kind=Light
+ colorUltraLight=#4B4940
+ colorExtraLight=#4A483F
+ colorLight=#46443C
+ colorMid=#44423A
+ colorDark=#414038
+ colorUltraDark=#413F37
+ colorForeground=#F1F1F2
+}
+
+Twilight Separator {
+ kind=Dark
+ colorUltraLight=#5B5952
+ colorExtraLight=#58564F
+ colorLight=#545249
+ colorMid=#3B3A33
+ colorDark=#36352E
+ colorUltraDark=#35342D
+ colorForeground=#F1F1F2
+}
+
+Twilight Decorations Separator {
+ kind=Dark
+ colorUltraLight=#4F4E4A
+ colorExtraLight=#4B4A47
+ colorLight=#494842
+ colorMid=#35342D
+ colorDark=#302F29
+ colorUltraDark=#2F2D28
+ colorForeground=#F1F1F2
+}
+
+Twilight Header Watermark {
+ kind=Dark
+ colorUltraLight=#141414
+ colorExtraLight=#121212
+ colorLight=#101010
+ colorMid=#0B0B0B
+ colorDark=#030303
+ colorUltraDark=#000000
+ colorForeground=#FFFFFF
+}
+
+Twilight Header Border {
+ kind=Dark
+ colorUltraLight=#000000
+ colorExtraLight=#000000
+ colorLight=#000000
+ colorMid=#000000
+ colorDark=#000000
+ colorUltraDark=#000000
+ colorForeground=#B9B49E
+}
+
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/jgoodies/looks/common/shadow.png b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/jgoodies/looks/common/shadow.png
new file mode 100644
index 0000000..2bf27f2
Binary files /dev/null and b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/jgoodies/looks/common/shadow.png differ
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels.properties
new file mode 100644
index 0000000..bc1d6db
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels.properties
@@ -0,0 +1,242 @@
+#
+# Some of the strings defined here have the same keys as found in
+# com.sun.swing.internal.plaf.basic.resource.basic.properties
+# apple.laf.resources.aqua.properties
+#
+# We can not rely on the presence of these properties though, because these
+# property files contain warnings stating this.
+#
+# @author Werner Randelshofer
+# @version 3.0 Reworked.
+#
+FileChooser.size=Size
+
+FileChooser.created=Created
+
+FileChooser.modified=Modified
+
+FileChooser.sizeBytes={1,choice,0#Zero bytes|1#1 byte|1<{1,number} bytes}{2,choice,1#|1< for {2,number} items}
+
+FileChooser.name=Name
+
+FileChooser.whereLabelText=Where
+
+FileChooser.original=Original
+
+FileChooser.sizeKBytes={0,number,#,##0.#} KB\n({1,number} bytes){2,choice,1#|1< for {2,number} items}
+
+FileChooser.sizeMBytes={0,number,#,##0.#} MB\n({1,number} bytes){2,choice,1#|1< for {2,number} items}
+
+FileChooser.sizeGBytes={0,number,#,##0.#} GB\n({1,number} bytes){2,choice,1#|1< for {2,number} items}
+
+FileChooser.items={0} items
+
+FileChooser.sizeUnknown=--
+
+FileChooser.alias=Alias
+
+FileChooser.document=Document
+
+FileChooser.folder=Folder
+
+FileChooser.application=Application
+
+FileChooser.aliasCount={0,choice,1#{0,number}\u00a0alias|1<{0,number}\u00a0aliases}
+
+FileChooser.documentCount={0,choice,1#{0,number}\u00a0document|1<{0,number}\u00a0documents}
+
+FileChooser.folderCount={0,choice,1#{0,number}\u00a0folder|1<{0,number}\u00a0folders}
+
+FileChooser.applicationCount={0,choice,1#{0,number}\u00a0application|1<{0,number}\u00a0applications}
+
+FileChooser.kind=Kind
+
+ColorChooser.grayScaleSlider=Gray Scale Slider
+
+ColorChooser.rgbSliders=RGB Sliders
+
+ColorChooser.cmykSliders=CMYK Sliders
+
+ColorChooser.htmlSliders=HTML Sliders
+
+ColorChooser.rgbRedText=Red
+
+ColorChooser.rgbGreenText=Green
+
+ColorChooser.rgbBlueText=Blue
+
+ColorChooser.cmykCyanText=Cyan
+
+ColorChooser.cmykMagentaText=Magenta
+
+ColorChooser.cmykYellowText=Yellow
+
+ColorChooser.cmykBlackText=Black
+
+ColorChooser.hsbHueText=Hue
+
+ColorChooser.hsbSaturationText=Saturation
+
+ColorChooser.hsbBrightnessText=Brightness
+
+ColorChooser.htmlText=HTML\:
+
+ColorChooser.htmlChooseOnlyWebSaveColorsText=Choose only web-safe colors
+
+ColorChooser.colorSliders=Color Sliders
+
+ColorChooser.colorSwatches=Color Swatches
+
+ColorChooser.hsbSliders=HSB Sliders
+
+
+
+FileChooser.fromLabelText=From\:
+
+FileChooser.newFolderButtonText=New Folder
+
+
+FileChooser.cancelButtonText=Cancel
+
+FileChooser.openButtonText=Open
+
+FileChooser.widget=Widget
+
+FileChooser.widgetCount={0,choice,1\#{0,number}\u00A0folder|1<{0,number}\u00A0widgets}
+
+ColorChooser.colorWheel=Color Wheel
+
+ColorChooser.crayon.800000=Cayenne
+ColorChooser.crayon.808000=Asparagus
+ColorChooser.crayon.008000=Clover
+ColorChooser.crayon.008080=Teal
+ColorChooser.crayon.000080=Midnight
+ColorChooser.crayon.800080=Plum
+ColorChooser.crayon.7f7f7f=Tin
+ColorChooser.crayon.808080=Nickel
+
+ColorChooser.crayon.804000=Mocha
+ColorChooser.crayon.408000=Fern
+ColorChooser.crayon.008040=Moss
+ColorChooser.crayon.004080=Ocean
+ColorChooser.crayon.400080=Eggplant
+ColorChooser.crayon.800040=Maroon
+ColorChooser.crayon.666666=Steel
+ColorChooser.crayon.999999=Aluminium
+
+ColorChooser.crayon.ff0000=Maraschino
+ColorChooser.crayon.ffff00=Lemon
+ColorChooser.crayon.00ff00=Spring
+ColorChooser.crayon.00ffff=Turquoise
+ColorChooser.crayon.0000ff=Blueberry
+ColorChooser.crayon.ff00ff=Magenta
+ColorChooser.crayon.4c4c4c=Iron
+ColorChooser.crayon.b3b3b3=Magnesium
+
+ColorChooser.crayon.ff8000=Tangerine
+ColorChooser.crayon.80ff00=Lime
+ColorChooser.crayon.00ff80=Sea Foam
+ColorChooser.crayon.0080ff=Aqua
+ColorChooser.crayon.8000ff=Grape
+ColorChooser.crayon.ff0080=Strawberry
+ColorChooser.crayon.333333=Tungsten
+ColorChooser.crayon.cccccc=Silver
+
+ColorChooser.crayon.ff6666=Salmon
+ColorChooser.crayon.ffff66=Banana
+ColorChooser.crayon.66ff66=Flora
+ColorChooser.crayon.66ffff=Ice
+ColorChooser.crayon.6666ff=Orchid
+ColorChooser.crayon.ff66ff=Bubblegum
+ColorChooser.crayon.191919=Lead
+ColorChooser.crayon.e6e6e6=Mercury
+
+ColorChooser.crayon.ffcc66=Cantaloupe
+ColorChooser.crayon.ccff66=Honeydew
+ColorChooser.crayon.66ffcc=Spindrift
+ColorChooser.crayon.66ccff=Sky
+ColorChooser.crayon.cc66ff=Lavender
+ColorChooser.crayon.ff6fcf=Carnation
+ColorChooser.crayon.000000=Licorice
+ColorChooser.crayon.ffffff=Snow
+
+ColorChooser.crayons=Crayons
+
+ColorChooser.color=Color
+
+ColorChooser.list=List\:
+
+ColorChooser.apple.000000=Black
+
+ColorChooser.apple.0000ff=Blue
+
+ColorChooser.apple.996633=Brown
+
+ColorChooser.apple.00ffff=Cyan
+
+ColorChooser.apple.00ff00=Green
+
+ColorChooser.apple.ff00ff=Magenta
+
+ColorChooser.apple.ff8000=Orange
+
+ColorChooser.apple.800080=Purple
+
+ColorChooser.apple.ff0000=Red
+
+ColorChooser.apple.ffff00=Yellow
+
+ColorChooser.apple.ffffff=White
+
+ColorChooser.appleColors=Apple
+
+ColorChooser.windowsBasicColors=Windows Basic Colors
+
+ColorChooser.colorPalettes=Color Palettes
+
+ColorChooser.webSafeColors=Web Safe Colors
+
+ColorChooser.profileContainsNColors=The profile "{0}" contains {1} colors.
+
+FileChooser.replaceAlert=<b>\u201C{0}\u201D already exists.<br>Do you want to replace it?</b><p>A file or folder with the same name already exists in<br>{1}.<br>Replacing it will overwrite its current contents.
+#Replace
+FileChooser.replace=Replace
+
+OptionPane.cancel=Cancel
+
+OptionPane.css=<head><style type\="text/css">b { font\: 13pt \\"Lucida Grande\\" } p { font\: 11pt \\"Lucida Grande\\"; margin-top\: 8px }</style></head>
+
+ColorChooser.colorPicker=Color Picker
+
+TextComponent.cut=Cut
+
+TextComponent.copy=Copy
+
+TextComponent.paste=Paste
+
+FileChooser.computerName=Computer
+
+FileChooser.lookInLabelText=Look In:
+FileChooser.saveInLabelText=Save In:
+FileChooser.fileNameLabelText=File Name:
+FileChooser.filesOfTypeLabelText=Files of Type:
+FileChooser.upFolderToolTipText=Up One Level
+FileChooser.upFolderAccessibleName=Up
+FileChooser.homeFolderToolTipText=Home
+FileChooser.homeFolderAccessibleName=Home
+FileChooser.newFolderToolTipText=Create New Folder
+FileChooser.newFolderAccessibleName=New Folder
+FileChooser.newFolderActionLabelText=New Folder
+FileChooser.listViewButtonToolTipText=List
+FileChooser.listViewButtonAccessibleName=List
+FileChooser.listViewActionLabelText=List
+FileChooser.detailsViewButtonToolTipText=Details
+FileChooser.detailsViewButtonAccessibleName=Details
+FileChooser.detailsViewActionLabelText=Details
+FileChooser.refreshActionLabelText=Refresh
+FileChooser.viewMenuLabelText=View
+FileChooser.fileNameHeaderText=Name
+FileChooser.fileSizeHeaderText=Size
+FileChooser.fileTypeHeaderText=Type
+FileChooser.fileDateHeaderText=Modified
+FileChooser.fileAttrHeaderText=Attributes
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_de.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_de.properties
new file mode 100644
index 0000000..6c454d1
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_de.properties
@@ -0,0 +1,271 @@
+
+FileChooser.size=Gr\u00F6sse
+
+FileChooser.created=Erstellt
+
+FileChooser.modified=Ge\u00E4ndert
+
+FileChooser.sizeBytes={1,choice,0#0 Bytes|1#1 Byte|1<{1,number} Bytes}{2,choice,1#|1< f�r {2,number} Objekte}
+
+FileChooser.name=Name
+
+FileChooser.whereLabelText=Ort
+
+FileChooser.original=Original
+
+FileChooser.sizeKBytes={0,number,#,##0.#} KB\n({1,number} Bytes){2,choice,1#|1< f�r {2,number} Objekte}
+
+FileChooser.sizeMBytes={0,number,#,##0.#} MB\n({1,number} Bytes){2,choice,1#|1< f�r {2,number} Objekte}
+
+FileChooser.sizeGBytes={0,number,#,##0.#} GB\n({1,number} Bytes){2,choice,1#|1< f�r {2,number} Objekte}
+
+FileChooser.items={0} Objekte
+
+FileChooser.sizeUnknown=--
+
+FileChooser.alias=Alias
+
+FileChooser.document=Dokument
+
+FileChooser.folder=Ordner
+
+FileChooser.application=Programm
+
+FileChooser.aliasCount={0,choice,1#{0,number}\u00a0Alias|1<{0,number}\u00a0Alias-Dateien}
+
+FileChooser.documentCount={0,choice,1#{0,number}\u00a0Dokument|1<{0,number}\u00a0Dokumente}
+
+FileChooser.folderCount={0,choice,1#{0,number}\u00a0Ordner|1<{0,number}\u00a0Ordner}
+
+FileChooser.applicationCount={0,choice,1#{0,number}\u00a0Programm|1<{0,number}\u00a0Programme}
+
+FileChooser.kind=Art
+
+ColorChooser.grayScaleSlider=Graustufen
+
+ColorChooser.rgbSliders=RGB
+
+ColorChooser.cmykSliders=CMYK
+
+ColorChooser.htmlSliders=HTML
+
+ColorChooser.rgbRedText=Rot
+
+ColorChooser.rgbGreenText=Gr\u00FCn
+
+ColorChooser.rgbBlueText=Blau
+
+ColorChooser.cmykCyanText=Zyan
+
+ColorChooser.cmykMagentaText=Magenta
+
+ColorChooser.cmykYellowText=Gelb
+
+ColorChooser.cmykBlackText=Schwarz
+
+ColorChooser.hsbHueText=Farbton
+
+ColorChooser.hsbSaturationText=S\u00E4ttigung
+
+ColorChooser.hsbBrightnessText=Helligkeit
+
+ColorChooser.htmlText=HTML\:
+
+ColorChooser.htmlChooseOnlyWebSaveColorsText=Nur web-sichere Farben w\u00E4hlen
+
+ColorChooser.colorSliders=Farbregler
+
+ColorChooser.colorSwatches=Farbfelder
+
+ColorChooser.hsbSliders=HSV
+
+FileChooser.fromLabelText=Ort\:
+
+FileChooser.widget=Widget
+
+FileChooser.widgetCount={0,choice,1\#{0,number}\u00A0folder|1<{0,number}\u00A0Widgets}
+
+ColorChooser.colorWheel=Farbrad
+
+ColorChooser.crayon.000000=Lakritze
+
+ColorChooser.crayon.000080=Mitternacht
+
+ColorChooser.crayon.800000=Cayenne
+
+ColorChooser.crayon.808000=Spargel
+
+ColorChooser.crayon.0000ff=Blaubeere
+
+ColorChooser.crayon.004080=Ozean
+
+ColorChooser.crayon.008040=Moos
+
+ColorChooser.crayon.0080ff=Wasser
+
+ColorChooser.crayon.00ff00=Fr\u00FChling
+
+ColorChooser.crayon.00ff80=Gischt
+
+ColorChooser.crayon.00ffff=T\u00FCrkis
+
+ColorChooser.crayon.191919=Blei
+
+ColorChooser.crayon.400080=Aubergine
+
+ColorChooser.crayon.408000=Farn
+
+ColorChooser.crayon.4c4c4c=Eisen
+
+ColorChooser.crayon.666666=Stahl
+
+ColorChooser.crayon.6666ff=Orchidee
+
+ColorChooser.crayon.66ccff=Himmel
+
+ColorChooser.crayon.66ffff=Eis
+
+ColorChooser.crayon.800080=Pflaume
+
+ColorChooser.crayon.804000=Mokka
+
+ColorChooser.crayon.808080=Nickel
+
+ColorChooser.crayon.999999=Aluminium
+
+ColorChooser.crayon.b3b3b3=Magnesium
+
+ColorChooser.crayon.cc66ff=Lavendel
+
+ColorChooser.crayon.cccccc=Silber
+
+ColorChooser.crayon.ff0080=Erdbeere
+
+ColorChooser.crayon.ff00ff=Magenta
+
+ColorChooser.crayon.ff6666=Lachs
+
+ColorChooser.crayon.ff66ff=Kaugummi
+
+ColorChooser.crayon.ffff00=Zitrone
+
+ColorChooser.crayon.ffff66=Banane
+
+ColorChooser.crayon.ffffff=Schnee
+
+ColorChooser.crayon.008000=Klee
+
+ColorChooser.crayon.008080=Seegras
+
+ColorChooser.crayon.7f7f7f=Zinn
+
+ColorChooser.crayon.800040=Dunkelrot
+
+ColorChooser.crayon.ff0000=Apfel
+
+ColorChooser.crayon.ff8000=Mandarine
+
+ColorChooser.crayon.80ff00=Limone
+
+ColorChooser.crayon.8000ff=Traube
+
+ColorChooser.crayon.333333=Basalt
+
+ColorChooser.crayon.66ff66=Pflanzen
+
+ColorChooser.crayon.e6e6e6=Quecksilber
+
+ColorChooser.crayon.ffcc66=Netzmelone
+
+ColorChooser.crayon.ccff66=Zuckermelone
+
+ColorChooser.crayon.66ffcc=Meeresgr\u00FCn
+
+ColorChooser.crayon.ff6fcf=Nelke
+
+ColorChooser.crayons=Farbstifte
+
+ColorChooser.apple.000000=Schwarz
+
+ColorChooser.apple.0000ff=Blau
+
+ColorChooser.apple.996633=Braun
+
+ColorChooser.apple.00ffff=Zyan
+
+ColorChooser.apple.00ff00=Gr\u00FCn
+
+ColorChooser.apple.ff00ff=Magenta
+
+ColorChooser.apple.ff8000=Orange
+
+ColorChooser.apple.800080=Lila
+
+ColorChooser.apple.ff0000=Rot
+
+ColorChooser.apple.ffff00=Gelb
+
+ColorChooser.apple.ffffff=Weiss
+
+ColorChooser.appleColors=Apple
+
+ColorChooser.windowsBasicColors=Windows Grundfarben
+
+ColorChooser.color=Farbe
+
+ColorChooser.list=Palette\:
+
+ColorChooser.colorPalettes=Farbpalette
+
+ColorChooser.webSafeColors=Web Safe Colors
+
+ColorChooser.profileContainsNColors=Das Profil "{0}" enth\u00E4lt {1} Farben.
+
+FileChooser.replaceAlert=<b>\u201E{0}\u201C existiert bereits.<br>M\u00F6chten Sie das Objekt ersetzen?</b><p>Es existiert bereits eine Datei oder ein Ordner mit demselben Namen im Ordner \u201E{1}\u201C. Beim Ersetzen wird der Inhalt \u00FCberschrieben.
+#Replace
+FileChooser.replace=Ersetzen
+
+OptionPane.cancel=Abbrechen
+
+OptionPane.css=<head><style type\="text/css">b { font\: 13pt \\"Lucida Grande\\" } p { font\: 11pt \\"Lucida Grande\\"; margin-top\: 8px }</style></head>
+
+ColorChooser.colorPicker=Pipette
+
+TextComponent.cut=Ausschneiden
+
+TextComponent.copy=Kopieren
+
+TextComponent.paste=Einsetzen
+
+FileChooser.computerName=Computer
+
+FileChooser.cancelButtonText=Abbrechen
+
+FileChooser.newFolderButtonText=Neuer Ordner
+
+FileChooser.openButtonText=\u00D6ffnen
+
+FileChooser.lookInLabelText=Suchen in:
+FileChooser.saveInLabelText=Speichern in:
+FileChooser.fileNameLabelText=Dateiname:
+FileChooser.filesOfTypeLabelText=Dateityp:
+FileChooser.upFolderToolTipText=Eine Ebene h\u00f6her
+FileChooser.upFolderAccessibleName=H\u00f6her
+FileChooser.homeFolderToolTipText=Home
+FileChooser.homeFolderAccessibleName=Home
+FileChooser.newFolderToolTipText=Neuen Ordner erstellen
+FileChooser.newFolderAccessibleName=Neuer Ordner
+FileChooser.newFolderActionLabelText=Neuer Ordner
+FileChooser.listViewButtonToolTipText=Liste
+FileChooser.listViewButtonAccessibleName=Liste
+FileChooser.listViewActionLabelText=Liste
+FileChooser.detailsViewButtonToolTipText=Einzelheiten
+FileChooser.detailsViewButtonAccessibleName=Einzelheiten
+FileChooser.detailsViewActionLabelText=Einzelheiten
+FileChooser.refreshActionLabelText=Aktualisieren
+FileChooser.viewMenuLabelText=Ansicht
+FileChooser.fileNameHeaderText=Dateiname
+FileChooser.fileSizeHeaderText=Gr\u00f6\u00dfe
+FileChooser.fileTypeHeaderText=Typ
+FileChooser.fileDateHeaderText=Ge\u00e4ndert
+FileChooser.fileAttrHeaderText=Attribut
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_en.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_en.properties
new file mode 100644
index 0000000..2c34d80
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_en.properties
@@ -0,0 +1,230 @@
+
+ColorChooser.grayScaleSlider=Gray Scale Slider
+
+ColorChooser.rgbSliders=RGB Sliders
+
+ColorChooser.cmykSliders=CMYK Sliders
+
+ColorChooser.htmlSliders=HTML Sliders
+
+ColorChooser.rgbRedText=Red
+
+ColorChooser.rgbGreenText=Green
+
+ColorChooser.rgbBlueText=Blue
+
+ColorChooser.cmykCyanText=Cyan
+
+ColorChooser.cmykMagentaText=Magenta
+
+ColorChooser.cmykYellowText=Yellow
+
+ColorChooser.cmykBlackText=Black
+
+ColorChooser.hsbHueText=Hue
+
+ColorChooser.hsbSaturationText=Saturation
+
+ColorChooser.hsbBrightnessText=Brightness
+
+ColorChooser.htmlText=HTML\:
+
+ColorChooser.htmlChooseOnlyWebSaveColorsText=Choose only web-safe colors
+
+ColorChooser.colorSliders=Color Sliders
+
+ColorChooser.colorSwatches=Color Swatches
+
+ColorChooser.hsbSliders=HSB Sliders
+
+ColorChooser.colorWheel=Color Wheel
+
+ColorChooser.crayon.800000=Cayenne
+ColorChooser.crayon.808000=Asparagus
+ColorChooser.crayon.008000=Clover
+ColorChooser.crayon.008080=Teal
+ColorChooser.crayon.000080=Midnight
+ColorChooser.crayon.800080=Plum
+ColorChooser.crayon.7f7f7f=Tin
+ColorChooser.crayon.808080=Nickel
+
+ColorChooser.crayon.804000=Mocha
+ColorChooser.crayon.408000=Fern
+ColorChooser.crayon.008040=Moss
+ColorChooser.crayon.004080=Ocean
+ColorChooser.crayon.400080=Eggplant
+ColorChooser.crayon.800040=Maroon
+ColorChooser.crayon.666666=Steel
+ColorChooser.crayon.999999=Aluminium
+
+ColorChooser.crayon.ff0000=Maraschino
+ColorChooser.crayon.ffff00=Lemon
+ColorChooser.crayon.00ff00=Spring
+ColorChooser.crayon.00ffff=Turquoise
+ColorChooser.crayon.0000ff=Blueberry
+ColorChooser.crayon.ff00ff=Magenta
+ColorChooser.crayon.4c4c4c=Iron
+ColorChooser.crayon.b3b3b3=Magnesium
+
+ColorChooser.crayon.ff8000=Tangerine
+ColorChooser.crayon.80ff00=Lime
+ColorChooser.crayon.00ff80=Sea Foam
+ColorChooser.crayon.0080ff=Aqua
+ColorChooser.crayon.8000ff=Grape
+ColorChooser.crayon.ff0080=Strawberry
+ColorChooser.crayon.333333=Tungsten
+ColorChooser.crayon.cccccc=Silver
+
+ColorChooser.crayon.ff6666=Salmon
+ColorChooser.crayon.ffff66=Banana
+ColorChooser.crayon.66ff66=Flora
+ColorChooser.crayon.66ffff=Ice
+ColorChooser.crayon.6666ff=Orchid
+ColorChooser.crayon.ff66ff=Bubblegum
+ColorChooser.crayon.191919=Lead
+ColorChooser.crayon.e6e6e6=Mercury
+
+ColorChooser.crayon.ffcc66=Cantaloupe
+ColorChooser.crayon.ccff66=Honeydew
+ColorChooser.crayon.66ffcc=Spindrift
+ColorChooser.crayon.66ccff=Sky
+ColorChooser.crayon.cc66ff=Lavender
+ColorChooser.crayon.ff6fcf=Carnation
+ColorChooser.crayon.000000=Licorice
+ColorChooser.crayon.ffffff=Snow
+
+ColorChooser.crayons=Crayons
+
+ColorChooser.apple.000000=Black
+
+ColorChooser.apple.0000ff=Blue
+
+ColorChooser.apple.996633=Brown
+
+ColorChooser.apple.00ffff=Cyan
+
+ColorChooser.apple.00ff00=Green
+
+ColorChooser.apple.ff00ff=Magenta
+
+ColorChooser.apple.ff8000=Orange
+
+ColorChooser.apple.800080=Purple
+
+ColorChooser.apple.ff0000=Red
+
+ColorChooser.apple.ffff00=Yellow
+
+ColorChooser.apple.ffffff=White
+
+ColorChooser.appleColors=Apple
+
+ColorChooser.windowsBasicColors=Windows Basic Colors
+
+ColorChooser.color=Color
+
+ColorChooser.list=List\:
+
+ColorChooser.colorPalettes=Color Palettes
+
+ColorChooser.webSafeColors=Web Safe Colors
+
+ColorChooser.profileContainsNColors=The profile "{0}" contains {1} colors.
+
+OptionPane.cancel=Cancel
+
+OptionPane.css=<head><style type\="text/css">b { font\: 13pt \\"Lucida Grande\\" } p { font\: 11pt \\"Lucida Grande\\"; margin-top\: 8px }</style></head>
+
+ColorChooser.colorPicker=Color Picker
+
+TextComponent.cut=Cut
+
+TextComponent.copy=Copy
+
+TextComponent.paste=Paste
+
+FileChooser.fromLabelText=From\:
+
+FileChooser.widget=Widget
+
+FileChooser.widgetCount={0,choice,1\#{0,number}\u00A0folder|1<{0,number}\u00A0widgets}
+
+FileChooser.replaceAlert=<b>\u201C{0}\u201D already exists. Do you want to replace it?</b><p>A file or folder with the same name already exists in {1}. Replacing it will overwrite its current contents.
+#Replace
+FileChooser.replace=Replace
+
+FileChooser.computerName=Computer
+
+FileChooser.cancelButtonText=Cancel
+
+FileChooser.newFolderButtonText=New Folder
+
+FileChooser.openButtonText=Open
+
+
+FileChooser.size=Size
+
+FileChooser.created=Created
+
+FileChooser.modified=Modified
+
+FileChooser.sizeBytes={1,choice,0#Zero bytes|1#1 byte|1<{1,number} bytes}{2,choice,1#|1< for {2,number} items}
+
+FileChooser.name=Name
+
+FileChooser.whereLabelText=Where
+
+FileChooser.original=Original
+
+FileChooser.sizeKBytes={0,number,#,##0.#} KB\n({1,number} bytes){2,choice,1#|1< for {2,number} items}
+
+FileChooser.sizeMBytes={0,number,#,##0.#} MB\n({1,number} bytes){2,choice,1#|1< for {2,number} items}
+
+FileChooser.sizeGBytes={0,number,#,##0.#} GB\n({1,number} bytes){2,choice,1#|1< for {2,number} items}
+
+FileChooser.items={0} items
+
+FileChooser.sizeUnknown=--
+
+FileChooser.alias=Alias
+
+FileChooser.document=Document
+
+FileChooser.folder=Folder
+
+FileChooser.application=Application
+
+FileChooser.aliasCount={0,choice,1#{0,number}\u00a0alias|1<{0,number}\u00a0aliases}
+
+FileChooser.documentCount={0,choice,1#{0,number}\u00a0document|1<{0,number}\u00a0documents}
+
+FileChooser.folderCount={0,choice,1#{0,number}\u00a0folder|1<{0,number}\u00a0folders}
+
+FileChooser.applicationCount={0,choice,1#{0,number}\u00a0application|1<{0,number}\u00a0applications}
+
+FileChooser.kind=Kind
+
+FileChooser.lookInLabelText=Look In:
+FileChooser.saveInLabelText=Save In:
+FileChooser.fileNameLabelText=File Name:
+FileChooser.filesOfTypeLabelText=Files of Type:
+FileChooser.upFolderToolTipText=Up One Level
+FileChooser.upFolderAccessibleName=Up
+FileChooser.homeFolderToolTipText=Home
+FileChooser.homeFolderAccessibleName=Home
+FileChooser.newFolderToolTipText=Create New Folder
+FileChooser.newFolderAccessibleName=New Folder
+FileChooser.newFolderActionLabelText=New Folder
+FileChooser.listViewButtonToolTipText=List
+FileChooser.listViewButtonAccessibleName=List
+FileChooser.listViewActionLabelText=List
+FileChooser.detailsViewButtonToolTipText=Details
+FileChooser.detailsViewButtonAccessibleName=Details
+FileChooser.detailsViewActionLabelText=Details
+FileChooser.refreshActionLabelText=Refresh
+FileChooser.viewMenuLabelText=View
+FileChooser.fileNameHeaderText=Name
+FileChooser.fileSizeHeaderText=Size
+FileChooser.fileTypeHeaderText=Type
+FileChooser.fileDateHeaderText=Modified
+FileChooser.fileAttrHeaderText=Attributes
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_fr.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_fr.properties
new file mode 100644
index 0000000..50cd8f7
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_fr.properties
@@ -0,0 +1,274 @@
+
+FileChooser.size=Taille
+
+FileChooser.created=Cr\u00E9\u00E9 le
+
+FileChooser.modified=Modifi\u00E9 le
+
+FileChooser.name=Nom
+
+FileChooser.kind=Type
+
+FileChooser.whereLabelText=Chemin
+
+FileChooser.original=Original
+
+FileChooser.sizeBytes={1,choice,0#Z�ro octets|1#1 octet|1<{1,number} octets}{2,choice,1#|1< pour {2,number} �l�ments}
+
+FileChooser.sizeKBytes={0,number,#,##0.#} Ko\n({1,number} octets){2,choice,1#|1< pour {2,number} �l�ments}
+
+FileChooser.sizeMBytes={0,number,\#,\#\#0.\#} Mo ({1,number} octets){2,choice,1\#|1< pour {2,number} \u00E9l\u00E9ments}
+
+FileChooser.sizeGBytes={0,number,#,##0.#} Go\n({1,number} octets){2,choice,1#|1< pour {2,number} �l�ments}
+
+FileChooser.items={0} �l�ments
+
+FileChooser.sizeUnknown=--
+
+FileChooser.alias=Alias
+
+FileChooser.document=Document
+
+FileChooser.folder=Dossier
+
+FileChooser.application=Application
+
+FileChooser.aliasCount={0,choice,1#{0,number}\u00a0alias|1<{0,number}\u00a0alias}
+
+FileChooser.documentCount={0,choice,1#{0,number}\u00a0document|1<{0,number}\u00a0documents}
+
+FileChooser.folderCount={0,choice,1#{0,number}\u00a0dossier|1<{0,number}\u00a0dossiers}
+
+FileChooser.applicationCount={0,choice,1#{0,number}\u00a0application|1<{0,number}\u00a0applications}
+
+
+ColorChooser.grayScaleSlider=Curseur d'\u00E9chelle de gris
+
+ColorChooser.rgbSliders=Curseurs RVB
+
+ColorChooser.cmykSliders=Curseurs CMJN
+
+ColorChooser.htmlSliders=Curseurs HTML
+
+ColorChooser.rgbRedText=Rouge
+
+ColorChooser.rgbGreenText=Vert
+
+ColorChooser.rgbBlueText=Bleu
+
+ColorChooser.cmykCyanText=Cyan
+
+ColorChooser.cmykMagentaText=Magenta
+
+ColorChooser.cmykYellowText=Jaune
+
+ColorChooser.cmykBlackText=Noir
+
+ColorChooser.hsbHueText=Teinte
+
+ColorChooser.hsbSaturationText=Saturation
+
+ColorChooser.hsbBrightnessText=Luminosit\u00E9
+
+ColorChooser.htmlText=HTML\:
+
+ColorChooser.htmlChooseOnlyWebSaveColorsText=Choisir que les couleurs "Web-save"
+
+ColorChooser.colorSliders=Curseurs de couleur
+
+ColorChooser.colorSwatches=Faces de couleur
+
+ColorChooser.hsbSliders=Curseurs TSL
+
+FileChooser.fromLabelText=Location\:
+
+FileChooser.widget=Widget
+
+FileChooser.widgetCount={0,choice,1\#{0,number}\u00A0folder|1<{0,number}\u00A0widgets}
+
+ColorChooser.colorWheel=Roue des couleurs
+
+ColorChooser.crayon.000000=R\u00E9glisse
+
+ColorChooser.crayon.000080=Minuit
+
+ColorChooser.crayon.004080=Oc\u00E9an
+
+ColorChooser.crayon.008040=Mousse
+
+ColorChooser.crayon.008000=Tr\u00E8fle
+
+ColorChooser.crayon.0000ff=Myrtille
+
+ColorChooser.crayon.0080ff=Aqua
+
+ColorChooser.crayon.00ff00=Printemps
+
+ColorChooser.crayon.00ffff=Turquoise
+
+ColorChooser.crayon.808000=Asperge
+
+ColorChooser.crayon.008080=Sarcelle
+
+ColorChooser.crayon.800080=Prune
+
+ColorChooser.crayon.7f7f7f=\u00C9tain
+
+ColorChooser.crayon.808080=Nickel
+
+ColorChooser.crayon.804000=Moka
+
+ColorChooser.crayon.408000=Foug\u00E8re
+
+ColorChooser.crayon.400080=Aubergine
+
+ColorChooser.crayon.800040=Bordeaux
+
+ColorChooser.crayon.666666=Acier
+
+ColorChooser.crayon.999999=Aluminium
+
+ColorChooser.crayon.ff0000=Marasquin
+
+ColorChooser.crayon.ffff00=Citron
+
+ColorChooser.crayon.ffffff=Neige
+
+ColorChooser.crayon.ff00ff=Magenta
+
+ColorChooser.crayon.4c4c4c=Fer
+
+ColorChooser.crayon.b3b3b3=Magn\u00E9sium
+
+ColorChooser.crayon.ff8000=Mandarine
+
+ColorChooser.crayon.80ff00=Citron vert
+
+ColorChooser.crayon.00ff80=\u00C9cume
+
+ColorChooser.crayon.8000ff=Raisin
+
+ColorChooser.crayon.ff0080=Fraise
+
+ColorChooser.crayon.333333=Tungst\u00E8ne
+
+ColorChooser.crayon.cccccc=Argent
+
+ColorChooser.crayon.ff6666=Saumon
+
+ColorChooser.crayon.ffff66=Banane
+
+ColorChooser.crayon.66ff66=Flore
+
+ColorChooser.crayon.66ffff=Glace
+
+ColorChooser.crayon.6666ff=Orchid\u00E9e
+
+ColorChooser.crayon.ff66ff=Chewing gum
+
+ColorChooser.crayon.191919=Plomb
+
+ColorChooser.crayon.e6e6e6=Mercure
+
+ColorChooser.crayon.ffcc66=Cantaloup
+
+ColorChooser.crayon.ccff66=Miell\u00E9e
+
+ColorChooser.crayon.66ffcc=Embruns
+
+ColorChooser.crayon.66ccff=Ciel
+
+ColorChooser.crayon.cc66ff=Lavande
+
+ColorChooser.crayon.ff6fcf=\u0152illet
+
+ColorChooser.crayon.800000=Cayenne
+
+ColorChooser.crayons=Crayons
+
+ColorChooser.apple.000000=Noir
+
+ColorChooser.apple.0000ff=Bleu
+
+ColorChooser.apple.996633=Brun
+
+ColorChooser.apple.00ffff=Cyan
+
+ColorChooser.apple.00ff00=Vert
+
+ColorChooser.apple.ff00ff=Magenta
+
+ColorChooser.apple.ff8000=Orange
+
+ColorChooser.apple.800080=Violet
+
+ColorChooser.apple.ff0000=Rouge
+
+ColorChooser.apple.ffff00=Jaune
+
+ColorChooser.apple.ffffff=Blanc
+
+ColorChooser.appleColors=Apple
+
+ColorChooser.windowsBasicColors=Windows couleurs de base
+
+ColorChooser.color=Couleur
+
+ColorChooser.list=Liste\:
+
+ColorChooser.colorPalettes=Palettes de couleurs
+
+ColorChooser.webSafeColors=Web Safe Colors
+
+ColorChooser.profileContainsNColors=Le profile "{0}" contient {1} couleurs.
+
+FileChooser.replaceAlert=<b>\u201C{0}\u201D existe d\u00E9j\u00E0. Souhaitez-vous le remplacer?</b><p>Un fichier ou dossier portant le m\u00EAme nom existe d\u00E9j\u00E0 dans {1}. Le remplacer aura pour effet d'\u00E9craser son contenu actuel.
+
+#Replace
+FileChooser.replace=Remplacer
+
+OptionPane.cancel=Annuler
+
+OptionPane.css=<head><style type\="text/css">b { font\: 13pt \\"Lucida Grande\\" } p { font\: 11pt \\"Lucida Grande\\"; margin-top\: 8px }</style></head>
+
+ColorChooser.colorPicker=Color Picker
+
+TextComponent.cut=Couper
+
+TextComponent.copy=Copier
+
+TextComponent.paste=Coller
+
+FileChooser.computerName=Ordinateur
+
+FileChooser.openButtonText=Ouvrir
+
+FileChooser.newFolderButtonText=Nouveau dossier
+
+FileChooser.cancelButtonText=Annuler
+
+FileChooser.lookInLabelText=Rechercher dans :
+FileChooser.saveInLabelText=Enregistrer dans :
+FileChooser.fileNameLabelText=Nom de fichier :
+FileChooser.filesOfTypeLabelText=Fichiers du type :
+FileChooser.upFolderToolTipText=Remonte d'un niveau.
+FileChooser.upFolderAccessibleName=Vers le haut
+FileChooser.homeFolderToolTipText=R\u00e9pertoire d'accueil
+FileChooser.homeFolderAccessibleName=Accueil
+FileChooser.newFolderToolTipText=Cr\u00e9e un nouveau dossier.
+FileChooser.newFolderAccessibleName=Nouveau dossier
+FileChooser.newFolderActionLabelText=Nouveau dossier
+FileChooser.listViewButtonToolTipText=Liste
+FileChooser.listViewButtonAccessibleName=Liste
+FileChooser.listViewActionLabelText=Liste
+FileChooser.detailsViewButtonToolTipText=D\u00e9tails
+FileChooser.detailsViewButtonAccessibleName=D\u00e9tails
+FileChooser.detailsViewActionLabelText=D\u00e9tails
+FileChooser.refreshActionLabelText=Actualiser
+FileChooser.viewMenuLabelText=Affichage
+FileChooser.fileNameHeaderText=Nom
+FileChooser.fileSizeHeaderText=Taille
+FileChooser.fileTypeHeaderText=Type
+FileChooser.fileDateHeaderText=Modifi\u00e9
+FileChooser.fileAttrHeaderText=Attributs
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_it.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_it.properties
new file mode 100644
index 0000000..1ddf9d8
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_it.properties
@@ -0,0 +1,270 @@
+
+FileChooser.size=Dimens.
+
+FileChooser.created=Creato
+
+FileChooser.modified=Modificato
+
+FileChooser.name=Nome
+
+FileChooser.whereLabelText=Situato
+
+FileChooser.original=Originale
+
+FileChooser.sizeBytes={1,choice,0\#Zero byte|1\#1 byte|1<{1,number} byte}{2,choice,1\#|1< per {2,number} elementi}
+
+FileChooser.sizeKBytes={0,number,\#,\#\#0.\#} KB ({1,number} byte){2,choice,1\#|1< per {2,number} elementi}
+
+FileChooser.sizeMBytes={0,number,\#,\#\#0.\#} MB ({1,number} byte){2,choice,1\#|1< per {2,number} elementi}
+
+FileChooser.sizeGBytes={0,number,\#,\#\#0.\#} GB ({1,number} byte){2,choice,1\#|1< per {2,number} elementi}
+
+FileChooser.items={0} elementi
+
+FileChooser.sizeUnknown=--
+
+FileChooser.alias=Alias
+
+FileChooser.document=Documento
+
+FileChooser.folder=Cartella
+
+FileChooser.application=Applicazione
+
+FileChooser.aliasCount={0,choice,1#{0,number}\u00a0alias|1<{0,number}\u00a0alias}
+
+FileChooser.documentCount={0,choice,1#{0,number}\u00a0documento|1<{0,number}\u00a0documenti}
+
+FileChooser.folderCount={0,choice,1#{0,number}\u00a0cartella|1<{0,number}\u00a0cartelle}
+
+FileChooser.applicationCount={0,choice,1#{0,number}\u00a0applicazione|1<{0,number}\u00a0applicazioni}
+
+FileChooser.kind=Tipo
+
+ColorChooser.grayScaleSlider=Cursore Scala di grigi
+
+ColorChooser.rgbSliders=Cursori RGB
+
+ColorChooser.cmykSliders=Cursori CMYK
+
+ColorChooser.htmlSliders=Cursori HTML
+
+ColorChooser.rgbRedText=Rosso
+
+ColorChooser.rgbGreenText=Verde
+
+ColorChooser.rgbBlueText=Blu
+
+ColorChooser.cmykCyanText=Ciano
+
+ColorChooser.cmykMagentaText=Magenta
+
+ColorChooser.cmykYellowText=Giallo
+
+ColorChooser.cmykBlackText=Nero
+
+ColorChooser.hsbHueText=Tonalit\u00E0
+
+ColorChooser.hsbSaturationText=Saturazione
+
+ColorChooser.hsbBrightnessText=Luminosit\u00E0
+
+ColorChooser.htmlText=HTML\:
+
+ColorChooser.htmlChooseOnlyWebSaveColorsText=Solamente scellere i colori "Web-save"
+
+ColorChooser.colorSliders=Cursori Colore
+
+ColorChooser.colorSwatches=Face Colori
+
+ColorChooser.hsbSliders=Cursori HSB
+
+FileChooser.fromLabelText=Situato\:
+
+FileChooser.widget=Widget
+
+FileChooser.widgetCount={0,choice,1\#{0,number}\u00A0folder|1<{0,number}\u00A0widgets}
+
+ColorChooser.colorWheel=Ruota Colori
+
+ColorChooser.crayon.66ccff=Cielo
+
+ColorChooser.crayon.ffffff=Neve
+
+ColorChooser.crayon.ffff66=Banana
+
+ColorChooser.crayon.ffff00=Limone
+
+ColorChooser.crayon.ffcc66=Melone
+
+ColorChooser.crayon.ff8000=Mandarancio
+
+ColorChooser.crayon.ff6fcf=Garofano
+
+ColorChooser.crayon.ff66ff=Bubblegum
+
+ColorChooser.crayon.ff6666=Salmone
+
+ColorChooser.crayon.ff00ff=Magenta
+
+ColorChooser.crayon.ff0080=Fragola
+
+ColorChooser.crayon.ff0000=Maraschino
+
+ColorChooser.crayon.e6e6e6=Mercurio
+
+ColorChooser.crayon.ccff66=Miele
+
+ColorChooser.crayon.cccccc=Argento
+
+ColorChooser.crayon.cc66ff=Lavanda
+
+ColorChooser.crayon.b3b3b3=Magnesio
+
+ColorChooser.crayon.999999=Alluminio
+
+ColorChooser.crayon.80ff00=Lima
+
+ColorChooser.crayon.808080=Nichel
+
+ColorChooser.crayon.808000=Asparago
+
+ColorChooser.crayon.804000=Moka
+
+ColorChooser.crayon.8000ff=Uva
+
+ColorChooser.crayon.800080=Prugna
+
+ColorChooser.crayon.800000=Cayenne
+
+ColorChooser.crayon.800040=Granata
+
+ColorChooser.crayon.7f7f7f=Stagno
+
+ColorChooser.crayon.66ffff=Ghiaccio
+
+ColorChooser.crayon.66ffcc=Verde acqua
+
+ColorChooser.crayon.66ff66=Flora
+
+ColorChooser.crayon.0080ff=Aqua
+
+ColorChooser.crayon.6666ff=Orchidea
+
+ColorChooser.crayon.666666=Accaio
+
+ColorChooser.crayon.4c4c4c=Ferro
+
+ColorChooser.crayon.408000=Felce
+
+ColorChooser.crayon.400080=Melanzana
+
+ColorChooser.crayon.333333=Tungsteno
+
+ColorChooser.crayon.191919=Grafite
+
+ColorChooser.crayon.00ffff=Turchese
+
+ColorChooser.crayon.00ff80=Schiuma marina
+
+ColorChooser.crayon.00ff00=Primavera
+
+ColorChooser.crayon.008080=Blu petrolio
+
+ColorChooser.crayon.008040=Muschio
+
+ColorChooser.crayon.008000=Trifoglio
+
+ColorChooser.crayon.004080=Oceano
+
+ColorChooser.crayon.0000ff=Mirtillo
+
+ColorChooser.crayon.000080=Blu notte
+
+ColorChooser.crayon.000000=Liquirizia
+
+ColorChooser.crayons=Pastelli
+
+ColorChooser.apple.000000=Nero
+
+ColorChooser.apple.0000ff=Blu
+
+ColorChooser.apple.996633=Marrone
+
+ColorChooser.apple.00ffff=Ciano
+
+ColorChooser.apple.00ff00=Verde
+
+ColorChooser.apple.ff00ff=Magenta
+
+ColorChooser.apple.ff8000=Arancio
+
+ColorChooser.apple.800080=Viola
+
+ColorChooser.apple.ff0000=Rosso
+
+ColorChooser.apple.ffff00=Giallo
+
+ColorChooser.apple.ffffff=Bianco
+
+ColorChooser.appleColors=Apple
+
+ColorChooser.windowsBasicColors=Windows Basic Colors
+
+ColorChooser.color=Colore
+
+ColorChooser.list=Elenco\:
+
+ColorChooser.colorPalettes=Tavolozze Colori
+
+ColorChooser.webSafeColors=Web Safe Colors
+
+ColorChooser.profileContainsNColors=Il profilo "$0" contiene $1 colori.
+
+FileChooser.replaceAlert=<b>\u201C{0}\u201D esiste gi\u00E0.<br>Vuoi sostituirlo?</b><p>\u00C8 gi\u00E0 presente un documento o una cartella con lo stesso nome in {1}. Sostituendolo verr\u00E0 sostituito il suo contenuto.
+FileChooser.replace=Sostituisci
+
+OptionPane.cancel=Annulla
+
+OptionPane.css=<head><style type\="text/css">b { font\: 13pt \\"Lucida Grande\\" } p { font\: 11pt \\"Lucida Grande\\"; margin-top\: 8px }</style></head>
+
+ColorChooser.colorPicker=Color Picker
+
+TextComponent.cut=Taglia
+
+TextComponent.copy=Copia
+
+TextComponent.paste=Incolla
+
+FileChooser.computerName=Calcolatore
+
+FileChooser.cancelButtonText=Annulla
+
+FileChooser.newFolderButtonText=Nuova Cartella
+
+FileChooser.openButtonText=Apri
+
+FileChooser.lookInLabelText=Cerca in:
+FileChooser.saveInLabelText=Salva in:
+FileChooser.fileNameLabelText=Nome file:
+FileChooser.filesOfTypeLabelText=Tipo file:
+FileChooser.upFolderToolTipText=Cartella superiore
+FileChooser.upFolderAccessibleName=Superiore
+FileChooser.homeFolderToolTipText=Principale
+FileChooser.homeFolderAccessibleName=Principale
+FileChooser.newFolderToolTipText=Crea nuova cartella
+FileChooser.newFolderAccessibleName=Nuova cartella
+FileChooser.newFolderActionLabelText=Nuova cartella
+FileChooser.listViewButtonToolTipText=Elenco
+FileChooser.listViewButtonAccessibleName=Elenco
+FileChooser.listViewActionLabelText=Elenco
+FileChooser.detailsViewButtonToolTipText=Dettagli
+FileChooser.detailsViewButtonAccessibleName=Dettagli
+FileChooser.detailsViewActionLabelText=Dettagli
+FileChooser.refreshActionLabelText=Aggiorna
+FileChooser.viewMenuLabelText=Visualizza
+FileChooser.fileNameHeaderText=Nome
+FileChooser.fileSizeHeaderText=Dimensioni
+FileChooser.fileTypeHeaderText=Tipo
+FileChooser.fileDateHeaderText=Modificato
+FileChooser.fileAttrHeaderText=Attributi
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_zh_CN.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_zh_CN.properties
new file mode 100644
index 0000000..9c8a753
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/Labels_zh_CN.properties
@@ -0,0 +1,245 @@
+#Chinese translation by Qil Wong
+
+FileChooser.size=\u5927\u5c0f
+
+FileChooser.created=\u521b\u5efa
+
+FileChooser.modified=\u4fee\u6539
+
+FileChooser.sizeBytes={1,choice,0#Zero bytes|1#1 byte|1<{1,number} bytes}{2,choice,1#|1< for {2,number} items}
+
+FileChooser.name=\u540d\u79f0
+
+FileChooser.whereLabelText=\u6765\u81ea
+
+FileChooser.original=\u8d77\u59cb
+
+FileChooser.sizeKBytes={0,number,#,##0.#} KB\n({1,number} bytes){2,choice,1#|1< for {2,number} items}
+
+FileChooser.sizeMBytes={0,number,#,##0.#} MB\n({1,number} bytes){2,choice,1#|1< for {2,number} items}
+
+FileChooser.sizeGBytes={0,number,#,##0.#} GB\n({1,number} bytes){2,choice,1#|1< for {2,number} items}
+
+FileChooser.items={0} items
+
+FileChooser.sizeUnknown=--
+
+FileChooser.alias=\u522b\u540d
+
+FileChooser.document=\u6587\u6863
+
+FileChooser.folder=\u6587\u4ef6\u5939
+
+FileChooser.upFolderToolTipText=\u5411\u4e0a
+
+FileChooser.desktopFolderToolTipText=\u684c\u9762
+
+FileChooser.newFolderToolTipText=\u65b0\u6587\u4ef6\u5939
+
+FileChooser.homeFolderToolTipText=\u684c\u9762
+
+FileChooser.listViewButtonToolTipText=\u5217\u8868\u89c6\u56fe
+
+FileChooser.detailsViewButtonToolTipText=\u8be6\u7ec6\u89c6\u56fe
+
+FileChooser.application=\u5e94\u7528\u7a0b\u5e8f
+
+FileChooser.aliasCount={0,choice,1#{0,number}\u00a0alias|1<{0,number}\u00a0aliases}
+
+FileChooser.documentCount={0,choice,1#{0,number}\u00a0document|1<{0,number}\u00a0documents}
+
+FileChooser.folderCount={0,choice,1#{0,number}\u00a0folder|1<{0,number}\u00a0folders}
+
+FileChooser.applicationCount={0,choice,1#{0,number}\u00a0application|1<{0,number}\u00a0applications}
+
+FileChooser.kind=\u7c7b\u522b
+
+ColorChooser.grayScaleSlider=\u7070\u5ea6\u6ed1\u5757
+
+ColorChooser.rgbSliders=RGB\u6ed1\u5757
+
+ColorChooser.cmykSliders=CMYK\u6ed1\u5757
+
+ColorChooser.htmlSliders=HTML\u6ed1\u5757
+
+ColorChooser.rgbRedText=\u7ea2\u8272
+
+ColorChooser.rgbGreenText=\u7eff\u8272
+
+ColorChooser.rgbBlueText=\u84dd\u8272
+
+ColorChooser.cmykCyanText=\u9752\u8272
+
+ColorChooser.cmykMagentaText=\u7d2b\u7ea2\u8272
+
+ColorChooser.cmykYellowText=\u9ec4\u8272
+
+ColorChooser.cmykBlackText=\u9ed1\u8272
+
+ColorChooser.hsbHueText=\u8272\u8c03
+
+ColorChooser.hsbSaturationText=\u9971\u548c\u5ea6
+
+ColorChooser.hsbBrightnessText=\u4eae\u5ea6
+
+ColorChooser.htmlText=HTML\:
+
+ColorChooser.htmlChooseOnlyWebSaveColorsText=\u9009\u62e9\u7f51\u7edc\u5b89\u5168\u989c\u8272
+
+ColorChooser.colorSliders=\u989c\u8272\u6ed1\u5757
+
+ColorChooser.colorSwatches=\u989c\u8272\u6837\u672c
+
+ColorChooser.hsbSliders=HSB\u6ed1\u5757
+
+
+
+FileChooser.fromLabelText=\u6765\u81ea\:
+
+FileChooser.newFolderButtonText=\u65b0\u6587\u4ef6\u5939
+
+
+FileChooser.cancelButtonText=\u53d6\u6d88
+
+FileChooser.openButtonText=\u6253\u5f00
+
+FileChooser.widget=\u56fe\u6807
+
+FileChooser.widgetCount={0,choice,1\#{0,number}\u00A0folder|1<{0,number}\u00A0widgets}
+
+ColorChooser.colorWheel=\u989c\u8272\u6eda\u8f6e
+
+ColorChooser.crayon.800000=Cayenne
+ColorChooser.crayon.808000=Asparagus
+ColorChooser.crayon.008000=Clover
+ColorChooser.crayon.008080=Teal
+ColorChooser.crayon.000080=Midnight
+ColorChooser.crayon.800080=Plum
+ColorChooser.crayon.7f7f7f=Tin
+ColorChooser.crayon.808080=Nickel
+
+ColorChooser.crayon.804000=Mocha
+ColorChooser.crayon.408000=Fern
+ColorChooser.crayon.008040=Moss
+ColorChooser.crayon.004080=Ocean
+ColorChooser.crayon.400080=Eggplant
+ColorChooser.crayon.800040=Maroon
+ColorChooser.crayon.666666=Steel
+ColorChooser.crayon.999999=Aluminium
+
+ColorChooser.crayon.ff0000=Maraschino
+ColorChooser.crayon.ffff00=Lemon
+ColorChooser.crayon.00ff00=Spring
+ColorChooser.crayon.00ffff=Turquoise
+ColorChooser.crayon.0000ff=Blueberry
+ColorChooser.crayon.ff00ff=Magenta
+ColorChooser.crayon.4c4c4c=Iron
+ColorChooser.crayon.b3b3b3=Magnesium
+
+ColorChooser.crayon.ff8000=Tangerine
+ColorChooser.crayon.80ff00=Lime
+ColorChooser.crayon.00ff80=Sea Foam
+ColorChooser.crayon.0080ff=Aqua
+ColorChooser.crayon.8000ff=Grape
+ColorChooser.crayon.ff0080=Strawberry
+ColorChooser.crayon.333333=Tungsten
+ColorChooser.crayon.cccccc=Silver
+
+ColorChooser.crayon.ff6666=Salmon
+ColorChooser.crayon.ffff66=Banana
+ColorChooser.crayon.66ff66=Flora
+ColorChooser.crayon.66ffff=Ice
+ColorChooser.crayon.6666ff=Orchid
+ColorChooser.crayon.ff66ff=Bubblegum
+ColorChooser.crayon.191919=Lead
+ColorChooser.crayon.e6e6e6=Mercury
+
+ColorChooser.crayon.ffcc66=Cantaloupe
+ColorChooser.crayon.ccff66=Honeydew
+ColorChooser.crayon.66ffcc=Spindrift
+ColorChooser.crayon.66ccff=Sky
+ColorChooser.crayon.cc66ff=Lavender
+ColorChooser.crayon.ff6fcf=Carnation
+ColorChooser.crayon.000000=Licorice
+ColorChooser.crayon.ffffff=Snow
+
+ColorChooser.crayons=\u6709\u8272\u7b14
+
+ColorChooser.color=\u989c\u8272
+
+ColorChooser.list=\u5217\u8868\:
+
+ColorChooser.apple.000000=\u9ed1\u8272
+
+ColorChooser.apple.0000ff=\u84dd\u8272
+
+ColorChooser.apple.996633=\u8910\u8272
+
+ColorChooser.apple.00ffff=\u9752\u8272
+
+ColorChooser.apple.00ff00=\u7eff\u8272
+
+ColorChooser.apple.ff00ff=\u7d2b\u7ea2\u8272
+
+ColorChooser.apple.ff8000=\u6a59\u8272
+
+ColorChooser.apple.800080=\u7d2b\u8272
+
+ColorChooser.apple.ff0000=\u7ea2\u8272
+
+ColorChooser.apple.ffff00=\u9ec4\u8272
+
+ColorChooser.apple.ffffff=\u767d\u8272
+
+ColorChooser.appleColors=Apple\u8272
+
+ColorChooser.windowsBasicColors=Windows\u57fa\u672c\u989c\u8272
+
+ColorChooser.colorPalettes=\u8c03\u8272\u677f
+
+ColorChooser.webSafeColors=\u7f51\u7edc\u5b89\u5168\u8272
+
+ColorChooser.profileContainsNColors=The profile "{0}" contains {1} colors.
+
+FileChooser.replaceAlert=<b>\u201C{0}\u201D \u5df2\u7ecf\u5b58\u5728.<br>\u66ff\u6362\u5df2\u6709\u6587\u4ef6\u5417\uff1f</b><p>\u540c\u540d\u7684\u6587\u4ef6\u6216\u76ee\u5f55\u5df2\u7ecf\u5b58\u5728<br>{1}.<br>\u66ff\u6362\u64cd\u4f5c\u5c06\u8986\u76d6\u5f53\u524d\u5185\u5bb9.
+#Replace
+FileChooser.replace=\u66ff\u6362
+
+OptionPane.cancel=\u53d6\u6d88
+
+OptionPane.css=<head><style type\="text/css">b { font\: 13pt \\"Lucida Grande\\" } p { font\: 11pt \\"Lucida Grande\\"; margin-top\: 8px }</style></head>
+
+ColorChooser.colorPicker=\u989c\u8272\u9009\u62e9\u5668
+
+TextComponent.cut=\u526a\u5207
+
+TextComponent.copy=\u590d\u5236
+
+TextComponent.paste=\u7c98\u8d34
+
+FileChooser.computerName=\u8ba1\u7b97\u673a
+
+FileChooser.lookInLabelText=\u67e5\u770b\uff1a
+FileChooser.saveInLabelText=\u4fdd\u5b58\uff1a
+FileChooser.fileNameLabelText=\u6587\u4ef6\u540d\uff1a
+FileChooser.filesOfTypeLabelText=\u6587\u4ef6\u7c7b\u578b\uff1a
+FileChooser.upFolderToolTipText=\u5411\u4e0a\u4e00\u5c42
+FileChooser.upFolderAccessibleName=\u5411\u4e0a
+FileChooser.homeFolderToolTipText=\u8d77\u59cb\u76ee\u5f55
+FileChooser.homeFolderAccessibleName=\u8d77\u59cb\u76ee\u5f55
+FileChooser.newFolderToolTipText=\u521b\u5efa\u65b0\u7684\u6587\u4ef6\u5939
+FileChooser.newFolderAccessibleName=\u65b0\u5efa\u6587\u4ef6\u5939
+FileChooser.newFolderActionLabelText=\u65b0\u5efa\u6587\u4ef6\u5939
+FileChooser.listViewButtonToolTipText=\u5217\u8868
+FileChooser.listViewButtonAccessibleName=\u5217\u8868
+FileChooser.listViewActionLabelText=\u5217\u8868
+FileChooser.detailsViewButtonToolTipText=\u8be6\u7ec6\u4fe1\u606f
+FileChooser.detailsViewButtonAccessibleName=\u8be6\u7ec6\u4fe1\u606f
+FileChooser.detailsViewActionLabelText=\u8be6\u7ec6\u4fe1\u606f
+FileChooser.refreshActionLabelText=\u5237\u65b0
+FileChooser.viewMenuLabelText=\u89c6\u56fe
+FileChooser.fileNameHeaderText=\u540d\u79f0
+FileChooser.fileSizeHeaderText=\u5927\u5c0f
+FileChooser.fileTypeHeaderText=\u7c7b\u578b
+FileChooser.fileDateHeaderText=\u4fee\u6b63\u7248
+FileChooser.fileAttrHeaderText=\u5c5e\u6027
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/big_crayons.png b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/big_crayons.png
new file mode 100644
index 0000000..a6dc82f
Binary files /dev/null and b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/big_crayons.png differ
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/chart_bar.png b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/chart_bar.png
new file mode 100644
index 0000000..b8e217b
Binary files /dev/null and b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/chart_bar.png differ
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/color_swatch.png b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/color_swatch.png
new file mode 100644
index 0000000..b930c7d
Binary files /dev/null and b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/color_swatch.png differ
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/color_wheel.png b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/color_wheel.png
new file mode 100644
index 0000000..b801372
Binary files /dev/null and b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/color_wheel.png differ
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/palette.png b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/palette.png
new file mode 100644
index 0000000..f89f6c8
Binary files /dev/null and b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/palette.png differ
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/pencil.png b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/pencil.png
new file mode 100644
index 0000000..b9bb8d0
Binary files /dev/null and b/substance/src/main/resources/org/pushingpixels/substance/internal/contrib/randelshofer/quaqua/images/pencil.png differ
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels.properties
new file mode 100644
index 0000000..13c39e3
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels.properties
@@ -0,0 +1,31 @@
+# Default labels by Kirill Grouchnikov
+
+SystemMenu.close=Close
+
+SystemMenu.iconify=Iconify
+
+SystemMenu.restore=Restore
+
+SystemMenu.maximize=Maximize
+
+SystemMenu.skins=Substance skins
+
+SystemMenu.showHeapStatus=Show heap status
+
+Tooltip.contentsNotSaved=contents not saved
+
+Tooltip.heapStatusPanel=Heap status. Click to run garbage collector
+
+Xoetrope.hue=Hue
+
+Xoetrope.brightness=Brightness
+
+Xoetrope.saturation=Saturation
+
+Xoetrope.warm=WARM
+
+Xoetrope.cold=COLD
+
+Xoetrope.decimalRGB=Decimal RGB
+
+Xoetrope.webSafeColors=Use web safe colors
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ar.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ar.properties
new file mode 100644
index 0000000..dec44c3
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ar.properties
@@ -0,0 +1,32 @@
+# Arabic translation by QamarAlZaman Habeek
+
+SystemMenu.close=\u0625\u063A\uFEFB\u0642
+
+SystemMenu.iconify=\u062A\u0635\u063A\u064A\u0631
+
+SystemMenu.restore=\u0627\u0633\u062A\u0631\u062C\u0627\u0639
+
+SystemMenu.maximize=\u062A\u0643\u0628\u064A\u0631
+
+SystemMenu.skins=\u0623\u064F\u0647\u064F\u0628 \u0627\u0644\u0633\u0628\u0633\u062A\u0646\u0633
+
+SystemMenu.showHeapStatus=\u0625\u0638\u0647\u0627\u0631 \u0648\u0636\u0639 \u0627\u0644\u0631\u0643\u0627\u0645
+
+Tooltip.contentsNotSaved=\u0627\u0644\u0645\u062d\u062a\u0648\u0649 \u063a\u064a\u0631 \u0645\u062d\u0641\u0648\u0638
+
+Tooltip.heapStatusPanel=\u0648\u0636\u0639 \u0627\u0644\u0631\u0643\u0627\u0645. \u0627\u0646\u0642\u0631 \u0644\u062a\u0646\u0641\u064a\u0630 \u062c\u0627\u0645\u0639 \u0627\u0644\u0646\u0641\u0627\u064a\u0627\u062a
+
+Xoetrope.hue=\u0635\u0650\u0628\u0652\u063a
+
+Xoetrope.brightness=\u0627\u0644\u0623\u0644\u0645\u0639\u064a\u0629
+
+Xoetrope.saturation=\u062a\u0634\u0628\u0639
+
+Xoetrope.warm=\u062f\u0627\u0641\u0626
+
+Xoetrope.cold=\u0628\u0627\u0631\u062f
+
+Xoetrope.decimalRGB=\u0627\u0644\u0631\u0645\u0632 \u0627\u0644\u0639\u062f\u062f\u064a \u0644\u0644\u0623\u0644\u0648\u0627\u0646
+
+Xoetrope.webSafeColors=\u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0627\u0644\u0623\u0644\u0648\u0627\u0646 \u0627\u0644\u0645\u0644\u0627\u0626\u0645\u0629 \u0644\u0644\u0634\u0628\u0643\u0629
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_bg.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_bg.properties
new file mode 100644
index 0000000..82d0ab6
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_bg.properties
@@ -0,0 +1,32 @@
+# Bulgarian translation by Zar Petkov
+
+SystemMenu.close=\u0417\u0430\u0442\u0432\u0430\u0440\u044f\u043d\u0435
+
+SystemMenu.iconify=\u041f\u043e\u0441\u0442\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0438\u043a\u043e\u043d\u0438
+
+SystemMenu.restore=\u0412\u044a\u0437\u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0432\u0430\u043d\u0435
+
+SystemMenu.maximize=\u041c\u0430\u043a\u0441\u0438\u043c\u0438\u0437\u0438\u0440\u0430\u043d\u0435
+
+SystemMenu.skins=\u0421\u0442\u0438\u043b\u043e\u0432\u0435 \u043d\u0430 Substance
+
+SystemMenu.showHeapStatus=\u0421\u0442\u0430\u0442\u0443\u0441 \u043D\u0430 \u0434\u0438\u043D\u0430\u043C\u0438\u0447\u043D\u0430\u0442\u0430 \u043F\u0430\u043C\u0435\u0442
+
+Tooltip.contentsNotSaved=\u0421\u044A\u0434\u044A\u0440\u0436\u0430\u043D\u0438\u0435\u0442\u043E \u043D\u0435 \u0435 \u0437\u0430\u043F\u0430\u0437\u0435\u043D\u043E \u0432\u044A\u0440\u0445\u0443 \u0434\u0438\u0441\u043A\u0430
+
+Tooltip.heapStatusPanel=\u0421\u0442\u0430\u0442\u0443\u0441 \u043D\u0430 \u0434\u0438\u043D\u0430\u043C\u0438\u0447\u043D\u0430\u0442\u0430 \u043F\u0430\u043C\u0435\u0442. \u041A\u043B\u0438\u043A\u043D\u0438 \u0437\u0430 \u0434\u0430 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430\u0448 garbage collector
+
+Xoetrope.hue=\u0422\u043E\u043D
+
+Xoetrope.brightness=\u041E\u0441\u0432\u0435\u0442\u0435\u043D\u043E\u0441\u0442
+
+Xoetrope.saturation=\u041D\u0430\u0441\u0438\u0442\u0435\u043D\u043E\u0441\u0442
+
+Xoetrope.warm=\u0421\u0422\u0423\u0414\u0415\u041D\u041E
+
+Xoetrope.cold=\u0422\u041E\u041F\u041B\u041E
+
+Xoetrope.decimalRGB=\u0414\u0435\u0441\u0435\u0442\u0438\u0447\u043D\u043E RGB
+
+Xoetrope.webSafeColors=\u0418\u0437\u043F\u043E\u043B\u0437\u0432\u0430\u0439 \u0446\u0432\u0435\u0442\u043E\u0432\u0435 \u043F\u043E\u0434\u0445\u043E\u0434\u044F\u0449\u0438 \u0437\u0430 \u0438\u043D\u0442\u0435\u0440\u043D\u0435\u0442 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430.
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_cs.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_cs.properties
new file mode 100644
index 0000000..a9cb49e
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_cs.properties
@@ -0,0 +1,32 @@
+# Czech translation by Milan Misak
+
+SystemMenu.close=Zav\u0159\u00EDt
+
+SystemMenu.iconify=Minimalizovat
+
+SystemMenu.restore=Obnovit
+
+SystemMenu.maximize=Maximalizovat
+
+SystemMenu.skins=Substance skiny
+
+SystemMenu.showHeapStatus=Uka\u017E heap stav
+
+Tooltip.contentsNotSaved=obsah nen\u00ED ulo\u017Een
+
+Tooltip.heapStatusPanel=Heap stav. Klikni pro spu\u0161t\u011Bn\u00ED garbage collectoru
+
+Xoetrope.hue=Zabarven\u00FD
+
+Xoetrope.brightness=Sv\u011Btlost
+
+Xoetrope.saturation=Nasycen\u00FD
+
+Xoetrope.warm=TEPL\u00C9
+
+Xoetrope.cold=CHLADN\u00C9
+
+Xoetrope.decimalRGB=Des\u00FDtkov\u00E9 RGB
+
+Xoetrope.webSafeColors=Pou\u017E\u00FD barvy bezpe\u010Dn\u00E9 pro web
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_da.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_da.properties
new file mode 100644
index 0000000..b973939
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_da.properties
@@ -0,0 +1,32 @@
+# Danish translation by Carsten O. Madsen
+
+SystemMenu.close=Luk
+
+SystemMenu.iconify=Ikoniser
+
+SystemMenu.restore=Genskab
+
+SystemMenu.maximize=Maksimer
+
+SystemMenu.skins=Substance skins
+
+SystemMenu.showHeapStatus=Vis heap status
+
+Tooltip.contentsNotSaved=indhold er ikke gemt
+
+Tooltip.heapStatusPanel=Heap status. Klick for at k\u00F8re garbage collectoren
+
+Xoetrope.hue=Farvetone
+
+Xoetrope.brightness=Lysintensitet
+
+Xoetrope.saturation=Farvem\u00E6tning
+
+Xoetrope.warm=VARM
+
+Xoetrope.cold=KOLD
+
+Xoetrope.decimalRGB=Decimal RGB
+
+Xoetrope.webSafeColors=Brug www sikre farver
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_de.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_de.properties
new file mode 100644
index 0000000..171ff3b
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_de.properties
@@ -0,0 +1,146 @@
+# German translation by Andreas Golchert
+
+SystemMenu.close=Schlie\u00DFen
+
+SystemMenu.iconify=Ikonifizieren
+
+SystemMenu.restore=Wiederherstellen
+
+SystemMenu.maximize=Maximieren
+
+SystemMenu.skins=Substance skins
+
+SystemMenu.showHeapStatus=Heap Status anzeigen
+
+Tooltip.contentsNotSaved=Inhalte wurden nicht gespeichert
+
+Tooltip.heapStatusPanel=Heap Status. Hier klicken, um den Garbage Collector zu starten
+
+Xoetrope.hue=Farbton
+
+Xoetrope.brightness=Helligkeit
+
+Xoetrope.saturation=S\u00E4ttigung
+
+Xoetrope.warm=WARM
+
+Xoetrope.cold=KALT
+
+Xoetrope.decimalRGB=Dezimal RGB
+
+Xoetrope.webSafeColors=Nur Webfarben anzeigen
+
+# File chooser strings from com.sun.swing.internal.plaf.basic.resources.basic
+FileChooser.acceptAllFileFilterText=Alle Dateien
+
+FileChooser.cancelButtonMnemonic=65
+
+FileChooser.cancelButtonText=Abbrechen
+
+FileChooser.cancelButtonToolTipText=Dialogfeld f\u00FCr Dateiauswahl abbrechen
+
+FileChooser.directoryDescriptionText=Verzeichnis
+
+FileChooser.directoryOpenButtonMnemonic=69
+
+FileChooser.directoryOpenButtonText=\u00D6ffnen
+
+FileChooser.directoryOpenButtonToolTipText=Markiertes Verzeichnis \u00F6ffnen
+
+FileChooser.fileDescriptionText=Allgemeine Datei
+
+FileChooser.fileSizeGigaBytes={0} GB
+
+FileChooser.fileSizeKiloBytes={0} KB
+
+FileChooser.fileSizeMegaBytes={0} MB
+
+FileChooser.helpButtonMnemonic=72
+
+FileChooser.helpButtonText=Hilfe
+
+FileChooser.helpButtonToolTipText=Hilfe f\u00FCr Dateiauswahl
+
+FileChooser.newFolderErrorSeparator=:
+
+FileChooser.newFolderErrorText=Fehler beim Erstellen eines neuen Ordners
+
+FileChooser.openButtonMnemonic=70
+
+FileChooser.openButtonText=\u00D6ffnen
+
+FileChooser.openButtonToolTipText=Ausgew\u00E4hlte Datei \u00F6ffnen
+
+FileChooser.openDialogTitleText=\u00D6ffnen
+
+FileChooser.other.newFolder=Neuer Ordner
+
+FileChooser.other.newFolder.subsequent=Neuer Ordner.{0}
+
+FileChooser.saveButtonMnemonic=83
+
+FileChooser.saveButtonText=Speichern
+
+FileChooser.saveButtonToolTipText=Ausgew\u00E4hlte Datei speichern
+
+FileChooser.saveDialogTitleText=Speichern
+
+FileChooser.updateButtonMnemonic=75
+
+FileChooser.updateButtonText=Aktualisieren
+
+FileChooser.updateButtonToolTipText=Verzeichnisliste aktualisieren
+
+FileChooser.win32.newFolder=Neuer Ordner
+
+FileChooser.win32.newFolder.subsequent=Neuer Ordner ({0})
+
+# File chooser strings from com.sun.swing.internal.plaf.metal.resources.metal
+FileChooser.detailsViewActionLabelText=Details
+
+FileChooser.detailsViewButtonAccessibleName=Details
+
+FileChooser.detailsViewButtonToolTipText=Details
+
+FileChooser.fileAttrHeaderText=Attribut
+
+FileChooser.fileDateHeaderText=Ge\u00E4ndert
+
+FileChooser.fileNameHeaderText=Dateiname
+
+FileChooser.fileNameLabelText=Dateiname:
+
+FileChooser.fileSizeHeaderText=Gr\u00F6\u00DFe
+
+FileChooser.fileTypeHeaderText=Typ
+
+FileChooser.filesOfTypeLabelText=Dateityp:
+
+FileChooser.homeFolderAccessibleName=Home
+
+FileChooser.homeFolderToolTipText=Home
+
+FileChooser.listViewActionLabelText=Liste
+
+FileChooser.listViewButtonAccessibleName=Liste
+
+FileChooser.listViewButtonToolTipText=Liste
+
+FileChooser.lookInLabelText=Suchen in:
+
+FileChooser.newFolderAccessibleName=Neuer Ordner
+
+FileChooser.newFolderActionLabelText=Neuer Ordner
+
+FileChooser.newFolderToolTipText=Neuen Ordner erstellen
+
+FileChooser.refreshActionLabelText=Aktualisieren
+
+FileChooser.saveInLabelText=Speichern in:
+
+FileChooser.upFolderAccessibleName=H\u00F6her
+
+FileChooser.upFolderToolTipText=Eine Ebene h\u00F6her
+
+FileChooser.viewMenuLabelText=Ansicht
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_el.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_el.properties
new file mode 100644
index 0000000..86af1f9
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_el.properties
@@ -0,0 +1,19 @@
+SystemMenu.showHeapStatus=\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7\u03c2 \u03c3\u03c4\u03bf\u03af\u03b2\u03b1\u03c2
+
+Tooltip.contentsNotSaved=\u03c4\u03b1 \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03b1 \u03b4\u03b5\u03bd \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c4\u03b7\u03ba\u03b1\u03bd
+
+Tooltip.heapStatusPanel=\u039a\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03c3\u03c4\u03bf\u03af\u03b2\u03b1\u03c2. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03b5\u03c3\u03c4\u03b5\u03af \u03bf \u03c3\u03c5\u03bb\u03bb\u03ad\u03ba\u03c4\u03b7\u03c2 \u03b1\u03c0\u03bf\u03c1\u03c1\u03b9\u03bc\u03ac\u03c4\u03c9\u03bd
+
+Xoetrope.hue=\u0391\u03c0\u03cc\u03c7\u03c1\u03c9\u03c3\u03b7
+
+Xoetrope.brightness=\u03a6\u03c9\u03c4\u03b5\u03b9\u03bd\u03cc\u03c4\u03b7\u03c4\u03b1
+
+Xoetrope.saturation=\u039a\u03bf\u03c1\u03b5\u03c3\u03bc\u03cc\u03c2
+
+Xoetrope.warm=\u0398\u0395\u03a1\u039c\u039f
+
+Xoetrope.cold=\u03a8\u03a5\u03a7\u03a1\u039f
+
+Xoetrope.decimalRGB=\u0394\u03b5\u03ba\u03b1\u03b4\u03b9\u03ba\u03cc RGB
+
+Xoetrope.webSafeColors=\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b1\u03c3\u03c6\u03b1\u03bb\u03ce\u03bd \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b4\u03af\u03ba\u03c4\u03c5\u03bf \u03c7\u03c1\u03c9\u03bc\u03ac\u03c4\u03c9\u03bd
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_en_GB.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_en_GB.properties
new file mode 100644
index 0000000..d45da45
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_en_GB.properties
@@ -0,0 +1,27 @@
+# British labels by Robert Gibson
+
+SystemMenu.showHeapStatus=Show heap status
+
+Tooltip.contentsNotSaved=contents not saved
+
+Tooltip.heapStatusPanel=Heap status. Click to run garbage collector
+
+Tooltip.menuSearchButton=Select to view menu search panel
+
+Tooltip.menuSearchField=Enter search string and press 'Enter' button to search
+
+Tooltip.menuSearchTooltip=Click to locate menu
+
+Xoetrope.hue=Hue
+
+Xoetrope.brightness=Brightness
+
+Xoetrope.saturation=Saturation
+
+Xoetrope.warm=WARM
+
+Xoetrope.cold=COLD
+
+Xoetrope.decimalRGB=Decimal RGB
+
+Xoetrope.webSafeColors=Use web safe colours
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_es.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_es.properties
new file mode 100644
index 0000000..8c490ed
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_es.properties
@@ -0,0 +1,146 @@
+# Danish translation by David �lvarez Le�n
+
+SystemMenu.close=Cerrar
+
+SystemMenu.iconify=Minimizar
+
+SystemMenu.restore=Restaurar
+
+SystemMenu.maximize=Maximizar
+
+SystemMenu.skins=Fondos Substance
+
+SystemMenu.showHeapStatus=Mostrar estado de pila
+
+Tooltip.contentsNotSaved=Contenidos sin guardar
+
+Tooltip.heapStatusPanel=Estado de pila. Pulse para pasar el recolector de basura.
+
+Xoetrope.hue=Tonalidad
+
+Xoetrope.brightness=Brillo
+
+Xoetrope.saturation=Saturaci\u00F3n
+
+Xoetrope.warm=c\u00C1LIDO
+
+Xoetrope.cold=F\u00CDRO
+
+Xoetrope.decimalRGB=RGB Decimal
+
+Xoetrope.webSafeColors=Use colores web seguros
+
+# File chooser strings from com.sun.swing.internal.plaf.basic.resources.basic
+FileChooser.acceptAllFileFilterText=Todos los archivos
+
+FileChooser.cancelButtonMnemonic=67
+
+FileChooser.cancelButtonText=Cancelar
+
+FileChooser.cancelButtonToolTipText=Cuadro de di\u00E1logo para cancelar elector de archivo
+
+FileChooser.directoryDescriptionText=Directorio
+
+FileChooser.directoryOpenButtonMnemonic=82
+
+FileChooser.directoryOpenButtonText=Abrir
+
+FileChooser.directoryOpenButtonToolTipText=Abrir directorio seleccionado
+
+FileChooser.fileDescriptionText=Archivo gen\u00E9rico
+
+FileChooser.fileSizeGigaBytes={0} GB
+
+FileChooser.fileSizeKiloBytes={0} KB
+
+FileChooser.fileSizeMegaBytes={0} MB
+
+FileChooser.helpButtonMnemonic=89
+
+FileChooser.helpButtonText=Ayuda
+
+FileChooser.helpButtonToolTipText=Ayuda elector de archivos
+
+FileChooser.newFolderErrorSeparator=:
+
+FileChooser.newFolderErrorText=Error al crear una nueva carpeta
+
+FileChooser.openButtonMnemonic=65
+
+FileChooser.openButtonText=Abrir
+
+FileChooser.openButtonToolTipText=Abrir archivo seleccionado
+
+FileChooser.openDialogTitleText=Abrir
+
+FileChooser.other.newFolder=Carpeta nueva
+
+FileChooser.other.newFolder.subsequent=Carpeta nueva.{0}
+
+FileChooser.saveButtonMnemonic=71
+
+FileChooser.saveButtonText=Guardar
+
+FileChooser.saveButtonToolTipText=Guardar archivo seleccionado
+
+FileChooser.saveDialogTitleText=Guardar
+
+FileChooser.updateButtonMnemonic=84
+
+FileChooser.updateButtonText=Actualizar
+
+FileChooser.updateButtonToolTipText=Actualizar lista de directorios
+
+FileChooser.win32.newFolder=Carpeta nueva
+
+FileChooser.win32.newFolder.subsequent=Carpeta nueva ({0})
+
+# File chooser strings from com.sun.swing.internal.plaf.metal.resources.metal
+FileChooser.detailsViewActionLabelText=Detalles
+
+FileChooser.detailsViewButtonAccessibleName=Detalles
+
+FileChooser.detailsViewButtonToolTipText=Detalles
+
+FileChooser.fileAttrHeaderText=Atributos
+
+FileChooser.fileDateHeaderText=Modificado
+
+FileChooser.fileNameHeaderText=Nombre
+
+FileChooser.fileNameLabelText=Nombre de archivo:
+
+FileChooser.fileSizeHeaderText=Tama\u00F1o
+
+FileChooser.fileTypeHeaderText=Tipo
+
+FileChooser.filesOfTypeLabelText=Archivos de tipo:
+
+FileChooser.homeFolderAccessibleName=Principal
+
+FileChooser.homeFolderToolTipText=Principal
+
+FileChooser.listViewActionLabelText=Lista
+
+FileChooser.listViewButtonAccessibleName=Lista
+
+FileChooser.listViewButtonToolTipText=Lista
+
+FileChooser.lookInLabelText=Buscar en:
+
+FileChooser.newFolderAccessibleName=Carpeta nueva
+
+FileChooser.newFolderActionLabelText=Carpeta nueva
+
+FileChooser.newFolderToolTipText=Crear carpeta nueva
+
+FileChooser.refreshActionLabelText=Renovar
+
+FileChooser.saveInLabelText=Guardar en:
+
+FileChooser.upFolderAccessibleName=Arriba
+
+FileChooser.upFolderToolTipText=Subir un nivel
+
+FileChooser.viewMenuLabelText=Ver
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_es_AR.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_es_AR.properties
new file mode 100644
index 0000000..a0f5c45
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_es_AR.properties
@@ -0,0 +1,105 @@
+# Spanish (Argentina) translation by Iv�n Ridao Freitas
+
+SystemMenu.close=Cerrar
+
+SystemMenu.iconify=Minimizar
+
+SystemMenu.restore=Restaurar
+
+SystemMenu.maximize=Maximizar
+
+SystemMenu.skins=Temas de Substance
+
+SystemMenu.showHeapStatus=Mostrar estado de pila
+
+TabbedPane.overviewButtonTooltip=Mostrar visi\u00f3n general de las pesta\u00F1as
+
+TabbedPane.overviewDialogTitle=Visi\u00f3n general de las pesta\u00F1as
+
+TabbedPane.overviewDialogTitleRefresh=Visi\u00f3n general de las pesta\u00F1as [actualizar cada {0} seg.]
+
+TabbedPane.overviewWidgetTooltip=Haga clic para cerrar la visi\u00f3n general y seleccionar la pesta\u00F1a
+
+Tooltip.contentsNotSaved=cambios sin guardar
+
+Tooltip.heapStatusPanel=Estado de pila. Haga clic para ejecutar el recolector de basura
+
+Tooltip.menuSearchButton=Seleccione para ver el panel de b\u00fasqueda del men\u00fa
+
+Tooltip.menuSearchField=Ingrese la cadena de b\u00fasqueda y presione la tecla 'Enter' para buscar
+
+Tooltip.menuSearchTooltip=Haga clic para ubicar el men\u00fa
+
+Xoetrope.hue=Tono
+
+Xoetrope.brightness=Brillo
+
+Xoetrope.saturation=Saturaci\u00f3n
+
+Xoetrope.warm=C\u00c1LIDO
+
+Xoetrope.cold=FR\u00cdO
+
+Xoetrope.decimalRGB=RGB decimal
+
+Xoetrope.webSafeColors=Usar colores web seguros
+
+EditMenu.copy=Copiar
+
+EditMenu.cut=Cortar
+
+EditMenu.paste=Pegar
+
+EditMenu.delete=Eliminar
+
+EditMenu.selectAll=Seleccionar todo
+
+# File chooser strings from com.sun.swing.internal.plaf.metal.resources.metal
+FileChooser.detailsViewActionLabelText=Detalles
+
+FileChooser.detailsViewButtonAccessibleName=Detalles
+
+FileChooser.detailsViewButtonToolTipText=Detalles
+
+FileChooser.fileAttrHeaderText=Atributos
+
+FileChooser.fileDateHeaderText=Modificado
+
+FileChooser.fileNameHeaderText=Nombre
+
+FileChooser.fileNameLabelText=Nombre de archivo:
+
+FileChooser.fileSizeHeaderText=Tama\u00F1o
+
+FileChooser.fileTypeHeaderText=Tipo
+
+FileChooser.filesOfTypeLabelText=Archivos de tipo:
+
+FileChooser.homeFolderAccessibleName=Principal
+
+FileChooser.homeFolderToolTipText=Principal
+
+FileChooser.listViewActionLabelText=Lista
+
+FileChooser.listViewButtonAccessibleName=Lista
+
+FileChooser.listViewButtonToolTipText=Lista
+
+FileChooser.lookInLabelText=Buscar en:
+
+FileChooser.newFolderAccessibleName=Carpeta nueva
+
+FileChooser.newFolderActionLabelText=Carpeta nueva
+
+FileChooser.newFolderToolTipText=Crear carpeta nueva
+
+FileChooser.refreshActionLabelText=Actualizar
+
+FileChooser.saveInLabelText=Guardar en:
+
+FileChooser.upFolderAccessibleName=Arriba
+
+FileChooser.upFolderToolTipText=Subir un nivel
+
+FileChooser.viewMenuLabelText=Ver
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_es_MX.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_es_MX.properties
new file mode 100644
index 0000000..d46993d
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_es_MX.properties
@@ -0,0 +1,20 @@
+SystemMenu.showHeapStatus=Mostrar estado de pila
+
+Tooltip.contentsNotSaved=contenidos no guardados
+
+Tooltip.heapStatusPanel=Estado de pila. Haga clic para ejecutar el basurero
+
+Xoetrope.hue=Tono
+
+Xoetrope.brightness=Brillo
+
+Xoetrope.saturation=Saturaci\u00f3n
+
+Xoetrope.warm=C\u00c1LIDO
+
+Xoetrope.cold=FR\u00cdO
+
+Xoetrope.decimalRGB=RGB decimal
+
+Xoetrope.webSafeColors=Use colores seguros para la Web
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_fi.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_fi.properties
new file mode 100644
index 0000000..edfbf28
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_fi.properties
@@ -0,0 +1,19 @@
+SystemMenu.showHeapStatus=N\u00e4yt\u00e4 keon tila
+
+Tooltip.contentsNotSaved=sis\u00e4lt\u00f6\u00e4 ei tallennettu
+
+Tooltip.heapStatusPanel=Keon tila. Suorita muistin siivous napsauttamalla
+
+Xoetrope.hue=S\u00e4vy
+
+Xoetrope.brightness=Kirkkaus
+
+Xoetrope.saturation=Kyll\u00e4isyys
+
+Xoetrope.warm=L\u00c4MMIN
+
+Xoetrope.cold=KYLM\u00c4
+
+Xoetrope.decimalRGB=Desimaali-RGB
+
+Xoetrope.webSafeColors=K\u00e4yt\u00e4 selainturvallisia v\u00e4rej\u00e4
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_fr.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_fr.properties
new file mode 100644
index 0000000..940cf93
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_fr.properties
@@ -0,0 +1,146 @@
+# French translation by Alois Cochard
+
+SystemMenu.close=Fermer
+
+SystemMenu.iconify=R\u00E9duire
+
+SystemMenu.restore=Restaurer
+
+SystemMenu.maximize=Agrandir
+
+SystemMenu.skins=Substance skins
+
+SystemMenu.showHeapStatus=Voir le statut de la pile
+
+Tooltip.contentsNotSaved=contenu non sauv\u00E9
+
+Tooltip.heapStatusPanel=Statut de la pile. Cliquez pour lancer 'garbage collector'
+
+Xoetrope.hue=Ton
+
+Xoetrope.brightness=Luminosit\u00E9
+
+Xoetrope.saturation=Saturation
+
+Xoetrope.warm=CHAUD
+
+Xoetrope.cold=FROID
+
+Xoetrope.decimalRGB=RGB D\u00E9cimal
+
+Xoetrope.webSafeColors=Couleurs compatible web
+
+# File chooser strings from com.sun.swing.internal.plaf.basic.resources.basic
+FileChooser.acceptAllFileFilterText=Tous les fichiers
+
+FileChooser.cancelButtonMnemonic=65
+
+FileChooser.cancelButtonText=Annuler
+
+FileChooser.cancelButtonToolTipText=Ferme la bo\u00EEte de dialogue du s\u00E9lecteur de fichiers
+
+FileChooser.directoryDescriptionText=R\u00E9pertoire
+
+FileChooser.directoryOpenButtonMnemonic=79
+
+FileChooser.directoryOpenButtonText=Ouvrir
+
+FileChooser.directoryOpenButtonToolTipText=Ouvrir le r\u00E9pertoire s\u00E9lectionn\u00E9
+
+FileChooser.fileDescriptionText=Fichier g\u00E9n\u00E9rique
+
+FileChooser.fileSizeGigaBytes={0} Go
+
+FileChooser.fileSizeKiloBytes={0} Ko
+
+FileChooser.fileSizeMegaBytes={0} Mo
+
+FileChooser.helpButtonMnemonic=68
+
+FileChooser.helpButtonText=Aide
+
+FileChooser.helpButtonToolTipText=Aide sur le s\u00E9lecteur de fichiers
+
+FileChooser.newFolderErrorSeparator=:
+
+FileChooser.newFolderErrorText=Erreur lors de la cr\u00E9ation du nouveau dossier
+
+FileChooser.openButtonMnemonic=79
+
+FileChooser.openButtonText=Ouvrir
+
+FileChooser.openButtonToolTipText=Ouvre le fichier s\u00E9lectionn\u00E9
+
+FileChooser.openDialogTitleText=Ouvrir
+
+FileChooser.other.newFolder=Nouveau dossier
+
+FileChooser.other.newFolder.subsequent=Nouveau dossier.{0}
+
+FileChooser.saveButtonMnemonic=69
+
+FileChooser.saveButtonText=Enregistrer
+
+FileChooser.saveButtonToolTipText=Enregistre le fichier s\u00E9lectionn\u00E9
+
+FileChooser.saveDialogTitleText=Enregistrer
+
+FileChooser.updateButtonMnemonic=77
+
+FileChooser.updateButtonText=Mise \u00E0 jour
+
+FileChooser.updateButtonToolTipText=Met \u00E0 jour la liste des r\u00E9pertoires
+
+FileChooser.win32.newFolder=Nouveau dossier
+
+FileChooser.win32.newFolder.subsequent=Nouveau dossier ({0})
+
+# File chooser strings from com.sun.swing.internal.plaf.metal.resources.metal
+FileChooser.detailsViewActionLabelText=D\u00E9tails
+
+FileChooser.detailsViewButtonAccessibleName=D\u00E9tails
+
+FileChooser.detailsViewButtonToolTipText=D\u00E9tails
+
+FileChooser.fileAttrHeaderText=Attributs
+
+FileChooser.fileDateHeaderText=Modifi\u00E9
+
+FileChooser.fileNameHeaderText=Nom
+
+FileChooser.fileNameLabelText=Nom de fichier :
+
+FileChooser.fileSizeHeaderText=Taille
+
+FileChooser.fileTypeHeaderText=Type
+
+FileChooser.filesOfTypeLabelText=Fichiers du type :
+
+FileChooser.homeFolderAccessibleName=Accueil
+
+FileChooser.homeFolderToolTipText=R\u00E9pertoire d'accueil
+
+FileChooser.listViewActionLabelText=Liste
+
+FileChooser.listViewButtonAccessibleName=Liste
+
+FileChooser.listViewButtonToolTipText=Liste
+
+FileChooser.lookInLabelText=Rechercher dans :
+
+FileChooser.newFolderAccessibleName=Nouveau dossier
+
+FileChooser.newFolderActionLabelText=Nouveau dossier
+
+FileChooser.newFolderToolTipText=Cr\u00E9e un nouveau dossier.
+
+FileChooser.refreshActionLabelText=Actualiser
+
+FileChooser.saveInLabelText=Enregistrer dans :
+
+FileChooser.upFolderAccessibleName=Vers le haut
+
+FileChooser.upFolderToolTipText=Remonte d'un niveau.
+
+FileChooser.viewMenuLabelText=Affichage
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_fr_CA.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_fr_CA.properties
new file mode 100644
index 0000000..89fbfae
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_fr_CA.properties
@@ -0,0 +1,19 @@
+SystemMenu.showHeapStatus=Afficher l\'\u00e9tat d\'entassement
+
+Tooltip.contentsNotSaved=contenu non enregistr\u00e9
+
+Tooltip.heapStatusPanel=\u00c9tat d\'entassement. Cliquez pour ex\u00e9cuter le nettoyeur
+
+Xoetrope.hue=Teinte
+
+Xoetrope.brightness=Luminosit\u00e9
+
+Xoetrope.saturation=Saturation
+
+Xoetrope.warm=CHAUD
+
+Xoetrope.cold=FROID
+
+Xoetrope.decimalRGB=RVB d\u00e9cimal
+
+Xoetrope.webSafeColors=Utiliser des couleurs s\u00fbres pour le Web
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_hu.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_hu.properties
new file mode 100644
index 0000000..012fb6e
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_hu.properties
@@ -0,0 +1,19 @@
+SystemMenu.showHeapStatus=Halom \u00e1llapot\u00e1nak megjelen\u00edt\u00e9se
+
+Tooltip.contentsNotSaved=A tartalom nincs mentve.
+
+Tooltip.heapStatusPanel=A halommem\u00f3ria \u00e1llapota. Kattintson ide a szem\u00e9tgy\u00fbjt\u00f5 futtat\u00e1s\u00e1hoz.
+
+Xoetrope.hue=\u00c1rnyalat
+
+Xoetrope.brightness=F\u00e9nyer\u00f5
+
+Xoetrope.saturation=Tel\u00edtetts\u00e9g
+
+Xoetrope.warm=MELEG
+
+Xoetrope.cold=HIDEG
+
+Xoetrope.decimalRGB=Decim\u00e1lis RGB
+
+Xoetrope.webSafeColors=Weben megjelen\u00edthet\u00f5 sz\u00ednek haszn\u00e1lata
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_it.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_it.properties
new file mode 100644
index 0000000..5836b21
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_it.properties
@@ -0,0 +1,148 @@
+# Italian translation by Sandro Martini
+
+SystemMenu.close=Chiudi
+
+SystemMenu.iconify=Iconizza
+
+SystemMenu.restore=Ripristina
+
+SystemMenu.maximize=Massimizza
+
+SystemMenu.skins=Skin di Substance
+
+SystemMenu.showHeapStatus=Visualizza stato memoria
+
+SystemMenu.titlePainters=Disegna Titoli di Substance
+
+Tooltip.contentsNotSaved=contenuto non salvato
+
+Tooltip.heapStatusPanel=Stato memoria. Clicca per eseguire il Garbage Collector
+
+Xoetrope.hue=Tonalit\u00e0
+
+Xoetrope.brightness=Luminosit\u00e0
+
+Xoetrope.saturation=Saturazione
+
+Xoetrope.warm=CALDO
+
+Xoetrope.cold=FREDDO
+
+Xoetrope.decimalRGB=RGB Decimale
+
+Xoetrope.webSafeColors=Usa colori sicuri per il web
+
+# File chooser strings from com.sun.swing.internal.plaf.basic.resources.basic
+FileChooser.acceptAllFileFilterText=Tutti i file
+
+FileChooser.cancelButtonMnemonic=65
+
+FileChooser.cancelButtonText=Annulla
+
+FileChooser.cancelButtonToolTipText=Finestra di dialogo Interrompi selezione file
+
+FileChooser.directoryDescriptionText=Directory
+
+FileChooser.directoryOpenButtonMnemonic=82
+
+FileChooser.directoryOpenButtonText=Apri
+
+FileChooser.directoryOpenButtonToolTipText=Apri directory selezionata
+
+FileChooser.fileDescriptionText=File generico
+
+FileChooser.fileSizeGigaBytes={0} GB
+
+FileChooser.fileSizeKiloBytes={0} KB
+
+FileChooser.fileSizeMegaBytes={0} MB
+
+FileChooser.helpButtonMnemonic=63
+
+FileChooser.helpButtonText=?
+
+FileChooser.helpButtonToolTipText=Guida di Selezione file
+
+FileChooser.newFolderErrorSeparator=:
+
+FileChooser.newFolderErrorText=Errore durante la creazione della nuova cartella
+
+FileChooser.openButtonMnemonic=80
+
+FileChooser.openButtonText=Apri
+
+FileChooser.openButtonToolTipText=Apri file selezionato
+
+FileChooser.openDialogTitleText=Apri
+
+FileChooser.other.newFolder=Nuova cartella
+
+FileChooser.other.newFolder.subsequent=Nuova cartella.{0}
+
+FileChooser.saveButtonMnemonic=83
+
+FileChooser.saveButtonText=Salva
+
+FileChooser.saveButtonToolTipText=Salva file selezionato
+
+FileChooser.saveDialogTitleText=Salva
+
+FileChooser.updateButtonMnemonic=71
+
+FileChooser.updateButtonText=Aggiorna
+
+FileChooser.updateButtonToolTipText=Aggiorna elenco directory
+
+FileChooser.win32.newFolder=Nuova cartella
+
+FileChooser.win32.newFolder.subsequent=Nuova cartella ({0})
+
+# File chooser strings from com.sun.swing.internal.plaf.metal.resources.metal
+FileChooser.detailsViewActionLabelText=Dettagli
+
+FileChooser.detailsViewButtonAccessibleName=Dettagli
+
+FileChooser.detailsViewButtonToolTipText=Dettagli
+
+FileChooser.fileAttrHeaderText=Attributi
+
+FileChooser.fileDateHeaderText=Modificato
+
+FileChooser.fileNameHeaderText=Nome
+
+FileChooser.fileNameLabelText=Nome file:
+
+FileChooser.fileSizeHeaderText=Dimensioni
+
+FileChooser.fileTypeHeaderText=Tipo
+
+FileChooser.filesOfTypeLabelText=Tipo file:
+
+FileChooser.homeFolderAccessibleName=Principale
+
+FileChooser.homeFolderToolTipText=Principale
+
+FileChooser.listViewActionLabelText=Elenco
+
+FileChooser.listViewButtonAccessibleName=Elenco
+
+FileChooser.listViewButtonToolTipText=Elenco
+
+FileChooser.lookInLabelText=Cerca in:
+
+FileChooser.newFolderAccessibleName=Nuova cartella
+
+FileChooser.newFolderActionLabelText=Nuova cartella
+
+FileChooser.newFolderToolTipText=Crea nuova cartella
+
+FileChooser.refreshActionLabelText=Aggiorna
+
+FileChooser.saveInLabelText=Salva in:
+
+FileChooser.upFolderAccessibleName=Superiore
+
+FileChooser.upFolderToolTipText=Cartella superiore
+
+FileChooser.viewMenuLabelText=Visualizza
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_iw.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_iw.properties
new file mode 100644
index 0000000..893cb16
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_iw.properties
@@ -0,0 +1,32 @@
+# Hebrew translation by Kirill Grouchnikov
+
+SystemMenu.close=\u05E1\u05D2\u05D5\u05E8
+
+SystemMenu.iconify=\u05DE\u05D6\u05E2\u05E8
+
+SystemMenu.restore=\u05E9\u05D7\u05D6\u05E8
+
+SystemMenu.maximize=\u05D4\u05D2\u05D3\u05DC
+
+SystemMenu.skins=\u05E7\u05E8\u05D5\u05DE\u05D9\u05DD \u05D1:Substance
+
+SystemMenu.showHeapStatus=\u05D4\u05E8\u05D0\u05D4 \u05DE\u05E6\u05D1 \u05D4\u05E2\u05E8\u05D9\u05DE\u05D4
+
+Tooltip.contentsNotSaved=\u05EA\u05DB\u05D5\u05DC\u05D4 \u05DC\u05D0 \u05E0\u05E9\u05DE\u05E8\u05D4
+
+Tooltip.heapStatusPanel=\u05DE\u05E6\u05D1 \u05D4\u05E2\u05E8\u05D9\u05DE\u05D4. \u05DC\u05D7\u05E5 \u05DC\u05D4\u05E8\u05E6\u05EA \u05D0\u05D5\u05E1\u05E3 \u05D0\u05E9\u05E4\u05D4
+
+Xoetrope.hue=\u05D2\u05D5\u05D5\u05DF
+
+Xoetrope.brightness=\u05D1\u05D4\u05D9\u05E8\u05D5\u05EA
+
+Xoetrope.saturation=\u05D4\u05E8\u05D5\u05D5\u05D9\u05D4
+
+Xoetrope.warm=\u05D7\u05D5\u05DD
+
+Xoetrope.cold=\u05E7\u05D5\u05E8
+
+Xoetrope.decimalRGB=\u05E2\u05E9\u05E8\u05D5\u05E0\u05D9
+
+Xoetrope.webSafeColors=\u05E7\u05D7 \u05E6\u05D1\u05E2\u05D9 web
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ja.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ja.properties
new file mode 100644
index 0000000..7a86ff6
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ja.properties
@@ -0,0 +1,134 @@
+SystemMenu.showHeapStatus=\u30d2\u30fc\u30d7 \u30b9\u30c6\u30fc\u30bf\u30b9\u3092\u8868\u793a
+
+Tooltip.contentsNotSaved=\u30b3\u30f3\u30c6\u30f3\u30c4\u304c\u4fdd\u5b58\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+
+Tooltip.heapStatusPanel=\u30d2\u30fc\u30d7 \u30b9\u30c6\u30fc\u30bf\u30b9\u3002\u30ac\u30fc\u30d9\u30b8 \u30b3\u30ec\u30af\u30bf\u3092\u5b9f\u884c\u3059\u308b\u306b\u306f\u3001\u30af\u30ea\u30c3\u30af\u3057\u307e\u3059
+
+Xoetrope.hue=\u8272\u5408\u3044
+
+Xoetrope.brightness=\u8f1d\u5ea6
+
+Xoetrope.saturation=\u5f69\u5ea6
+
+Xoetrope.warm=\u6696
+
+Xoetrope.cold=\u51b7
+
+Xoetrope.decimalRGB=10 \u9032\u6570 RGB
+
+Xoetrope.webSafeColors=Web \u30bb\u30fc\u30d5 \u30ab\u30e9\u30fc\u3092\u4f7f\u7528
+
+# File chooser strings from com.sun.swing.internal.plaf.basic.resources.basic
+FileChooser.acceptAllFileFilterText=\u3059\u3079\u3066\u306E\u30D5\u30A1\u30A4\u30EB
+
+FileChooser.cancelButtonMnemonic=67
+
+FileChooser.cancelButtonText=\u53D6\u6D88\u3057
+
+FileChooser.cancelButtonToolTipText=\u30D5\u30A1\u30A4\u30EB\u30C1\u30E5\u30FC\u30B6\u30C0\u30A4\u30A2\u30ED\u30B0\u3092\u7D42\u4E86
+
+FileChooser.directoryDescriptionText=\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
+
+FileChooser.directoryOpenButtonMnemonic=79
+
+FileChooser.directoryOpenButtonText=\u958B\u304F
+
+FileChooser.directoryOpenButtonToolTipText=\u9078\u629E\u3057\u305F\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u958B\u304F
+
+FileChooser.fileDescriptionText=\u6C4E\u7528\u30D5\u30A1\u30A4\u30EB
+
+FileChooser.fileSizeGigaBytes={0} G \u30D0\u30A4\u30C8
+
+FileChooser.fileSizeKiloBytes={0} K \u30D0\u30A4\u30C8
+
+FileChooser.fileSizeMegaBytes={0} M \u30D0\u30A4\u30C8
+
+FileChooser.helpButtonMnemonic=72
+
+FileChooser.helpButtonText=\u30D8\u30EB\u30D7(H)
+
+FileChooser.helpButtonToolTipText=\u30D5\u30A1\u30A4\u30EB\u30C1\u30E5\u30FC\u30B6\u30D8\u30EB\u30D7
+
+FileChooser.newFolderErrorSeparator=:
+
+FileChooser.newFolderErrorText=\u65B0\u898F\u30D5\u30A9\u30EB\u30C0\u306E\u4F5C\u6210\u306B\u5931\u6557
+
+FileChooser.openButtonMnemonic=79
+
+FileChooser.openButtonText=\u958B\u304F
+
+FileChooser.openButtonToolTipText=\u9078\u629E\u3057\u305F\u30D5\u30A1\u30A4\u30EB\u3092\u958B\u304F
+
+FileChooser.openDialogTitleText=\u958B\u304F
+
+FileChooser.other.newFolder=\u65B0\u898F\u30D5\u30A9\u30EB\u30C0
+
+FileChooser.other.newFolder.subsequent=\u65B0\u898F\u30D5\u30A9\u30EB\u30C0.{0}
+
+FileChooser.saveButtonMnemonic=83
+
+FileChooser.saveButtonText=\u4FDD\u5B58
+
+FileChooser.saveButtonToolTipText=\u9078\u629E\u3057\u305F\u30D5\u30A1\u30A4\u30EB\u3092\u4FDD\u5B58
+
+FileChooser.saveDialogTitleText=\u4FDD\u5B58
+
+FileChooser.updateButtonMnemonic=85
+
+FileChooser.updateButtonText=\u66F4\u65B0(U)
+
+FileChooser.updateButtonToolTipText=\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u4E00\u89A7\u3092\u66F4\u65B0
+
+FileChooser.win32.newFolder=\u65B0\u898F\u30D5\u30A9\u30EB\u30C0
+
+FileChooser.win32.newFolder.subsequent=\u65B0\u898F\u30D5\u30A9\u30EB\u30C0 ({0})
+
+# File chooser strings from com.sun.swing.internal.plaf.metal.resources.metal
+FileChooser.detailsViewActionLabelText=\u8A73\u7D30
+
+FileChooser.detailsViewButtonAccessibleName=\u8A73\u7D30
+
+FileChooser.detailsViewButtonToolTipText=\u8A73\u7D30
+
+FileChooser.fileAttrHeaderText=\u5C5E\u6027
+
+FileChooser.fileDateHeaderText=\u4FEE\u6B63\u65E5
+
+FileChooser.fileNameHeaderText=\u540D\u524D
+
+FileChooser.fileNameLabelText=\u30D5\u30A1\u30A4\u30EB\u540D:
+
+FileChooser.fileSizeHeaderText=\u30B5\u30A4\u30BA
+
+FileChooser.fileTypeHeaderText=\u578B
+
+FileChooser.filesOfTypeLabelText=\u30D5\u30A1\u30A4\u30EB\u30BF\u30A4\u30D7:
+
+FileChooser.homeFolderAccessibleName=\u30DB\u30FC\u30E0
+
+FileChooser.homeFolderToolTipText=\u30DB\u30FC\u30E0
+
+FileChooser.listViewActionLabelText=\u30EA\u30B9\u30C8
+
+FileChooser.listViewButtonAccessibleName=\u30EA\u30B9\u30C8
+
+FileChooser.listViewButtonToolTipText=\u30EA\u30B9\u30C8
+
+FileChooser.lookInLabelText=\u53C2\u7167:
+
+FileChooser.newFolderAccessibleName=\u65B0\u898F\u30D5\u30A9\u30EB\u30C0
+
+FileChooser.newFolderActionLabelText=\u65B0\u898F\u30D5\u30A9\u30EB\u30C0
+
+FileChooser.newFolderToolTipText=\u30D5\u30A9\u30EB\u30C0\u306E\u65B0\u898F\u4F5C\u6210
+
+FileChooser.refreshActionLabelText=\u66F4\u65B0
+
+FileChooser.saveInLabelText=\u4FDD\u5B58:
+
+FileChooser.upFolderAccessibleName=\u4E0A\u3078
+
+FileChooser.upFolderToolTipText=1 \u30EC\u30D9\u30EB\u4E0A\u3078
+
+FileChooser.viewMenuLabelText=\u8868\u793A
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ko.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ko.properties
new file mode 100644
index 0000000..fc4bf50
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ko.properties
@@ -0,0 +1,114 @@
+# File chooser strings from com.sun.swing.internal.plaf.basic.resources.basic
+FileChooser.acceptAllFileFilterText=\uBAA8\uB4E0 \uD30C\uC77C
+
+FileChooser.cancelButtonMnemonic=67
+
+FileChooser.cancelButtonText=\uCDE8\uC18C
+
+FileChooser.cancelButtonToolTipText=\uD30C\uC77C \uC120\uD0DD \uB300\uD654 \uC0C1\uC790 \uC911\uC9C0
+
+FileChooser.directoryDescriptionText=\uB514\uB809\uD1A0\uB9AC
+
+FileChooser.directoryOpenButtonMnemonic=79
+
+FileChooser.directoryOpenButtonText=\uC5F4\uAE30
+
+FileChooser.directoryOpenButtonToolTipText=\uC120\uD0DD\uB41C \uB514\uB809\uD1A0\uB9AC \uC5F4\uAE30
+
+FileChooser.fileDescriptionText=\uC77C\uBC18 \uD30C\uC77C
+
+FileChooser.fileSizeGigaBytes={0}GB
+
+FileChooser.fileSizeKiloBytes={0}KB
+
+FileChooser.fileSizeMegaBytes={0}MB
+
+FileChooser.helpButtonMnemonic=72
+
+FileChooser.helpButtonText=\uB3C4\uC6C0\uB9D0(H)
+
+FileChooser.helpButtonToolTipText=FileChooser \uB3C4\uC6C0\uB9D0
+
+FileChooser.newFolderErrorSeparator=:
+
+FileChooser.newFolderErrorText=\uC0C8 \uD3F4\uB354 \uC791\uC131 \uC624\uB958
+
+FileChooser.openButtonMnemonic=79
+
+FileChooser.openButtonText=\uC5F4\uAE30
+
+FileChooser.openButtonToolTipText=\uC120\uD0DD\uB41C \uD30C\uC77C \uC5F4\uAE30
+
+FileChooser.openDialogTitleText=\uC5F4\uAE30
+
+FileChooser.other.newFolder=\uC0C8 \uD3F4\uB354
+
+FileChooser.other.newFolder.subsequent=\uC0C8 \uD3F4\uB354.{0}
+
+FileChooser.saveButtonMnemonic=83
+
+FileChooser.saveButtonText=\uC800\uC7A5
+
+FileChooser.saveButtonToolTipText=\uC120\uD0DD\uB41C \uD30C\uC77C \uC800\uC7A5
+
+FileChooser.saveDialogTitleText=\uC800\uC7A5
+
+FileChooser.updateButtonMnemonic=85
+
+FileChooser.updateButtonText=\uC5C5\uB370\uC774\uD2B8(U)
+
+FileChooser.updateButtonToolTipText=\uB514\uB809\uD1A0\uB9AC \uBAA9\uB85D \uC5C5\uB370\uC774\uD2B8
+
+FileChooser.win32.newFolder=\uC0C8 \uD3F4\uB354
+
+FileChooser.win32.newFolder.subsequent=\uC0C8 \uD3F4\uB354 ({0})
+
+# File chooser strings from com.sun.swing.internal.plaf.metal.resources.metal
+FileChooser.detailsViewActionLabelText=\uC790\uC138\uD788
+
+FileChooser.detailsViewButtonAccessibleName=\uC790\uC138\uD788
+
+FileChooser.detailsViewButtonToolTipText=\uC790\uC138\uD788
+
+FileChooser.fileAttrHeaderText=\uC18D\uC131
+
+FileChooser.fileDateHeaderText=\uC218\uC815
+
+FileChooser.fileNameHeaderText=\uC774\uB984
+
+FileChooser.fileNameLabelText=\uD30C\uC77C \uC774\uB984:
+
+FileChooser.fileSizeHeaderText=\uD06C\uAE30
+
+FileChooser.fileTypeHeaderText=\uC885\uB958
+
+FileChooser.filesOfTypeLabelText=\uD30C\uC77C \uC885\uB958:
+
+FileChooser.homeFolderAccessibleName=\uD648
+
+FileChooser.homeFolderToolTipText=\uD648
+
+FileChooser.listViewActionLabelText=\uBAA9\uB85D
+
+FileChooser.listViewButtonAccessibleName=\uBAA9\uB85D
+
+FileChooser.listViewButtonToolTipText=\uBAA9\uB85D
+
+FileChooser.lookInLabelText=\uAC80\uC0C9 \uC704\uCE58:
+
+FileChooser.newFolderAccessibleName=\uC0C8 \uD3F4\uB354
+
+FileChooser.newFolderActionLabelText=\uC0C8 \uD3F4\uB354
+
+FileChooser.newFolderToolTipText=\uC0C8 \uD3F4\uB354 \uC791\uC131
+
+FileChooser.refreshActionLabelText=\uAC31\uC2E0
+
+FileChooser.saveInLabelText=\uC800\uC7A5 \uC704\uCE58
+
+FileChooser.upFolderAccessibleName=\uC704
+
+FileChooser.upFolderToolTipText=\uD55C \uB2E8\uACC4 \uC704\uB85C
+
+FileChooser.viewMenuLabelText=\uBCF4\uAE30
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_nl.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_nl.properties
new file mode 100644
index 0000000..8178b5d
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_nl.properties
@@ -0,0 +1,31 @@
+# Dutch translation by Ilana Paktor and Joachim van den Berg
+
+SystemMenu.close=Sluit
+
+SystemMenu.iconify=Minimaliseer
+
+SystemMenu.restore=Herstel
+
+SystemMenu.maximize=Maximaliseer
+
+SystemMenu.skins=Substance skins
+
+SystemMenu.showHeapStatus=Toon heap status
+
+Tooltip.contentsNotSaved=contents zijn niet bewaard
+
+Tooltip.heapStatusPanel=Heap status. Klik om de Garbage Collector te activeren
+
+Xoetrope.hue=Kleurschakering
+
+Xoetrope.brightness=Helderheid
+
+Xoetrope.saturation=Verzadiging
+
+Xoetrope.warm=WARM
+
+Xoetrope.cold=KOUD
+
+Xoetrope.decimalRGB=Decimale RGB
+
+Xoetrope.webSafeColors=Gebruik Web-Veilige kleuren
\ No newline at end of file
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_no.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_no.properties
new file mode 100644
index 0000000..a25e4c1
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_no.properties
@@ -0,0 +1,32 @@
+# Norwegian translation by Nils-Morten Nilssen
+
+SystemMenu.close=Lukk
+
+SystemMenu.iconify=Ikoniser
+
+SystemMenu.restore=Gjenopprett
+
+SystemMenu.maximize=Maksimer
+
+SystemMenu.skins=Substance drakter
+
+SystemMenu.showHeapStatus=Vis heap status
+
+Tooltip.contentsNotSaved=innholdet er ikke lagret
+
+Tooltip.heapStatusPanel=Heap status. Klikk for \u00E5 kj\u00F8re garbage collector
+
+Xoetrope.hue=Fargetone
+
+Xoetrope.brightness=Lysstyrke
+
+Xoetrope.saturation=Metningsgrad
+
+Xoetrope.warm=VARM
+
+Xoetrope.cold=KALD
+
+Xoetrope.decimalRGB=Desimal RGB
+
+Xoetrope.webSafeColors=Bruk websikre farger
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_pl.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_pl.properties
new file mode 100644
index 0000000..078df0c
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_pl.properties
@@ -0,0 +1,32 @@
+# Polish translation by Maciej Zwirski
+
+SystemMenu.close=Zamknij
+
+SystemMenu.iconify=Minimalizuj
+
+SystemMenu.restore=Przywr\u00F3\u0107
+
+SystemMenu.maximize=Maksymalizuj
+
+SystemMenu.skins=Sk\u00F3rki Substance
+
+SystemMenu.showHeapStatus=Poka\u017C stan stosu
+
+Tooltip.contentsNotSaved=zawarto\u015B\u0107 nie zosta\u0142a zapisana
+
+Tooltip.heapStatusPanel=Stan stosu. Kliknij, aby uruchomi\u0107 od\u015Bmiecanie pami\u0119ci
+
+Xoetrope.hue=Odcie\u0144
+
+Xoetrope.brightness=Jasno\u015B\u0107
+
+Xoetrope.saturation=Nasycenie
+
+Xoetrope.warm=CIEP\u0141E
+
+Xoetrope.cold=ZIMNE
+
+Xoetrope.decimalRGB=Dziesi\u0119tne RGB
+
+Xoetrope.webSafeColors=U\u017Cyj kolor\u00F3w przyjaznych dla sieci
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_pt.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_pt.properties
new file mode 100644
index 0000000..10f81dc
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_pt.properties
@@ -0,0 +1,19 @@
+SystemMenu.showHeapStatus=Mostrar status da pilha
+
+Tooltip.contentsNotSaved=conte\u00fado n\u00e3o gravado
+
+Tooltip.heapStatusPanel=Status da pilha. Clique para executar a coleta de lixo
+
+Xoetrope.hue=Matiz
+
+Xoetrope.brightness=Brilho
+
+Xoetrope.saturation=Satura\u00e7\u00e3o
+
+Xoetrope.warm=QUENTE
+
+Xoetrope.cold=FRIO
+
+Xoetrope.decimalRGB=RGB decimal
+
+Xoetrope.webSafeColors=Usar cores de seguran\u00e7a da Web
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_pt_BR.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_pt_BR.properties
new file mode 100644
index 0000000..a01c989
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_pt_BR.properties
@@ -0,0 +1,33 @@
+# Portuguese (Brazil) translation by Claudio de Oliveira Miranda
+
+SystemMenu.close=Fechar
+
+SystemMenu.iconify=Minimizar
+
+SystemMenu.restore=Restaurar
+
+SystemMenu.maximize=Maximizar
+
+SystemMenu.skins=Apar\u00eancia do Substance
+
+SystemMenu.showHeapStatus=Mostrar status do heap
+
+Tooltip.contentsNotSaved=Conte\u00fado n\u00e3o gravado
+
+Tooltip.heapStatusPanel=Status do heap. Clique para acionar o garbage collector
+
+Xoetrope.hue=Tonalidade
+
+Xoetrope.brightness=Brilho
+
+Xoetrope.saturation=Satura\u00e7\u00e3o
+
+Xoetrope.warm=QUENTE
+
+Xoetrope.cold=FRIO
+
+Xoetrope.decimalRGB=RGB Decimal
+
+Xoetrope.webSafeColors=Usar cores seguras da web
+
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ro.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ro.properties
new file mode 100644
index 0000000..b9ce364
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ro.properties
@@ -0,0 +1,21 @@
+# Romanian translation by Sergiu Nicolae Nacu and Ran Locar
+
+SystemMenu.showHeapStatus=Vizionare stare memorie heap
+
+Tooltip.contentsNotSaved=con\u0163inutul nu a fost salvat
+
+Tooltip.heapStatusPanel=Stare memorie heap. Apasa\u0163i pentru a porni garbage colector - ul
+
+Xoetrope.hue=Nuan\u0163\u0103
+
+Xoetrope.brightness=Luminozitate
+
+Xoetrope.saturation=Satura\u0163ie
+
+Xoetrope.warm=CALD
+
+Xoetrope.cold=RECE
+
+Xoetrope.decimalRGB=RGB decimal
+
+Xoetrope.webSafeColors=Folose\u015Fte culori compatibile web
\ No newline at end of file
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ru.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ru.properties
new file mode 100644
index 0000000..9066306
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_ru.properties
@@ -0,0 +1,32 @@
+# Russian translation by Kirill Grouchnikov
+
+SystemMenu.close=\u0417\u0430\u043A\u0440\u044B\u0442\u044C
+
+SystemMenu.iconify=\u0421\u0432\u0435\u0440\u043d\u0443\u0442\u044c
+
+SystemMenu.restore=\u0412\u0435\u0440\u043d\u0443\u0442\u044c
+
+SystemMenu.maximize=\u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0442\u044c
+
+SystemMenu.skins=\u041E\u0431\u043E\u043B\u043E\u0447\u043A\u0438 Substance
+
+SystemMenu.showHeapStatus=\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043A\u0443\u0447\u0438
+
+Tooltip.contentsNotSaved=\u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u0435 \u043D\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E
+
+Tooltip.heapStatusPanel=\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043A\u0443\u0447\u0438. \u041A\u043B\u0438\u043A \u043C\u044B\u0448\u0438 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442 \u0441\u0431\u043E\u0440\u0449\u0438\u043A \u043C\u0443\u0441\u043E\u0440\u0430.
+
+Xoetrope.hue=\u041E\u0442\u0442\u0435\u043D\u043E\u043A
+
+Xoetrope.brightness=\u042F\u0440\u043A\u043E\u0441\u0442\u044C
+
+Xoetrope.saturation=\u0421\u0430\u0442\u0443\u0440\u0430\u0446\u0438\u044F
+
+Xoetrope.warm=\u0422\u0415\u041F\u041B\u041E
+
+Xoetrope.cold=\u0425\u041E\u041B\u041E\u0414
+
+Xoetrope.decimalRGB=\u0414\u0435\u0441\u044F\u0442\u0438\u0447\u043D\u043E\u0435
+
+Xoetrope.webSafeColors=\u0411\u0440\u0430\u0442\u044C web-\u0446\u0432\u0435\u0442\u0430
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_sv.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_sv.properties
new file mode 100644
index 0000000..52b05ce
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_sv.properties
@@ -0,0 +1,142 @@
+SystemMenu.close=St\u00e4ng
+
+SystemMenu.iconify=Minimera
+
+SystemMenu.restore=\u00c5terst\u00e4ll
+
+SystemMenu.maximize=Maximera
+
+SystemMenu.showHeapStatus=Visa heap-status
+
+Tooltip.contentsNotSaved=inneh\u00e5llet har inte sparats
+
+Tooltip.heapStatusPanel=Heap-status. Klicka h\u00e4r f\u00f6r att k\u00f6ra skr\u00e4pinsamlaren
+
+Xoetrope.hue=Nyans
+
+Xoetrope.brightness=Ljusstyrka
+
+Xoetrope.saturation=M\u00e4ttnad
+
+Xoetrope.warm=VARM
+
+Xoetrope.cold=KALL
+
+Xoetrope.decimalRGB=RGB-decimaler
+
+Xoetrope.webSafeColors=Anv\u00e4nd webbs\u00e4kra f\u00e4rger
+
+# File chooser strings from com.sun.swing.internal.plaf.basic.resources.basic
+FileChooser.acceptAllFileFilterText=Alla filer
+
+FileChooser.cancelButtonMnemonic=65
+
+FileChooser.cancelButtonText=Avbryt
+
+FileChooser.cancelButtonToolTipText=Avbryt filvalsdialogruta
+
+FileChooser.directoryDescriptionText=Katalog
+
+FileChooser.directoryOpenButtonMnemonic=80
+
+FileChooser.directoryOpenButtonText=\u00D6ppna
+
+FileChooser.directoryOpenButtonToolTipText=\u00D6ppnar den markerade katalogen
+
+FileChooser.fileDescriptionText=Generisk fil
+
+FileChooser.fileSizeGigaBytes={0} GB
+
+FileChooser.fileSizeKiloBytes={0} KB
+
+FileChooser.fileSizeMegaBytes={0} MB
+
+FileChooser.helpButtonMnemonic=72
+
+FileChooser.helpButtonText=Hj\u00E4lp
+
+FileChooser.helpButtonToolTipText=Hj\u00E4lp - Filv\u00E4ljare
+
+FileChooser.newFolderErrorSeparator=:
+
+FileChooser.newFolderErrorText=Fel d\u00E5 ny mapp skapades
+
+FileChooser.openButtonMnemonic=78
+
+FileChooser.openButtonText=\u00D6ppna
+
+FileChooser.openButtonToolTipText=\u00D6ppna markerad fil
+
+FileChooser.openDialogTitleText=\u00D6ppna
+
+FileChooser.other.newFolder=Ny mapp
+
+FileChooser.other.newFolder.subsequent=Ny mapp.{0}
+
+FileChooser.saveButtonMnemonic=83
+
+FileChooser.saveButtonText=Spara
+
+FileChooser.saveButtonToolTipText=Spara markerad fil
+
+FileChooser.saveDialogTitleText=Spara
+
+FileChooser.updateButtonMnemonic=85
+
+FileChooser.updateButtonText=Uppdatera
+
+FileChooser.updateButtonToolTipText=Uppdatera kataloglistan
+
+FileChooser.win32.newFolder=Ny mapp
+
+FileChooser.win32.newFolder.subsequent=Ny mapp ({0})
+
+# File chooser strings from com.sun.swing.internal.plaf.metal.resources.metal
+FileChooser.detailsViewActionLabelText=Detaljerad lista
+
+FileChooser.detailsViewButtonAccessibleName=Detaljerad lista
+
+FileChooser.detailsViewButtonToolTipText=Detaljerad lista
+
+FileChooser.fileAttrHeaderText=Attribut
+
+FileChooser.fileDateHeaderText=\u00C4ndrad
+
+FileChooser.fileNameHeaderText=Namn
+
+FileChooser.fileNameLabelText=Filnamn:
+
+FileChooser.fileSizeHeaderText=Storlek
+
+FileChooser.fileTypeHeaderText=Typ
+
+FileChooser.filesOfTypeLabelText=Filformat:
+
+FileChooser.homeFolderAccessibleName=Hem
+
+FileChooser.homeFolderToolTipText=Hem
+
+FileChooser.listViewActionLabelText=Lista
+
+FileChooser.listViewButtonAccessibleName=Lista
+
+FileChooser.listViewButtonToolTipText=Lista
+
+FileChooser.lookInLabelText=S\u00F6k i:
+
+FileChooser.newFolderAccessibleName=Ny mapp
+
+FileChooser.newFolderActionLabelText=Ny mapp
+
+FileChooser.newFolderToolTipText=Skapa ny mapp
+
+FileChooser.refreshActionLabelText=Uppdatera
+
+FileChooser.saveInLabelText=Spara i:
+
+FileChooser.upFolderAccessibleName=Upp
+
+FileChooser.upFolderToolTipText=Upp en niv\u00E5
+
+FileChooser.viewMenuLabelText=Vy
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_th.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_th.properties
new file mode 100644
index 0000000..3b25828
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_th.properties
@@ -0,0 +1,19 @@
+SystemMenu.showHeapStatus=\u0e41\u0e2a\u0e14\u0e07\u0e2a\u0e16\u0e32\u0e19\u0e30\u0e42\u0e14\u0e22\u0e23\u0e27\u0e21
+
+Tooltip.contentsNotSaved=\u0e22\u0e31\u0e07\u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01\u0e2d\u0e07\u0e04\u0e4c\u0e1b\u0e23\u0e30\u0e01\u0e2d\u0e1a
+
+Tooltip.heapStatusPanel=\u0e2a\u0e16\u0e32\u0e19\u0e30\u0e42\u0e14\u0e22\u0e23\u0e27\u0e21 \u0e04\u0e25\u0e34\u0e01\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e40\u0e23\u0e35\u0e22\u0e01\u0e43\u0e0a\u0e49\u0e15\u0e31\u0e27\u0e23\u0e27\u0e1a\u0e23\u0e27\u0e21\u0e02\u0e22\u0e30
+
+Xoetrope.hue=\u0e2a\u0e35\u0e2a\u0e31\u0e19
+
+Xoetrope.brightness=\u0e04\u0e27\u0e32\u0e21\u0e2a\u0e27\u0e48\u0e32\u0e07
+
+Xoetrope.saturation=\u0e04\u0e27\u0e32\u0e21\u0e40\u0e02\u0e49\u0e21
+
+Xoetrope.warm=\u0e2d\u0e1a\u0e2d\u0e38\u0e48\u0e19
+
+Xoetrope.cold=\u0e40\u0e22\u0e47\u0e19\u0e0a\u0e32
+
+Xoetrope.decimalRGB=RGB \u0e40\u0e1b\u0e47\u0e19\u0e17\u0e28\u0e19\u0e34\u0e22\u0e21
+
+Xoetrope.webSafeColors=\u0e43\u0e0a\u0e49\u0e2a\u0e35\u0e17\u0e35\u0e48\u0e1b\u0e25\u0e2d\u0e14\u0e20\u0e31\u0e22\u0e1a\u0e19\u0e40\u0e27\u0e47\u0e1a
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_tr.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_tr.properties
new file mode 100644
index 0000000..afeef26
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_tr.properties
@@ -0,0 +1,29 @@
+# Turkish translation by Magnus de Pourbaix and Oncu Altuntas
+
+SystemMenu.close=Kapat
+
+SystemMenu.iconify=Simge Durumuna K\u00FC\u00E7\u00FClt
+
+SystemMenu.restore=\u00D6nceki boyut
+
+SystemMenu.maximize=Ekran\u0131 Kapla
+
+SystemMenu.showHeapStatus=K\u00fcme durumunu g\u00f6ster
+
+Tooltip.contentsNotSaved=i\u00e7erik kaydedilmedi
+
+Tooltip.heapStatusPanel=K\u00fcme durumu. \u00c7\u00f6p toplay\u0131c\u0131y\u0131 \u00e7al\u0131\u015ft\u0131rmak i\u00e7in t\u0131klay\u0131n
+
+Xoetrope.hue=Renk tonu
+
+Xoetrope.brightness=Parlakl\u0131k
+
+Xoetrope.saturation=Doygunluk
+
+Xoetrope.warm=ILIK
+
+Xoetrope.cold=SO\u011eUK
+
+Xoetrope.decimalRGB=Ondal\u0131k RGB
+
+Xoetrope.webSafeColors=Web i\u00e7in g\u00fcvenli renkler kullan
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_vi.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_vi.properties
new file mode 100644
index 0000000..8c22e0f
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_vi.properties
@@ -0,0 +1,19 @@
+#Vietnamese translation by Thang Nguyen
+
+SystemMenu.close = \u0110\u00F3ng
+SystemMenu.iconify = T\u1ED1i thi\u1EC3u
+SystemMenu.maximize = T\u1ED1i \u0111a
+SystemMenu.restore = Ph\u1EE5c h\u1ED3i
+SystemMenu.showHeapStatus = Hi\u1EC3n th\u1ECB thanh tr\u1EA1ng th\u00E1i
+SystemMenu.skins = B\u1ED9 m\u00E0u
+
+Tooltip.contentsNotSaved = N\u1ED9i dung ch\u01B0a \u0111\u01B0\u1EE3c l\u01B0u
+Tooltip.heapStatusPanel = Thanh tr\u1EA1ng th\u00E1i. Nh\u1EA5p chu\u1ED9t \u0111\u1EC3 d\u1ECDn d\u1EB9p b\u1ED9 nh\u1EDB
+
+Xoetrope.brightness = \u0110\u1ED9 s\u00E1ng
+Xoetrope.cold = L\u1EA0NH
+Xoetrope.decimalRGB = RGB th\u1EADp ph\u00E2n
+Xoetrope.hue = M\u00E0u s\u1EAFc
+Xoetrope.saturation = \u0110\u1ED9 s\u1EABm
+Xoetrope.warm = N\u00D3NG
+Xoetrope.webSafeColors = S\u1EED d\u1EE5ng m\u00E0u web
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_zh_CN.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_zh_CN.properties
new file mode 100644
index 0000000..6233927
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_zh_CN.properties
@@ -0,0 +1,144 @@
+# Chinese translation by Pprun and Qil Wong
+
+SystemMenu.close=\u5173\u95ed
+
+SystemMenu.iconify=\u6700\u5c0f\u5316
+
+SystemMenu.restore=\u6062\u590d
+
+SystemMenu.maximize=\u6700\u5927\u5316
+
+SystemMenu.showHeapStatus=\u663e\u793a\u5783\u573e\uff08GC\uff09\u5806\u72b6\u6001
+
+Tooltip.contentsNotSaved=\u5185\u5bb9\u672a\u4fdd\u5b58
+
+Tooltip.heapStatusPanel=\u5355\u51fb\u8fd0\u884c\u5783\u573e\u6536\u96c6
+
+Xoetrope.hue=\u8272\u8c03
+
+Xoetrope.brightness=\u4eae\u5ea6
+
+Xoetrope.saturation=\u9971\u548c\u5ea6
+
+Xoetrope.warm=\u6696\u8272\u8c03
+
+Xoetrope.cold=\u51b7\u8272\u8c03
+
+Xoetrope.decimalRGB=\u5341\u8fdb\u5236RGB
+
+Xoetrope.webSafeColors=\u4ec5\u4f7f\u7528WEB\u8272
+
+# File chooser strings from com.sun.swing.internal.plaf.basic.resources.basic
+FileChooser.acceptAllFileFilterText=\u6240\u6709\u6587\u4EF6
+
+FileChooser.cancelButtonMnemonic=67
+
+FileChooser.cancelButtonText=\u53D6\u6D88
+
+FileChooser.cancelButtonToolTipText=\u4E2D\u6B62\u6587\u4EF6\u9009\u62E9\u5668\u5BF9\u8BDD\u6846
+
+FileChooser.directoryDescriptionText=\u76EE\u5F55
+
+FileChooser.directoryOpenButtonMnemonic=79
+
+FileChooser.directoryOpenButtonText=\u6253\u5F00
+
+FileChooser.directoryOpenButtonToolTipText=\u6253\u5F00\u9009\u62E9\u7684\u76EE\u5F55
+
+FileChooser.fileDescriptionText=\u666E\u901A\u7684\u6587\u4EF6
+
+FileChooser.fileSizeGigaBytes={0} GB
+
+FileChooser.fileSizeKiloBytes={0} KB
+
+FileChooser.fileSizeMegaBytes={0} MB
+
+FileChooser.helpButtonMnemonic=72
+
+FileChooser.helpButtonText=\u5E2E\u52A9(H)
+
+FileChooser.helpButtonToolTipText=\u6587\u4EF6\u9009\u62E9\u5668\u5E2E\u52A9
+
+FileChooser.newFolderErrorSeparator=:
+
+FileChooser.newFolderErrorText=\u521B\u5EFA\u65B0\u7684\u6587\u4EF6\u5939\u65F6\u53D1\u751F\u9519\u8BEF
+
+FileChooser.openButtonMnemonic=79
+
+FileChooser.openButtonText=\u6253\u5F00
+
+FileChooser.openButtonToolTipText=\u6253\u5F00\u9009\u62E9\u7684\u6587\u4EF6
+
+FileChooser.openDialogTitleText=\u6253\u5F00
+
+FileChooser.other.newFolder=\u65B0\u5EFA\u6587\u4EF6\u5939
+
+FileChooser.other.newFolder.subsequent=\u65B0\u5EFA\u6587\u4EF6\u5939.{0}
+
+FileChooser.saveButtonMnemonic=83
+
+FileChooser.saveButtonText=\u4FDD\u5B58
+
+FileChooser.saveButtonToolTipText=\u4FDD\u5B58\u9009\u62E9\u7684\u6587\u4EF6
+
+FileChooser.saveDialogTitleText=\u4FDD\u5B58
+
+FileChooser.updateButtonMnemonic=85
+
+FileChooser.updateButtonText=\u66F4\u65B0(U)
+
+FileChooser.updateButtonToolTipText=\u66F4\u65B0\u76EE\u5F55\u5217\u8868
+
+FileChooser.win32.newFolder=\u65B0\u5EFA\u6587\u4EF6\u5939
+
+FileChooser.win32.newFolder.subsequent=\u65B0\u5EFA\u6587\u4EF6\u5939 ({0})
+
+# File chooser strings from com.sun.swing.internal.plaf.metal.resources.metal
+FileChooser.detailsViewActionLabelText=\u8BE6\u7EC6\u4FE1\u606F
+
+FileChooser.detailsViewButtonAccessibleName=\u8BE6\u7EC6\u4FE1\u606F
+
+FileChooser.detailsViewButtonToolTipText=\u8BE6\u7EC6\u4FE1\u606F
+
+FileChooser.fileAttrHeaderText=\u5C5E\u6027
+
+FileChooser.fileDateHeaderText=\u4FEE\u6B63\u7248
+
+FileChooser.fileNameHeaderText=\u540D\u79F0
+
+FileChooser.fileNameLabelText=\u6587\u4EF6\u540D\uFF1A
+
+FileChooser.fileSizeHeaderText=\u5927\u5C0F
+
+FileChooser.fileTypeHeaderText=\u7C7B\u578B
+
+FileChooser.filesOfTypeLabelText=\u6587\u4EF6\u7C7B\u578B\uFF1A
+
+FileChooser.homeFolderAccessibleName=\u8D77\u59CB\u76EE\u5F55
+
+FileChooser.homeFolderToolTipText=\u8D77\u59CB\u76EE\u5F55
+
+FileChooser.listViewActionLabelText=\u5217\u8868
+
+FileChooser.listViewButtonAccessibleName=\u5217\u8868
+
+FileChooser.listViewButtonToolTipText=\u5217\u8868
+
+FileChooser.lookInLabelText=\u67E5\u770B\uFF1A
+
+FileChooser.newFolderAccessibleName=\u65B0\u5EFA\u6587\u4EF6\u5939
+
+FileChooser.newFolderActionLabelText=\u65B0\u5EFA\u6587\u4EF6\u5939
+
+FileChooser.newFolderToolTipText=\u521B\u5EFA\u65B0\u7684\u6587\u4EF6\u5939
+
+FileChooser.refreshActionLabelText=\u5237\u65B0
+
+FileChooser.saveInLabelText=\u4FDD\u5B58\uFF1A
+
+FileChooser.upFolderAccessibleName=\u5411\u4E0A
+
+FileChooser.upFolderToolTipText=\u5411\u4E0A\u4E00\u5C42
+
+FileChooser.viewMenuLabelText=\u89C6\u56FE
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_zh_HK.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_zh_HK.properties
new file mode 100644
index 0000000..d75c095
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_zh_HK.properties
@@ -0,0 +1,134 @@
+SystemMenu.showHeapStatus=\u986f\u793a\u5806\u7a4d\u72c0\u614b
+
+Tooltip.contentsNotSaved=\u5167\u5bb9\u672a\u5132\u5b58
+
+Tooltip.heapStatusPanel=\u5806\u7a4d\u72c0\u614b\uff0c\u9ede\u64ca\u4f86\u57f7\u884c\u5783\u573e\u56de\u6536
+
+Xoetrope.hue=\u8272\u5f69
+
+Xoetrope.brightness=\u5149\u6697
+
+Xoetrope.saturation=\u98fd\u548c\u5ea6
+
+Xoetrope.warm=\u6696\u8272
+
+Xoetrope.cold=\u51b7\u8272
+
+Xoetrope.decimalRGB=\u5341\u9032\u4f4d\u5236RGB
+
+Xoetrope.webSafeColors=\u4f7f\u7528\u7db2\u9801\u5b89\u5168\u8272
+
+# File chooser strings from com.sun.swing.internal.plaf.basic.resources.basic
+FileChooser.acceptAllFileFilterText=\u6240\u6709\u6A94\u6848
+
+FileChooser.cancelButtonMnemonic=67
+
+FileChooser.cancelButtonText=\u53D6\u6D88
+
+FileChooser.cancelButtonToolTipText=\u4E2D\u65B7\u300C\u6A94\u6848\u9078\u64C7\u5668\u300D\u5C0D\u8A71\u65B9\u584A
+
+FileChooser.directoryDescriptionText=\u76EE\u9304
+
+FileChooser.directoryOpenButtonMnemonic=79
+
+FileChooser.directoryOpenButtonText=\u958B\u555F
+
+FileChooser.directoryOpenButtonToolTipText=\u958B\u555F\u9078\u53D6\u7684\u76EE\u9304
+
+FileChooser.fileDescriptionText=\u4E00\u822C\u6A94\u6848
+
+FileChooser.fileSizeGigaBytes={0} GB
+
+FileChooser.fileSizeKiloBytes={0} KB
+
+FileChooser.fileSizeMegaBytes={0} MB
+
+FileChooser.helpButtonMnemonic=72
+
+FileChooser.helpButtonText=\u8AAA\u660E(H)
+
+FileChooser.helpButtonToolTipText=\u300C\u6A94\u6848\u9078\u64C7\u5668\u300D\u8AAA\u660E
+
+FileChooser.newFolderErrorSeparator=:
+
+FileChooser.newFolderErrorText=\u5EFA\u7ACB\u65B0\u6A94\u6848\u593E\u6642\u767C\u751F\u932F\u8AA4
+
+FileChooser.openButtonMnemonic=79
+
+FileChooser.openButtonText=\u958B\u555F
+
+FileChooser.openButtonToolTipText=\u958B\u555F\u9078\u53D6\u7684\u6A94\u6848
+
+FileChooser.openDialogTitleText=\u958B\u555F
+
+FileChooser.other.newFolder=\u65B0\u8CC7\u6599\u593E
+
+FileChooser.other.newFolder.subsequent=\u65B0\u8CC7\u6599\u593E.{0}
+
+FileChooser.saveButtonMnemonic=83
+
+FileChooser.saveButtonText=\u5132\u5B58
+
+FileChooser.saveButtonToolTipText=\u5132\u5B58\u9078\u53D6\u7684\u6A94\u6848
+
+FileChooser.saveDialogTitleText=\u5132\u5B58
+
+FileChooser.updateButtonMnemonic=85
+
+FileChooser.updateButtonText=\u66F4\u65B0(U)
+
+FileChooser.updateButtonToolTipText=\u66F4\u65B0\u76EE\u9304\u6E05\u55AE
+
+FileChooser.win32.newFolder=\u65B0\u8CC7\u6599\u593E
+
+FileChooser.win32.newFolder.subsequent=\u65B0\u8CC7\u6599\u593E ({0})
+
+# File chooser strings from com.sun.swing.internal.plaf.metal.resources.metal
+FileChooser.detailsViewActionLabelText=\u8A73\u7D30\u8CC7\u8A0A
+
+FileChooser.detailsViewButtonAccessibleName=\u8A73\u7D30\u8CC7\u8A0A
+
+FileChooser.detailsViewButtonToolTipText=\u8A73\u7D30\u8CC7\u8A0A
+
+FileChooser.fileAttrHeaderText=\u5C6C\u6027
+
+FileChooser.fileDateHeaderText=\u5DF2\u4FEE\u6539
+
+FileChooser.fileNameHeaderText=\u540D\u7A31
+
+FileChooser.fileNameLabelText=\u6A94\u6848\u540D\u7A31\uFE55
+
+FileChooser.fileSizeHeaderText=\u5927\u5C0F
+
+FileChooser.fileTypeHeaderText=\u985E\u578B
+
+FileChooser.filesOfTypeLabelText=\u6A94\u6848\u985E\u578B\uFE55
+
+FileChooser.homeFolderAccessibleName=\u4E3B\u76EE\u9304
+
+FileChooser.homeFolderToolTipText=\u4E3B\u76EE\u9304
+
+FileChooser.listViewActionLabelText=\u6E05\u55AE
+
+FileChooser.listViewButtonAccessibleName=\u6E05\u55AE
+
+FileChooser.listViewButtonToolTipText=\u6E05\u55AE
+
+FileChooser.lookInLabelText=\u67E5\u770B\uFE55
+
+FileChooser.newFolderAccessibleName=\u65B0\u8CC7\u6599\u593E
+
+FileChooser.newFolderActionLabelText=\u65B0\u8CC7\u6599\u593E
+
+FileChooser.newFolderToolTipText=\u5EFA\u7ACB\u65B0\u8CC7\u6599\u593E
+
+FileChooser.refreshActionLabelText=\u66F4\u65B0
+
+FileChooser.saveInLabelText=\u5132\u5B58\u65BC\uFF1A
+
+FileChooser.upFolderAccessibleName=\u5F80\u4E0A
+
+FileChooser.upFolderToolTipText=\u5F80\u4E0A\u4E00\u5C64
+
+FileChooser.viewMenuLabelText=\u6AA2\u8996
+
diff --git a/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_zh_TW.properties b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_zh_TW.properties
new file mode 100644
index 0000000..b72fd6b
--- /dev/null
+++ b/substance/src/main/resources/org/pushingpixels/substance/internal/resources/Labels_zh_TW.properties
@@ -0,0 +1,134 @@
+SystemMenu.showHeapStatus=\u986f\u793a\u5806\u7a4d\u72c0\u614b
+
+Tooltip.contentsNotSaved=\u5167\u5bb9\u672a\u5132\u5b58
+
+Tooltip.heapStatusPanel=\u5c0d\u6a5f\u72c0\u614b\u3002\u9ede\u9078\u672c\u7d10\u57f7\u884c\u5ee2\u6599\u6536\u96c6\u5668\u3002
+
+Xoetrope.hue=\u8272\u8abf
+
+Xoetrope.brightness=\u4eae\u5ea6
+
+Xoetrope.saturation=\u98fd\u548c\u5ea6
+
+Xoetrope.warm=\u6eab\u6696
+
+Xoetrope.cold=\u5bd2\u51b7
+
+Xoetrope.decimalRGB=RGB \u503c
+
+Xoetrope.webSafeColors=\u4f7f\u7528\u9069\u65bc\u7db2\u969b\u7db2\u8def\u7684\u984f\u8272
+
+# File chooser strings from com.sun.swing.internal.plaf.basic.resources.basic
+FileChooser.acceptAllFileFilterText=\u6240\u6709\u6A94\u6848
+
+FileChooser.cancelButtonMnemonic=67
+
+FileChooser.cancelButtonText=\u53D6\u6D88
+
+FileChooser.cancelButtonToolTipText=\u4E2D\u65B7\u300C\u6A94\u6848\u9078\u64C7\u5668\u300D\u5C0D\u8A71\u65B9\u584A
+
+FileChooser.directoryDescriptionText=\u76EE\u9304
+
+FileChooser.directoryOpenButtonMnemonic=79
+
+FileChooser.directoryOpenButtonText=\u958B\u555F
+
+FileChooser.directoryOpenButtonToolTipText=\u958B\u555F\u9078\u53D6\u7684\u76EE\u9304
+
+FileChooser.fileDescriptionText=\u4E00\u822C\u6A94\u6848
+
+FileChooser.fileSizeGigaBytes={0} GB
+
+FileChooser.fileSizeKiloBytes={0} KB
+
+FileChooser.fileSizeMegaBytes={0} MB
+
+FileChooser.helpButtonMnemonic=72
+
+FileChooser.helpButtonText=\u8AAA\u660E(H)
+
+FileChooser.helpButtonToolTipText=\u300C\u6A94\u6848\u9078\u64C7\u5668\u300D\u8AAA\u660E
+
+FileChooser.newFolderErrorSeparator=:
+
+FileChooser.newFolderErrorText=\u5EFA\u7ACB\u65B0\u6A94\u6848\u593E\u6642\u767C\u751F\u932F\u8AA4
+
+FileChooser.openButtonMnemonic=79
+
+FileChooser.openButtonText=\u958B\u555F
+
+FileChooser.openButtonToolTipText=\u958B\u555F\u9078\u53D6\u7684\u6A94\u6848
+
+FileChooser.openDialogTitleText=\u958B\u555F
+
+FileChooser.other.newFolder=\u65B0\u8CC7\u6599\u593E
+
+FileChooser.other.newFolder.subsequent=\u65B0\u8CC7\u6599\u593E.{0}
+
+FileChooser.saveButtonMnemonic=83
+
+FileChooser.saveButtonText=\u5132\u5B58
+
+FileChooser.saveButtonToolTipText=\u5132\u5B58\u9078\u53D6\u7684\u6A94\u6848
+
+FileChooser.saveDialogTitleText=\u5132\u5B58
+
+FileChooser.updateButtonMnemonic=85
+
+FileChooser.updateButtonText=\u66F4\u65B0(U)
+
+FileChooser.updateButtonToolTipText=\u66F4\u65B0\u76EE\u9304\u6E05\u55AE
+
+FileChooser.win32.newFolder=\u65B0\u8CC7\u6599\u593E
+
+FileChooser.win32.newFolder.subsequent=\u65B0\u8CC7\u6599\u593E ({0})
+
+# File chooser strings from com.sun.swing.internal.plaf.metal.resources.metal
+FileChooser.detailsViewActionLabelText=\u8A73\u7D30\u8CC7\u8A0A
+
+FileChooser.detailsViewButtonAccessibleName=\u8A73\u7D30\u8CC7\u8A0A
+
+FileChooser.detailsViewButtonToolTipText=\u8A73\u7D30\u8CC7\u8A0A
+
+FileChooser.fileAttrHeaderText=\u5C6C\u6027
+
+FileChooser.fileDateHeaderText=\u5DF2\u4FEE\u6539
+
+FileChooser.fileNameHeaderText=\u540D\u7A31
+
+FileChooser.fileNameLabelText=\u6A94\u6848\u540D\u7A31\uFE55
+
+FileChooser.fileSizeHeaderText=\u5927\u5C0F
+
+FileChooser.fileTypeHeaderText=\u985E\u578B
+
+FileChooser.filesOfTypeLabelText=\u6A94\u6848\u985E\u578B\uFE55
+
+FileChooser.homeFolderAccessibleName=\u4E3B\u76EE\u9304
+
+FileChooser.homeFolderToolTipText=\u4E3B\u76EE\u9304
+
+FileChooser.listViewActionLabelText=\u6E05\u55AE
+
+FileChooser.listViewButtonAccessibleName=\u6E05\u55AE
+
+FileChooser.listViewButtonToolTipText=\u6E05\u55AE
+
+FileChooser.lookInLabelText=\u67E5\u770B\uFE55
+
+FileChooser.newFolderAccessibleName=\u65B0\u8CC7\u6599\u593E
+
+FileChooser.newFolderActionLabelText=\u65B0\u8CC7\u6599\u593E
+
+FileChooser.newFolderToolTipText=\u5EFA\u7ACB\u65B0\u8CC7\u6599\u593E
+
+FileChooser.refreshActionLabelText=\u66F4\u65B0
+
+FileChooser.saveInLabelText=\u5132\u5B58\u65BC\uFF1A
+
+FileChooser.upFolderAccessibleName=\u5F80\u4E0A
+
+FileChooser.upFolderToolTipText=\u5F80\u4E0A\u4E00\u5C64
+
+FileChooser.viewMenuLabelText=\u6AA2\u8996
+
diff --git a/substance/src/main/resources/resource/32/dialog-error.png b/substance/src/main/resources/resource/32/dialog-error.png
new file mode 100644
index 0000000..cdd95ba
Binary files /dev/null and b/substance/src/main/resources/resource/32/dialog-error.png differ
diff --git a/substance/src/main/resources/resource/32/dialog-information.png b/substance/src/main/resources/resource/32/dialog-information.png
new file mode 100644
index 0000000..2ac5747
Binary files /dev/null and b/substance/src/main/resources/resource/32/dialog-information.png differ
diff --git a/substance/src/main/resources/resource/32/dialog-warning.png b/substance/src/main/resources/resource/32/dialog-warning.png
new file mode 100644
index 0000000..7233d45
Binary files /dev/null and b/substance/src/main/resources/resource/32/dialog-warning.png differ
diff --git a/substance/src/main/resources/resource/32/help-browser.png b/substance/src/main/resources/resource/32/help-browser.png
new file mode 100644
index 0000000..d60425f
Binary files /dev/null and b/substance/src/main/resources/resource/32/help-browser.png differ
diff --git a/substance/src/main/resources/resource/TangoFamfamIcons.license b/substance/src/main/resources/resource/TangoFamfamIcons.license
new file mode 100644
index 0000000..0bc7785
--- /dev/null
+++ b/substance/src/main/resources/resource/TangoFamfamIcons.license
@@ -0,0 +1,260 @@
+Attribution-ShareAlike 2.5
+CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT
+PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT
+CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES
+THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO
+WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS
+LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
+
+License
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS
+CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK
+IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE
+OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT
+LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT
+AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR
+GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR
+ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
+
+1. Definitions
+
+ 1. "Collective Work" means a work, such as a periodical issue,
+ anthology or encyclopedia, in which the Work in its entirety
+ in unmodified form, along with a number of other contributions,
+ constituting separate and independent works in themselves,
+ are assembled into a collective whole. A work that constitutes
+ a Collective Work will not be considered a Derivative Work
+ (as defined below) for the purposes of this License.
+ 2. "Derivative Work" means a work based upon the Work or upon
+ the Work and other pre-existing works, such as a translation,
+ musical arrangement, dramatization, fictionalization, motion
+ picture version, sound recording, art reproduction, abridgment,
+ condensation, or any other form in which the Work may be recast,
+ transformed, or adapted, except that a work that constitutes
+ a Collective Work will not be considered a Derivative Work for
+ the purpose of this License. For the avoidance of doubt, where
+ the Work is a musical composition or sound recording, the
+ synchronization of the Work in timed-relation with a moving
+ image ("synching") will be considered a Derivative Work for
+ the purpose of this License.
+ 3. "Licensor" means the individual or entity that offers the
+ Work under the terms of this License.
+ 4. "Original Author" means the individual or entity who
+ created the Work.
+ 5. "Work" means the copyrightable work of authorship offered
+ under the terms of this License.
+ 6. "You" means an individual or entity exercising rights under
+ this License who has not previously violated the terms of this
+ License with respect to the Work, or who has received express
+ permission from the Licensor to exercise rights under this
+ License despite a previous violation.
+ 7. "License Elements" means the following high-level license
+ attributes as selected by Licensor and indicated in the title
+ of this License: Attribution, ShareAlike.
+
+2. Fair Use Rights. Nothing in this license is intended to reduce,
+limit, or restrict any rights arising from fair use, first sale
+or other limitations on the exclusive rights of the copyright
+owner under copyright law or other applicable laws.
+
+3. License Grant. Subject to the terms and conditions of this
+License, Licensor hereby grants You a worldwide, royalty-free,
+non-exclusive, perpetual (for the duration of the applicable
+copyright) license to exercise the rights in the Work as stated
+below:
+
+ 1. to reproduce the Work, to incorporate the Work into one
+ or more Collective Works, and to reproduce the Work as
+ incorporated in the Collective Works;
+ 2. to create and reproduce Derivative Works;
+ 3. to distribute copies or phonorecords of, display publicly,
+ perform publicly, and perform publicly by means of a digital
+ audio transmission the Work including as incorporated in
+ Collective Works;
+ 4. to distribute copies or phonorecords of, display publicly,
+ perform publicly, and perform publicly by means of a digital
+ audio transmission Derivative Works.
+ 5.
+
+ For the avoidance of doubt, where the work is a musical composition:
+ 1. Performance Royalties Under Blanket Licenses. Licensor
+ waives the exclusive right to collect, whether individually
+ or via a performance rights society (e.g. ASCAP, BMI,
+ SESAC), royalties for the public performance or public
+ digital performance (e.g. webcast) of the Work.
+ 2. Mechanical Rights and Statutory Royalties. Licensor
+ waives the exclusive right to collect, whether individually
+ or via a music rights society or designated agent (e.g.
+ Harry Fox Agency), royalties for any phonorecord You
+ create from the Work ("cover version") and distribute, subject
+ to the compulsory license created by 17 USC Section 115 of the
+ US Copyright Act (or the equivalent in other jurisdictions).
+ 6. Webcasting Rights and Statutory Royalties. For the avoidance
+ of doubt, where the Work is a sound recording, Licensor waives
+ the exclusive right to collect, whether individually or via a
+ performance-rights society (e.g. SoundExchange), royalties for
+ the public digital performance (e.g. webcast) of the Work, subject
+ to the compulsory license created by 17 USC Section 114 of the US
+ Copyright Act (or the equivalent in other jurisdictions).
+
+The above rights may be exercised in all media and formats whether
+now known or hereafter devised. The above rights include the right
+to make such modifications as are technically necessary to exercise
+the rights in other media and formats. All rights not expressly
+granted by Licensor are hereby reserved.
+
+4. Restrictions.The license granted in Section 3 above is expressly
+made subject to and limited by the following restrictions:
+
+ 1. You may distribute, publicly display, publicly perform,
+ or publicly digitally perform the Work only under the terms
+ of this License, and You must include a copy of, or the
+ Uniform Resource Identifier for, this License with every copy
+ or phonorecord of the Work You distribute, publicly display,
+ publicly perform, or publicly digitally perform. You may not
+ offer or impose any terms on the Work that alter or restrict
+ the terms of this License or the recipients' exercise of the
+ rights granted hereunder. You may not sublicense the Work.
+ You must keep intact all notices that refer to this License
+ and to the disclaimer of warranties. You may not distribute,
+ publicly display, publicly perform, or publicly digitally
+ perform the Work with any technological measures that control
+ access or use of the Work in a manner inconsistent with the
+ terms of this License Agreement. The above applies to the Work
+ as incorporated in a Collective Work, but this does not require
+ the Collective Work apart from the Work itself to be made
+ subject to the terms of this License. If You create a Collective
+ Work, upon notice from any Licensor You must, to the extent
+ practicable, remove from the Collective Work any credit as
+ required by clause 4(c), as requested. If You create a Derivative
+ Work, upon notice from any Licensor You must, to the extent
+ practicable, remove from the Derivative Work any credit as required
+ by clause 4(c), as requested.
+ 2. You may distribute, publicly display, publicly perform, or
+ publicly digitally perform a Derivative Work only under the
+ terms of this License, a later version of this License with
+ the same License Elements as this License, or a Creative Commons
+ iCommons license that contains the same License Elements as this
+ License (e.g. Attribution-ShareAlike 2.5 Japan). You must
+ include a copy of, or the Uniform Resource Identifier for,
+ this License or other license specified in the previous sentence
+ with every copy or phonorecord of each Derivative Work You
+ distribute, publicly display, publicly perform, or publicly
+ digitally perform. You may not offer or impose any terms on the
+ Derivative Works that alter or restrict the terms of this License
+ or the recipients' exercise of the rights granted hereunder, and
+ You must keep intact all notices that refer to this License and
+ to the disclaimer of warranties. You may not distribute, publicly
+ display, publicly perform, or publicly digitally perform the Derivative
+ Work with any technological measures that control access or use of the
+ Work in a manner inconsistent with the terms of this License Agreement.
+ The above applies to the Derivative Work as incorporated in a Collective
+ Work, but this does not require the Collective Work apart from the
+ Derivative Work itself to be made subject to the terms of this License.
+ 3. If you distribute, publicly display, publicly perform, or
+ publicly digitally perform the Work or any Derivative Works or
+ Collective Works, You must keep intact all copyright notices for
+ the Work and provide, reasonable to the medium or means You are
+ utilizing: (i) the name of the Original Author (or pseudonym, if
+ applicable) if supplied, and/or (ii) if the Original Author and/or
+ Licensor designate another party or parties (e.g. a sponsor institute,
+ publishing entity, journal) for attribution in Licensor's copyright
+ notice, terms of service or by other reasonable means, the name of
+ such party or parties; the title of the Work if supplied; to the
+ extent reasonably practicable, the Uniform Resource Identifier, if
+ any, that Licensor specifies to be associated with the Work, unless
+ such URI does not refer to the copyright notice or licensing
+ information for the Work; and in the case of a Derivative Work, a
+ credit identifying the use of the Work in the Derivative Work (e.g.,
+ "French translation of the Work by Original Author," or "Screenplay
+ based on original Work by Original Author"). Such credit may be
+ implemented in any reasonable manner; provided, however, that in
+ the case of a Derivative Work or Collective Work, at a minimum
+ such credit will appear where any other comparable authorship credit
+ appears and in a manner at least as prominent as such other comparable
+ authorship credit.
+
+
+5. Representations, Warranties and Disclaimer
+
+UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR
+OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES
+OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY
+OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE,
+MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT,
+OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE
+OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS
+DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION
+MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY
+APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON
+ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL,
+PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR
+THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+
+ 1. This License and the rights granted hereunder will
+ terminate automatically upon any breach by You of the terms
+ of this License. Individuals or entities who have received
+ Derivative Works or Collective Works from You under this
+ License, however, will not have their licenses terminated
+ provided such individuals or entities remain in full compliance
+ with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive
+ any termination of this License.
+ 2. Subject to the above terms and conditions, the license
+ granted here is perpetual (for the duration of the applicable
+ copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
+
+8. Miscellaneous
+
+ 1. Each time You distribute or publicly digitally perform
+ the Work or a Collective Work, the Licensor offers to the
+ recipient a license to the Work on the same terms and conditions
+ as the license granted to You under this License.
+ 2. Each time You distribute or publicly digitally perform a
+ Derivative Work, Licensor offers to the recipient a license
+ to the original Work on the same terms and conditions as the
+ license granted to You under this License.
+ 3. If any provision of this License is invalid or unenforceable
+ under applicable law, it shall not affect the validity or
+ enforceability of the remainder of the terms of this License,
+ and without further action by the parties to this agreement,
+ such provision shall be reformed to the minimum extent necessary
+ to make such provision valid and enforceable.
+ 4. No term or provision of this License shall be deemed waived
+ and no breach consented to unless such waiver or consent shall
+ be in writing and signed by the party to be charged with such
+ waiver or consent.
+ 5. This License constitutes the entire agreement between the
+ parties with respect to the Work licensed here. There are no
+ understandings, agreements or representations with respect to
+ the Work not specified here. Licensor shall not be bound by
+ any additional provisions that may appear in any communication
+ from You. This License may not be modified without the mutual
+ written agreement of the Licensor and You.
+
+Creative Commons is not a party to this License, and makes no
+warranty whatsoever in connection with the Work. Creative Commons
+will not be liable to You or any party on any legal theory for
+any damages whatsoever, including without limitation any general,
+special, incidental or consequential damages arising in connection
+to this license. Notwithstanding the foregoing two (2) sentences,
+if Creative Commons has expressly identified itself as the Licensor
+hereunder, it shall have all rights and obligations of Licensor.
+
+Except for the limited purpose of indicating to the public that
+the Work is licensed under the CCPL, neither party will use the
+trademark "Creative Commons" or any related trademark or logo
+of Creative Commons without the prior written consent of Creative
+Commons. Any permitted use will be in compliance with Creative
+Commons' then-current trademark usage guidelines, as may be
+published on its website or otherwise made available upon request
+from time to time.
+
+Creative Commons may be contacted at http://creativecommons.org/.
diff --git a/substance/src/main/resources/resource/application_view_detail.png b/substance/src/main/resources/resource/application_view_detail.png
new file mode 100644
index 0000000..aba044b
Binary files /dev/null and b/substance/src/main/resources/resource/application_view_detail.png differ
diff --git a/substance/src/main/resources/resource/application_view_list.png b/substance/src/main/resources/resource/application_view_list.png
new file mode 100644
index 0000000..acc30b8
Binary files /dev/null and b/substance/src/main/resources/resource/application_view_list.png differ
diff --git a/substance/src/main/resources/resource/brushed.gif b/substance/src/main/resources/resource/brushed.gif
new file mode 100644
index 0000000..8a44551
Binary files /dev/null and b/substance/src/main/resources/resource/brushed.gif differ
diff --git a/substance/src/main/resources/resource/computer.png b/substance/src/main/resources/resource/computer.png
new file mode 100644
index 0000000..c9cc93e
Binary files /dev/null and b/substance/src/main/resources/resource/computer.png differ
diff --git a/substance/src/main/resources/resource/drive-harddisk.png b/substance/src/main/resources/resource/drive-harddisk.png
new file mode 100644
index 0000000..5c3b858
Binary files /dev/null and b/substance/src/main/resources/resource/drive-harddisk.png differ
diff --git a/substance/src/main/resources/resource/folder-new.png b/substance/src/main/resources/resource/folder-new.png
new file mode 100644
index 0000000..628f4d5
Binary files /dev/null and b/substance/src/main/resources/resource/folder-new.png differ
diff --git a/substance/src/main/resources/resource/folder.png b/substance/src/main/resources/resource/folder.png
new file mode 100644
index 0000000..901edc9
Binary files /dev/null and b/substance/src/main/resources/resource/folder.png differ
diff --git a/substance/src/main/resources/resource/go-up.png b/substance/src/main/resources/resource/go-up.png
new file mode 100644
index 0000000..fa9a7d7
Binary files /dev/null and b/substance/src/main/resources/resource/go-up.png differ
diff --git a/substance/src/main/resources/resource/katakana.license b/substance/src/main/resources/resource/katakana.license
new file mode 100644
index 0000000..02d1047
--- /dev/null
+++ b/substance/src/main/resources/resource/katakana.license
@@ -0,0 +1 @@
+D3-Factorism-Katakana is a free font created by Yoshiyasu Ito / DigitalDreamDesign
\ No newline at end of file
diff --git a/substance/src/main/resources/resource/katakana.ttf b/substance/src/main/resources/resource/katakana.ttf
new file mode 100644
index 0000000..aef883e
Binary files /dev/null and b/substance/src/main/resources/resource/katakana.ttf differ
diff --git a/substance/src/main/resources/resource/media-floppy.png b/substance/src/main/resources/resource/media-floppy.png
new file mode 100644
index 0000000..f1d7a19
Binary files /dev/null and b/substance/src/main/resources/resource/media-floppy.png differ
diff --git a/substance/src/main/resources/resource/text-x-generic.png b/substance/src/main/resources/resource/text-x-generic.png
new file mode 100644
index 0000000..2d7f2d6
Binary files /dev/null and b/substance/src/main/resources/resource/text-x-generic.png differ
diff --git a/substance/src/main/resources/resource/user-home.png b/substance/src/main/resources/resource/user-home.png
new file mode 100644
index 0000000..13d2c00
Binary files /dev/null and b/substance/src/main/resources/resource/user-home.png differ
diff --git a/substance/src/tools/java/tools/common/JImageComponent.java b/substance/src/tools/java/tools/common/JImageComponent.java
new file mode 100644
index 0000000..42a4fea
--- /dev/null
+++ b/substance/src/tools/java/tools/common/JImageComponent.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.common;
+
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.net.URL;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+
+import org.pushingpixels.lafwidget.utils.RenderingUtils;
+import org.pushingpixels.substance.internal.utils.border.SubstanceBorder;
+
+public class JImageComponent extends JPanel {
+ private BufferedImage image;
+
+ private double leftX;
+
+ private double topY;
+
+ private double zoom;
+
+ private boolean isDragging;
+
+ private Point lastDragPoint;
+
+ private Color selectedColor;
+
+ private Color rolloverColor;
+
+ private File originalFile;
+
+ private String[] legend;
+
+ public JImageComponent(boolean hasKeyboardZoom) {
+ this.setTransferHandler(new TransferHandler() {
+ @Override
+ public boolean canImport(TransferSupport support) {
+ // can import a list of files
+ if (support
+ .isDataFlavorSupported(DataFlavor.javaFileListFlavor))
+ return true;
+ // an image
+ if (support.isDataFlavorSupported(DataFlavor.imageFlavor))
+ return true;
+ for (DataFlavor df : support.getDataFlavors()) {
+ // and a flavor represented by URL
+ if (df.getRepresentationClass() == URL.class)
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean importData(TransferSupport support) {
+ Transferable t = support.getTransferable();
+
+ try {
+ if (t.isDataFlavorSupported(DataFlavor.imageFlavor)) {
+ // load the image
+ Image data = (Image) t
+ .getTransferData(DataFlavor.imageFlavor);
+ originalFile = null;
+ BufferedImage old = image;
+ image = new BufferedImage(data.getWidth(null), data
+ .getHeight(null), BufferedImage.TYPE_INT_ARGB);
+ image.getGraphics().drawImage(data, 0, 0, null);
+ reset();
+ repaint();
+
+ firePropertyChange("image", old, image);
+
+ return true;
+ }
+
+ if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
+ // load the image from the file
+ java.util.List<File> l = (java.util.List<File>) t
+ .getTransferData(DataFlavor.javaFileListFlavor);
+
+ if (l.size() == 1) {
+ BufferedImage oldImage = image;
+ File oldFile = originalFile;
+ originalFile = l.get(0);
+ image = ImageIO.read(originalFile);
+ reset();
+ repaint();
+
+ firePropertyChange("image", oldImage, image);
+ firePropertyChange("file", oldFile, originalFile);
+ }
+ return true;
+ }
+
+ for (DataFlavor df : support.getDataFlavors()) {
+ if (df.getRepresentationClass() == URL.class) {
+ // load the image from the URL
+ URL url = (URL) t.getTransferData(df);
+ originalFile = null;
+ Image data = ImageIO.read(url);
+ if (data != null) {
+ BufferedImage old = image;
+ image = new BufferedImage(data.getWidth(null),
+ data.getHeight(null),
+ BufferedImage.TYPE_INT_ARGB);
+ image.getGraphics().drawImage(data, 0, 0, null);
+ reset();
+ repaint();
+
+ firePropertyChange("image", old, image);
+ return true;
+ }
+ }
+ }
+ return true;
+ } catch (Throwable thr) {
+ thr.printStackTrace();
+ return false;
+ }
+ }
+ });
+
+ this.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (image == null)
+ return;
+
+ if (!e.isPopupTrigger()) {
+ lastDragPoint = e.getPoint();
+ } else {
+ processPopup(e);
+ }
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (e.isPopupTrigger()) {
+ processPopup(e);
+ } else {
+ if (image == null)
+ return;
+
+ if (!isDragging) {
+ int xRel = e.getX();
+ int yRel = e.getY();
+ int xAbs = (int) ((xRel / zoom) + leftX);
+ int yAbs = (int) ((yRel / zoom) + topY);
+
+ if ((xAbs >= 0) && (xAbs < image.getWidth())
+ && (yAbs >= 0) && (yAbs < image.getHeight())) {
+ selectedColor = new Color(image.getRGB(xAbs, yAbs));
+ firePropertyChange("selectedColor", null,
+ selectedColor);
+ }
+ }
+ isDragging = false;
+ }
+ }
+
+ private void processPopup(MouseEvent e) {
+ JPopupMenu editMenu = new JPopupMenu();
+ editMenu.add(new AbstractAction("paste from clipboard") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ try {
+ Clipboard clipboard = Toolkit.getDefaultToolkit()
+ .getSystemClipboard();
+ DataFlavor[] flavors = clipboard
+ .getAvailableDataFlavors();
+ if (flavors != null) {
+ for (DataFlavor flavor : flavors) {
+ if (Image.class == flavor
+ .getRepresentationClass()) {
+ Image data = (Image) clipboard
+ .getData(flavor);
+
+ BufferedImage old = image;
+ originalFile = null;
+ image = new BufferedImage(data
+ .getWidth(null), data
+ .getHeight(null),
+ BufferedImage.TYPE_INT_ARGB);
+ image.getGraphics().drawImage(data, 0,
+ 0, null);
+ reset();
+ repaint();
+ firePropertyChange("image", old, image);
+ break;
+ }
+ }
+ }
+ repaint();
+ } catch (Throwable ignored) {
+ }
+ }
+
+ @Override
+ public boolean isEnabled() {
+ Clipboard clipboard = Toolkit.getDefaultToolkit()
+ .getSystemClipboard();
+ DataFlavor[] flavors = clipboard
+ .getAvailableDataFlavors();
+ if (flavors != null) {
+ for (DataFlavor flavor : flavors) {
+ if (Image.class == flavor
+ .getRepresentationClass()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ });
+ Point pt = SwingUtilities.convertPoint(e.getComponent(), e
+ .getPoint(), JImageComponent.this);
+ editMenu.show(JImageComponent.this, pt.x, pt.y);
+ }
+
+ });
+
+ this.addMouseMotionListener(new MouseMotionAdapter() {
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ if (image == null)
+ return;
+
+ isDragging = true;
+
+ Point currDragPoint = e.getPoint();
+ double dx = ((currDragPoint.x - lastDragPoint.x) / zoom);
+ double dy = ((currDragPoint.y - lastDragPoint.y) / zoom);
+ leftX -= dx;
+ topY -= dy;
+
+ lastDragPoint = currDragPoint;
+ repaint();
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ if (image == null)
+ return;
+
+ int xRel = e.getX();
+ int yRel = e.getY();
+ int xAbs = (int) ((xRel / zoom) + leftX);
+ int yAbs = (int) ((yRel / zoom) + topY);
+
+ // System.out.println(xRel + ":" + yRel + "->" + xAbs + ":"
+ // + yAbs);
+
+ if ((xAbs >= 0) && (xAbs < image.getWidth()) && (yAbs >= 0)
+ && (yAbs < image.getHeight())) {
+ Color old = rolloverColor;
+ rolloverColor = new Color(image.getRGB(xAbs, yAbs));
+ firePropertyChange("rolloverColor", old, rolloverColor);
+ }
+ }
+ });
+
+ this.addMouseWheelListener(new MouseWheelListener() {
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ zoom += e.getScrollAmount() * e.getWheelRotation() / 10.0;
+ zoom = Math.max(1.0, zoom);
+ repaint();
+ }
+ });
+
+ if (hasKeyboardZoom) {
+ Action zoomInAction = new AbstractAction("zoomin") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ zoom += 0.1;
+ repaint();
+ }
+ };
+ Action zoomOutAction = new AbstractAction("zoomout") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ zoom -= 0.1;
+ zoom = Math.max(1.0, zoom);
+ repaint();
+ }
+ };
+
+ // create the key input maps to handle the zooming
+ // with I and O
+ InputMap inputMap = new ComponentInputMap(this);
+ inputMap.put(KeyStroke.getKeyStroke("I"), "zoomin");
+ inputMap.put(KeyStroke.getKeyStroke("O"), "zoomout");
+
+ ActionMap actionMap = new ActionMap();
+ actionMap.put("zoomin", zoomInAction);
+ actionMap.put("zoomout", zoomOutAction);
+
+ // and register the maps
+ this.setInputMap(WHEN_IN_FOCUSED_WINDOW, inputMap);
+ this.setActionMap(actionMap);
+ }
+
+ this.setBorder(new SubstanceBorder());
+
+ this.zoom = 1.0;
+ }
+
+ public void setLegend(String[] legend) {
+ this.legend = legend;
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setColor(Color.white);
+ g2d.fillRect(0, 0, getWidth(), getHeight());
+
+ if (this.image != null) {
+ // zoom from the visible top-left pixel
+ g2d.scale(zoom, zoom);
+ g2d.translate(-this.leftX, -this.topY);
+ g2d.drawImage(this.image, 0, 0, null);
+ } else {
+ RenderingUtils.installDesktopHints(g2d, this);
+ g2d.setFont(UIManager.getFont("Label.font"));
+ g2d.setColor(Color.black);
+
+ int fh = g2d.getFontMetrics().getHeight();
+ if (this.legend != null) {
+ for (int i = 0; i < this.legend.length; i++) {
+ g2d.drawString(this.legend[i], 10, 20 + i * fh);
+ }
+ }
+ }
+
+ g2d.dispose();
+ }
+
+ private void reset() {
+ leftX = 0;
+ topY = 0;
+ zoom = 1.0;
+ }
+
+ public BufferedImage getImage() {
+ return image;
+ }
+
+ public File getOriginalFile() {
+ return originalFile;
+ }
+
+ public Point toOriginalImageCoords(int x, int y) {
+ int xAbs = (int) ((x / zoom) + leftX);
+ int yAbs = (int) ((y / zoom) + topY);
+ return new Point(xAbs, yAbs);
+ }
+}
\ No newline at end of file
diff --git a/substance/src/tools/java/tools/docrobot/BaseRobot.java b/substance/src/tools/java/tools/docrobot/BaseRobot.java
new file mode 100644
index 0000000..a419a8c
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/BaseRobot.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot;
+
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.GuiActionRunner;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.timing.Pause;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+
+import test.check.SampleFrame;
+
+/**
+ * The base class for taking a single screenshot for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class BaseRobot {
+ /**
+ * The associated Substance skin.
+ */
+ protected SubstanceSkin skin;
+
+ /**
+ * The screenshot filename.
+ */
+ protected String screenshotFilename;
+
+ /**
+ * The frame instance.
+ */
+ protected SampleFrame sf;
+
+ /**
+ * Creates the new screenshot robot.
+ *
+ * @param skin
+ * The skin.
+ * @param screenshotFilename
+ * The screenshot filename.
+ */
+ public BaseRobot(SubstanceSkin skin, String screenshotFilename) {
+ this.skin = skin;
+ this.screenshotFilename = screenshotFilename;
+ }
+
+ /**
+ * Runs the screenshot process.
+ */
+ public void run() {
+ long start = System.currentTimeMillis();
+
+ Robot robot = BasicRobot.robotWithNewAwtHierarchy();
+
+ // set skin
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ SubstanceLookAndFeel.setSkin(skin);
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ }
+ });
+ robot.waitForIdle();
+
+ // create the frame and set the icon image
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ sf = new SampleFrame();
+ sf.setIconImage(SubstanceImageCreator.getColorSchemeImage(null,
+ new ImageIcon(SkinRobot.class.getClassLoader()
+ .getResource(
+ "test/resource/image-x-generic.png")),
+ SubstanceLookAndFeel.getCurrentSkin(sf.getRootPane())
+ .getActiveColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE),
+ 0.0f));
+ sf.setSize(338, 245);
+ sf.setLocationRelativeTo(null);
+ sf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ sf.setVisible(true);
+ }
+ });
+ robot.waitForIdle();
+
+ // wait for one second
+ Pause.pause(1000);
+
+ // make the screenshot
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ makeScreenshot();
+ }
+ });
+ robot.waitForIdle();
+
+ // dispose the frame
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ sf.dispose();
+ }
+ });
+ robot.waitForIdle();
+
+ long end = System.currentTimeMillis();
+ System.out.println(this.getClass().getSimpleName() + " : "
+ + (end - start) + "ms");
+ }
+
+ /**
+ * Creates the screenshot and saves it on the disk.
+ */
+ public void makeScreenshot() {
+ BufferedImage bi = new BufferedImage(sf.getWidth(), sf.getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics g = bi.getGraphics();
+ sf.paint(g);
+ try {
+ File target = new File(this.screenshotFilename);
+ target.getParentFile().mkdirs();
+ ImageIO.write(bi, "png", target);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/ColorSchemeRobot.java b/substance/src/tools/java/tools/docrobot/ColorSchemeRobot.java
new file mode 100644
index 0000000..d52c60f
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/ColorSchemeRobot.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+
+/**
+ * The base class for taking a single screenshot for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class ColorSchemeRobot extends BaseRobot {
+ /**
+ * Creates the new screenshot robot.
+ *
+ * @param colorScheme
+ * The color scheme.
+ * @param screenshotFilename
+ * The screenshot filename.
+ */
+ public ColorSchemeRobot(SubstanceColorScheme colorScheme,
+ String screenshotFilename) {
+ super(colorScheme.isDark() ? new RobotDefaultDarkSkin(colorScheme)
+ : new RobotDefaultSkin(colorScheme), screenshotFilename);
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/ImageWatermarkRobot.java b/substance/src/tools/java/tools/docrobot/ImageWatermarkRobot.java
new file mode 100644
index 0000000..77f4931
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/ImageWatermarkRobot.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot;
+
+import java.awt.FlowLayout;
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.GuiActionRunner;
+import org.fest.swing.edt.GuiTask;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.skin.BusinessBlackSteelSkin;
+
+/**
+ * The base class for taking a single screenshot for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class ImageWatermarkRobot {
+ /**
+ * The screenshot filename.
+ */
+ protected String screenshotFilename;
+
+ /**
+ * The frame instance.
+ */
+ protected JFrame sf;
+
+ /**
+ * Creates the new screenshot robot.
+ *
+ * @param screenshotFilename
+ * The screenshot filename.
+ */
+ public ImageWatermarkRobot(String screenshotFilename) {
+ this.screenshotFilename = screenshotFilename;
+ }
+
+ /**
+ * Runs the screenshot process.
+ */
+ public void run() {
+ long start = System.currentTimeMillis();
+
+ Robot robot = BasicRobot.robotWithNewAwtHierarchy();
+
+ // set skin
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ SubstanceLookAndFeel.setSkin(new BusinessBlackSteelSkin());
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ }
+ });
+ robot.waitForIdle();
+
+ // create the frame and set the icon image
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ sf = new JFrame("Sample frame");
+ apply();
+ sf.setSize(238, 261);
+
+ sf.setLayout(new FlowLayout(FlowLayout.CENTER));
+ JButton defButton = new JButton("default");
+ JButton disButton = new JButton("disabled");
+ JButton regButton = new JButton("regular");
+ disButton.setEnabled(false);
+ sf.add(defButton);
+ sf.add(disButton);
+ sf.add(regButton);
+ sf.getRootPane().setDefaultButton(defButton);
+
+ BufferedImage iconImage = new BufferedImage(1, 1,
+ BufferedImage.TYPE_INT_ARGB);
+ sf.setIconImage(iconImage);
+ sf.setLocationRelativeTo(null);
+ sf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ sf.setVisible(true);
+ }
+ });
+ robot.waitForIdle();
+
+ // make the screenshot
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ makeScreenshot();
+ }
+ });
+ robot.waitForIdle();
+
+ // dispose the frame
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ sf.dispose();
+ }
+ });
+ robot.waitForIdle();
+
+ long end = System.currentTimeMillis();
+ System.out.println(this.getClass().getSimpleName() + " : "
+ + (end - start) + "ms");
+ }
+
+ /**
+ * Applies instance-specific Substance settings before taking the
+ * screenshot.
+ */
+ protected abstract void apply();
+
+ /**
+ * Creates the screenshot and saves it on the disk.
+ */
+ public void makeScreenshot() {
+ BufferedImage bi = new BufferedImage(sf.getWidth(), sf.getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics g = bi.getGraphics();
+ sf.paint(g);
+ try {
+ ImageIO.write(bi, "png", new File(this.screenshotFilename));
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/RobotDefaultDarkSkin.java b/substance/src/tools/java/tools/docrobot/RobotDefaultDarkSkin.java
new file mode 100644
index 0000000..467c6ab
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/RobotDefaultDarkSkin.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot;
+
+import org.pushingpixels.substance.api.ColorSchemeSingleColorQuery;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceColorSchemeBundle;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.colorscheme.DarkMetallicColorScheme;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.decoration.MarbleNoiseDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.ClassicFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.api.watermark.SubstanceNullWatermark;
+import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
+import org.pushingpixels.substance.internal.colorscheme.BlendBiColorScheme;
+
+/**
+ * The default dark skin for the docrobot scripts.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RobotDefaultDarkSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static String NAME = "Robot Default Dark";
+
+ /**
+ * Creates the skin based on the specified color scheme.
+ *
+ * @param colorScheme
+ * The active color scheme.
+ */
+ public RobotDefaultDarkSkin(SubstanceColorScheme colorScheme) {
+ SubstanceColorScheme inactiveScheme = new BlendBiColorScheme(
+ colorScheme, new DarkMetallicColorScheme(), 0.6);
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ colorScheme, inactiveScheme, inactiveScheme);
+ defaultSchemeBundle.registerColorScheme(inactiveScheme, 0.5f,
+ ComponentState.DISABLED_UNSELECTED,
+ ComponentState.DISABLED_SELECTED);
+
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerAsDecorationArea(colorScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ this.selectedTabFadeStart = 1.0;
+ this.selectedTabFadeEnd = 1.0;
+
+ BottomLineOverlayPainter bottomLineOverlayPainter = new BottomLineOverlayPainter(
+ ColorSchemeSingleColorQuery.MID);
+ this.addOverlayPainter(bottomLineOverlayPainter,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ this.watermark = new SubstanceNullWatermark();
+ this.watermarkScheme = new BlendBiColorScheme(colorScheme,
+ new DarkMetallicColorScheme(), 0.5);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new ClassicFillPainter();
+ this.borderPainter = new ClassicBorderPainter();
+
+ MarbleNoiseDecorationPainter decorationPainter = new MarbleNoiseDecorationPainter();
+ decorationPainter.setBaseDecorationPainter(new ArcDecorationPainter());
+ decorationPainter.setTextureAlpha(0.3f);
+ this.decorationPainter = decorationPainter;
+
+ this.highlightPainter = new ClassicHighlightPainter();
+ this.borderPainter = new ClassicBorderPainter();
+ }
+
+ /**
+ * Creates the skin based on the specified color scheme and watermark.
+ *
+ * @param colorScheme
+ * The active color scheme.
+ * @param watermark
+ * Watermark.
+ */
+ public RobotDefaultDarkSkin(SubstanceColorScheme colorScheme,
+ SubstanceWatermark watermark) {
+ this(colorScheme);
+ this.watermark = watermark;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/RobotDefaultSkin.java b/substance/src/tools/java/tools/docrobot/RobotDefaultSkin.java
new file mode 100644
index 0000000..8887aae
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/RobotDefaultSkin.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.colorscheme.LightGrayColorScheme;
+import org.pushingpixels.substance.api.colorscheme.MetallicColorScheme;
+import org.pushingpixels.substance.api.painter.border.ClassicBorderPainter;
+import org.pushingpixels.substance.api.painter.decoration.ArcDecorationPainter;
+import org.pushingpixels.substance.api.painter.decoration.MarbleNoiseDecorationPainter;
+import org.pushingpixels.substance.api.painter.fill.ClassicFillPainter;
+import org.pushingpixels.substance.api.painter.highlight.ClassicHighlightPainter;
+import org.pushingpixels.substance.api.painter.overlay.BottomLineOverlayPainter;
+import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
+import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
+
+/**
+ * The default light skin for the docrobot scripts.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RobotDefaultSkin extends SubstanceSkin {
+ /**
+ * Display name for <code>this</code> skin.
+ */
+ public static String NAME = "Robot Default";
+
+ /**
+ * Creates the skin based on the specified color scheme.
+ *
+ * @param colorScheme
+ * The active color scheme.
+ */
+ public RobotDefaultSkin(SubstanceColorScheme colorScheme) {
+ SubstanceColorSchemeBundle defaultSchemeBundle = new SubstanceColorSchemeBundle(
+ colorScheme, new MetallicColorScheme(),
+ new LightGrayColorScheme());
+ this.registerDecorationAreaSchemeBundle(defaultSchemeBundle,
+ DecorationAreaType.NONE);
+
+ this.registerAsDecorationArea(colorScheme,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ BottomLineOverlayPainter bottomLineOverlayPainter = new BottomLineOverlayPainter(
+ ColorSchemeSingleColorQuery.MID);
+ this.addOverlayPainter(bottomLineOverlayPainter,
+ DecorationAreaType.PRIMARY_TITLE_PANE,
+ DecorationAreaType.SECONDARY_TITLE_PANE,
+ DecorationAreaType.HEADER);
+
+ this.buttonShaper = new ClassicButtonShaper();
+ this.fillPainter = new ClassicFillPainter();
+ this.borderPainter = new ClassicBorderPainter();
+
+ MarbleNoiseDecorationPainter decorationPainter = new MarbleNoiseDecorationPainter();
+ decorationPainter.setBaseDecorationPainter(new ArcDecorationPainter());
+ decorationPainter.setTextureAlpha(0.3f);
+ this.decorationPainter = decorationPainter;
+
+ this.highlightPainter = new ClassicHighlightPainter();
+ this.borderPainter = new ClassicBorderPainter();
+ }
+
+ /**
+ * Creates the skin based on the specified color scheme and watermark.
+ *
+ * @param colorScheme
+ * The active color scheme.
+ * @param watermark
+ * Watermark.
+ */
+ public RobotDefaultSkin(SubstanceColorScheme colorScheme,
+ SubstanceWatermark watermark) {
+ this(colorScheme);
+ this.watermark = watermark;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.skin.SubstanceSkin#getDisplayName()
+ */
+ @Override
+ public String getDisplayName() {
+ return NAME;
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/RobotMain.java b/substance/src/tools/java/tools/docrobot/RobotMain.java
new file mode 100644
index 0000000..a6af574
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/RobotMain.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot;
+
+import java.lang.reflect.Method;
+
+import javax.swing.JFrame;
+
+/**
+ * The main method for taking screenshots for Substance documentation. Expects
+ * one parameter - fully qualified class name of a single screenshot robot which
+ * has a <code>public void run()</code> method.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RobotMain {
+ /**
+ * Runs the specified screenshot robot.
+ *
+ * @param args
+ * Should contain one string - fully qualified class name of a
+ * single screenshot robot which has a
+ * <code>public void run()</code> method.
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+
+ String mainClassName = args[0];
+ Class<?> robotClass = Class.forName(mainClassName);
+ Object robotInstance = robotClass.newInstance();
+ Method runMethod = robotClass.getMethod("run", new Class[0]);
+ runMethod.invoke(robotInstance, new Object[0]);
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/SkinRobot.java b/substance/src/tools/java/tools/docrobot/SkinRobot.java
new file mode 100644
index 0000000..b051072
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/SkinRobot.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot;
+
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.GuiActionRunner;
+import org.fest.swing.edt.GuiQuery;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.timing.Pause;
+import org.pushingpixels.substance.api.DecorationAreaType;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
+
+import test.check.SampleFrame;
+
+/**
+ * The base class for taking screenshots of skins for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class SkinRobot {
+ /**
+ * The associated Substance skin.
+ */
+ protected SubstanceSkin skin;
+
+ /**
+ * The screenshot filename.
+ */
+ protected String screenshotFilename;
+
+ /**
+ * The frame instance.
+ */
+ protected SampleFrame sf;
+
+ /**
+ * Creates the new screenshot robot.
+ *
+ * @param skin
+ * Substance skin.
+ * @param screenshotFilename
+ * The screenshot filename.
+ */
+ public SkinRobot(SubstanceSkin skin, String screenshotFilename) {
+ this.skin = skin;
+ this.screenshotFilename = screenshotFilename;
+ }
+
+ /**
+ * Runs the screenshot process.
+ */
+ public void run() {
+ long start = System.currentTimeMillis();
+
+ Robot robot = BasicRobot.robotWithNewAwtHierarchy();
+
+ // set skin
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ SubstanceLookAndFeel.setSkin(skin);
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ }
+ });
+ robot.waitForIdle();
+
+ // create the frame and set the icon image
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ sf = new SampleFrame();
+ sf.setIconImage(SubstanceImageCreator.getColorSchemeImage(null,
+ new ImageIcon(SkinRobot.class.getClassLoader()
+ .getResource(
+ "test/resource/image-x-generic.png")),
+ SubstanceLookAndFeel.getCurrentSkin(sf.getRootPane())
+ .getActiveColorScheme(
+ DecorationAreaType.PRIMARY_TITLE_PANE),
+ 0.0f));
+ sf.setSize(338, 245);
+ sf.setLocationRelativeTo(null);
+ sf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ sf.setVisible(true);
+ }
+ });
+ robot.waitForIdle();
+
+ // get the default button
+ JButton defaultButton = GuiActionRunner
+ .execute(new GuiQuery<JButton>() {
+ @Override
+ protected JButton executeInEDT() throws Throwable {
+ return sf.getRootPane().getDefaultButton();
+ }
+ });
+ // and move the mouse to it
+ robot.moveMouse(defaultButton);
+ robot.waitForIdle();
+
+ // wait for a second
+ Pause.pause(1000);
+
+ // make the first screenshot
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ makeScreenshot(1);
+ }
+ });
+ robot.waitForIdle();
+
+ // switch to the last tab
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ sf.switchToLastTab();
+ }
+ });
+ robot.waitForIdle();
+
+ // move the mouse away from the frame
+ robot.moveMouse(new Point(0, 0));
+ robot.waitForIdle();
+
+ // wait for two seconds
+ Pause.pause(1000);
+
+ // make the second screenshot
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ makeScreenshot(2);
+ }
+ });
+ robot.waitForIdle();
+
+ // dispose the frame
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ sf.dispose();
+ }
+ });
+ robot.waitForIdle();
+
+ long end = System.currentTimeMillis();
+ System.out.println(this.getClass().getSimpleName() + " : "
+ + (end - start) + "ms");
+ }
+
+ /**
+ * Creates the screenshot and saves it on the disk.
+ *
+ * @param count
+ * Sequence number for the screenshot.
+ */
+ public void makeScreenshot(int count) {
+ BufferedImage bi = new BufferedImage(sf.getWidth(), sf.getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics g = bi.getGraphics();
+ sf.paint(g);
+ try {
+ File output = new File(this.screenshotFilename + count + ".png");
+ output.getParentFile().mkdirs();
+ ImageIO.write(bi, "png", output);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/WatermarkRobot.java b/substance/src/tools/java/tools/docrobot/WatermarkRobot.java
new file mode 100644
index 0000000..0cb1983
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/WatermarkRobot.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot;
+
+import org.pushingpixels.substance.api.skin.ModerateSkin;
+import org.pushingpixels.substance.api.watermark.SubstanceWatermark;
+
+/**
+ * The base class for taking a single screenshot for Substance documentation.
+ *
+ * @author Kirill Grouchnikov
+ */
+public abstract class WatermarkRobot extends BaseRobot {
+ private static class MySkin extends ModerateSkin {
+ @Override
+ public String getDisplayName() {
+ return "Custom " + this.watermark.getDisplayName();
+ }
+
+ public MySkin(SubstanceWatermark watermark) {
+ super();
+ this.watermark = watermark;
+ }
+ }
+
+ /**
+ * Creates the new screenshot robot.
+ *
+ * @param watermark
+ * Watermark.
+ * @param screenshotFilename
+ * The screenshot filename.
+ */
+ public WatermarkRobot(SubstanceWatermark watermark,
+ String screenshotFilename) {
+ super(new MySkin(watermark), screenshotFilename);
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/painters/WatermarkOverlaying.java b/substance/src/tools/java/tools/docrobot/painters/WatermarkOverlaying.java
new file mode 100644
index 0000000..5cdb966
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/painters/WatermarkOverlaying.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.painters;
+
+import org.pushingpixels.substance.api.skin.RavenSkin;
+import org.pushingpixels.substance.api.watermark.SubstanceCrosshatchWatermark;
+
+import tools.docrobot.BaseRobot;
+
+/**
+ * Screenshot robot for watermark overlaying.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class WatermarkOverlaying extends BaseRobot {
+ private static class MySkin extends RavenSkin {
+ @Override
+ public String getDisplayName() {
+ return "My";
+ }
+
+ /**
+ * Creates a custom skin.
+ */
+ public MySkin() {
+ super();
+ this.watermark = new SubstanceCrosshatchWatermark();
+ }
+ }
+
+ /**
+ * Creates the screenshot robot.
+ */
+ public WatermarkOverlaying() {
+ super(
+ new MySkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/painters/titlepane.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/AquaScheme.java b/substance/src/tools/java/tools/docrobot/schemes/AquaScheme.java
new file mode 100644
index 0000000..dd84f76
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/AquaScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.AquaColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link AquaColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class AquaScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public AquaScheme() {
+ super(
+ new AquaColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/aqua.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/BarbyPinkScheme.java b/substance/src/tools/java/tools/docrobot/schemes/BarbyPinkScheme.java
new file mode 100644
index 0000000..95a0755
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/BarbyPinkScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.BarbyPinkColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link BarbyPinkColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BarbyPinkScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public BarbyPinkScheme() {
+ super(
+ new BarbyPinkColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/barby-pink.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/BottleGreenScheme.java b/substance/src/tools/java/tools/docrobot/schemes/BottleGreenScheme.java
new file mode 100644
index 0000000..6bc2a57
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/BottleGreenScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.BottleGreenColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link BottleGreenColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BottleGreenScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public BottleGreenScheme() {
+ super(
+ new BottleGreenColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/bottle-green.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/BrownScheme.java b/substance/src/tools/java/tools/docrobot/schemes/BrownScheme.java
new file mode 100644
index 0000000..9049b4f
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/BrownScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.BrownColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link BrownColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BrownScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public BrownScheme() {
+ super(
+ new BrownColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/brown.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/CharcoalScheme.java b/substance/src/tools/java/tools/docrobot/schemes/CharcoalScheme.java
new file mode 100644
index 0000000..b764d1f
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/CharcoalScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.CharcoalColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link CharcoalColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class CharcoalScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public CharcoalScheme() {
+ super(
+ new CharcoalColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/charcoal.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/CremeScheme.java b/substance/src/tools/java/tools/docrobot/schemes/CremeScheme.java
new file mode 100644
index 0000000..a828031
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/CremeScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.CremeColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link CremeColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class CremeScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public CremeScheme() {
+ super(
+ new CremeColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/creme.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/DarkVioletScheme.java b/substance/src/tools/java/tools/docrobot/schemes/DarkVioletScheme.java
new file mode 100644
index 0000000..ad1ebda
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/DarkVioletScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.DarkVioletColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link DarkVioletColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DarkVioletScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DarkVioletScheme() {
+ super(
+ new DarkVioletColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/dark-violet.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/DerivedDesaturatedScheme.java b/substance/src/tools/java/tools/docrobot/schemes/DerivedDesaturatedScheme.java
new file mode 100644
index 0000000..7044430
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/DerivedDesaturatedScheme.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.PurpleColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link SubstanceColorScheme#saturate(double)}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DerivedDesaturatedScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DerivedDesaturatedScheme() {
+ super(
+ new PurpleColorScheme().saturate(-0.4),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/derived-desaturate.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/DerivedHueShiftedScheme.java b/substance/src/tools/java/tools/docrobot/schemes/DerivedHueShiftedScheme.java
new file mode 100644
index 0000000..8ac8822
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/DerivedHueShiftedScheme.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.PurpleColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link SubstanceColorScheme#hueShift(double)}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DerivedHueShiftedScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DerivedHueShiftedScheme() {
+ super(
+ new PurpleColorScheme().hueShift(0.4),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/derived-hueshift.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/DerivedInvertedScheme.java b/substance/src/tools/java/tools/docrobot/schemes/DerivedInvertedScheme.java
new file mode 100644
index 0000000..7a07ed8
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/DerivedInvertedScheme.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.PurpleColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link SubstanceColorScheme#invert()}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DerivedInvertedScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DerivedInvertedScheme() {
+ super(
+ new PurpleColorScheme().invert(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/derived-invert.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/DerivedNegatedScheme.java b/substance/src/tools/java/tools/docrobot/schemes/DerivedNegatedScheme.java
new file mode 100644
index 0000000..53b2f79
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/DerivedNegatedScheme.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.PurpleColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link SubstanceColorScheme#negate()}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DerivedNegatedScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DerivedNegatedScheme() {
+ super(
+ new PurpleColorScheme().negate(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/derived-negate.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/DerivedSaturatedScheme.java b/substance/src/tools/java/tools/docrobot/schemes/DerivedSaturatedScheme.java
new file mode 100644
index 0000000..6d5040a
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/DerivedSaturatedScheme.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.PurpleColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link SubstanceColorScheme#saturate(double)}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DerivedSaturatedScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DerivedSaturatedScheme() {
+ super(
+ new PurpleColorScheme().saturate(0.4),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/derived-saturate.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/DerivedShadedScheme.java b/substance/src/tools/java/tools/docrobot/schemes/DerivedShadedScheme.java
new file mode 100644
index 0000000..8c252f5
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/DerivedShadedScheme.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.PurpleColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link SubstanceColorScheme#shade(double)}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DerivedShadedScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DerivedShadedScheme() {
+ super(
+ new PurpleColorScheme().shade(0.4),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/derived-shaded.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/DerivedShiftedBackgroundScheme.java b/substance/src/tools/java/tools/docrobot/schemes/DerivedShiftedBackgroundScheme.java
new file mode 100644
index 0000000..c3d1be4
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/DerivedShiftedBackgroundScheme.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.PurpleColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the
+ * {@link SubstanceColorScheme#shiftBackground(Color, double)}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DerivedShiftedBackgroundScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DerivedShiftedBackgroundScheme() {
+ super(
+ new PurpleColorScheme().shiftBackground(
+ new Color(255, 128, 128), 0.8),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/derived-shiftedbackground.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/DerivedShiftedScheme.java b/substance/src/tools/java/tools/docrobot/schemes/DerivedShiftedScheme.java
new file mode 100644
index 0000000..993f657
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/DerivedShiftedScheme.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.PurpleColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the
+ * {@link SubstanceColorScheme#shift(Color, double, Color, double)}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DerivedShiftedScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DerivedShiftedScheme() {
+ super(
+ new PurpleColorScheme().shift(new Color(128, 255, 128), 0.8,
+ new Color(128, 0, 0), 0.7),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/derived-shifted.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/DerivedTintedScheme.java b/substance/src/tools/java/tools/docrobot/schemes/DerivedTintedScheme.java
new file mode 100644
index 0000000..f167b3c
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/DerivedTintedScheme.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.PurpleColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link SubstanceColorScheme#tint(double)}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DerivedTintedScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DerivedTintedScheme() {
+ super(
+ new PurpleColorScheme().tint(0.4),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/derived-tinted.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/DerivedTonedScheme.java b/substance/src/tools/java/tools/docrobot/schemes/DerivedTonedScheme.java
new file mode 100644
index 0000000..d043410
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/DerivedTonedScheme.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.PurpleColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link SubstanceColorScheme#tone(double)}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DerivedTonedScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DerivedTonedScheme() {
+ super(
+ new PurpleColorScheme().tone(0.4),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/derived-toned.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/DesertSandScheme.java b/substance/src/tools/java/tools/docrobot/schemes/DesertSandScheme.java
new file mode 100644
index 0000000..e2b0c70
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/DesertSandScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.DesertSandColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link DesertSandColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DesertSandScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DesertSandScheme() {
+ super(
+ new DesertSandColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/desert-sand.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/EbonyScheme.java b/substance/src/tools/java/tools/docrobot/schemes/EbonyScheme.java
new file mode 100644
index 0000000..5cc9764
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/EbonyScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.EbonyColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link EbonyColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class EbonyScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public EbonyScheme() {
+ super(
+ new EbonyColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/ebony.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/JadeForestScheme.java b/substance/src/tools/java/tools/docrobot/schemes/JadeForestScheme.java
new file mode 100644
index 0000000..044921a
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/JadeForestScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.JadeForestColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link JadeForestColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class JadeForestScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public JadeForestScheme() {
+ super(
+ new JadeForestColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/jade-forest.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/LightAquaScheme.java b/substance/src/tools/java/tools/docrobot/schemes/LightAquaScheme.java
new file mode 100644
index 0000000..372a57e
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/LightAquaScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.LightAquaColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link LightAquaColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LightAquaScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public LightAquaScheme() {
+ super(
+ new LightAquaColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/light-aqua.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/LimeGreenScheme.java b/substance/src/tools/java/tools/docrobot/schemes/LimeGreenScheme.java
new file mode 100644
index 0000000..73f6407
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/LimeGreenScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.LimeGreenColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link LimeGreenColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class LimeGreenScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public LimeGreenScheme() {
+ super(
+ new LimeGreenColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/lime-green.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/OliveScheme.java b/substance/src/tools/java/tools/docrobot/schemes/OliveScheme.java
new file mode 100644
index 0000000..eadf10f
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/OliveScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.OliveColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link OliveColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class OliveScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public OliveScheme() {
+ super(
+ new OliveColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/olive.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/OrangeScheme.java b/substance/src/tools/java/tools/docrobot/schemes/OrangeScheme.java
new file mode 100644
index 0000000..dec4b4a
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/OrangeScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.OrangeColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link OrangeColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class OrangeScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public OrangeScheme() {
+ super(
+ new OrangeColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/orange.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/PurpleScheme.java b/substance/src/tools/java/tools/docrobot/schemes/PurpleScheme.java
new file mode 100644
index 0000000..3ffae2d
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/PurpleScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.PurpleColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link PurpleColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class PurpleScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public PurpleScheme() {
+ super(
+ new PurpleColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/purple.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/RaspberryScheme.java b/substance/src/tools/java/tools/docrobot/schemes/RaspberryScheme.java
new file mode 100644
index 0000000..34ec137
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/RaspberryScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.RaspberryColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link RaspberryColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class RaspberryScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public RaspberryScheme() {
+ super(
+ new RaspberryColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/raspberry.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/SepiaScheme.java b/substance/src/tools/java/tools/docrobot/schemes/SepiaScheme.java
new file mode 100644
index 0000000..1fbd725
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/SepiaScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.SepiaColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link SepiaColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SepiaScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public SepiaScheme() {
+ super(
+ new SepiaColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/sepia.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/SteelBlueScheme.java b/substance/src/tools/java/tools/docrobot/schemes/SteelBlueScheme.java
new file mode 100644
index 0000000..1c1112e
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/SteelBlueScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.SteelBlueColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link SteelBlueColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SteelBlueScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public SteelBlueScheme() {
+ super(
+ new SteelBlueColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/steel-blue.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/SunGlareScheme.java b/substance/src/tools/java/tools/docrobot/schemes/SunGlareScheme.java
new file mode 100644
index 0000000..fcf2a20
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/SunGlareScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.SunGlareColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link SunGlareColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SunGlareScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public SunGlareScheme() {
+ super(
+ new SunGlareColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/sun-glare.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/SunsetScheme.java b/substance/src/tools/java/tools/docrobot/schemes/SunsetScheme.java
new file mode 100644
index 0000000..91ecf30
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/SunsetScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.SunsetColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link SunsetColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SunsetScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public SunsetScheme() {
+ super(
+ new SunsetColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/sunset.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/TerracottaScheme.java b/substance/src/tools/java/tools/docrobot/schemes/TerracottaScheme.java
new file mode 100644
index 0000000..13d3967
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/TerracottaScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESSOR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.TerracottaColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link TerracottaColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TerracottaScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public TerracottaScheme() {
+ super(
+ new TerracottaColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/terracotta.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/schemes/UltramarineScheme.java b/substance/src/tools/java/tools/docrobot/schemes/UltramarineScheme.java
new file mode 100644
index 0000000..1220ffa
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/schemes/UltramarineScheme.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.schemes;
+
+import org.pushingpixels.substance.api.colorscheme.UltramarineColorScheme;
+
+import tools.docrobot.ColorSchemeRobot;
+
+/**
+ * Screenshot robot for the {@link UltramarineColorScheme}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class UltramarineScheme extends ColorSchemeRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public UltramarineScheme() {
+ super(
+ new UltramarineColorScheme(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/colorschemes/ultramarine.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Autumn.java b/substance/src/tools/java/tools/docrobot/skins/Autumn.java
new file mode 100644
index 0000000..72b637d
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Autumn.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.AutumnSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link AutumnSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Autumn extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Autumn() {
+ super(new AutumnSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/autumn");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Business.java b/substance/src/tools/java/tools/docrobot/skins/Business.java
new file mode 100644
index 0000000..aae7a3b
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Business.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.BusinessSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link BusinessSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Business extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Business() {
+ super(new BusinessSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/business");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/BusinessBlackSteel.java b/substance/src/tools/java/tools/docrobot/skins/BusinessBlackSteel.java
new file mode 100644
index 0000000..1c88250
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/BusinessBlackSteel.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.BusinessBlackSteelSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link BusinessBlackSteelSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BusinessBlackSteel extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public BusinessBlackSteel() {
+ super(
+ new BusinessBlackSteelSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/businessblacksteel");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/BusinessBlueSteel.java b/substance/src/tools/java/tools/docrobot/skins/BusinessBlueSteel.java
new file mode 100644
index 0000000..2b2ac48
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/BusinessBlueSteel.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.BusinessBlueSteelSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link BusinessBlueSteelSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class BusinessBlueSteel extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public BusinessBlueSteel() {
+ super(
+ new BusinessBlueSteelSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/businessbluesteel");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Cerulean.java b/substance/src/tools/java/tools/docrobot/skins/Cerulean.java
new file mode 100755
index 0000000..3700c22
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Cerulean.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.CremeSkin;
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link org.pushingpixels.substance.api.skin.CeruleanSkinSkin}.
+ *
+ */
+public class Cerulean extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Cerulean() {
+ super(new CremeSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/cerulean");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/ChallengerDeep.java b/substance/src/tools/java/tools/docrobot/skins/ChallengerDeep.java
new file mode 100644
index 0000000..c7f265d
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/ChallengerDeep.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.ChallengerDeepSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link ChallengerDeepSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ChallengerDeep extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public ChallengerDeep() {
+ super(
+ new ChallengerDeepSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/challengerdeep");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Creme.java b/substance/src/tools/java/tools/docrobot/skins/Creme.java
new file mode 100644
index 0000000..bc008f2
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Creme.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.CremeSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link CremeSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Creme extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Creme() {
+ super(new CremeSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/creme");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/CremeCoffee.java b/substance/src/tools/java/tools/docrobot/skins/CremeCoffee.java
new file mode 100644
index 0000000..cd14f6f
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/CremeCoffee.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.CremeCoffeeSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link CremeCoffeeSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class CremeCoffee extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public CremeCoffee() {
+ super(new CremeCoffeeSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/cremecoffee");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Dust.java b/substance/src/tools/java/tools/docrobot/skins/Dust.java
new file mode 100644
index 0000000..5142262
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Dust.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.DustSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link DustSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Dust extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Dust() {
+ super(new DustSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/dust");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/DustCoffee.java b/substance/src/tools/java/tools/docrobot/skins/DustCoffee.java
new file mode 100644
index 0000000..a172b68
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/DustCoffee.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.DustCoffeeSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link DustCoffeeSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class DustCoffee extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public DustCoffee() {
+ super(new DustCoffeeSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/dustcoffee");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/EmeraldDusk.java b/substance/src/tools/java/tools/docrobot/skins/EmeraldDusk.java
new file mode 100644
index 0000000..07cfc60
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/EmeraldDusk.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.EmeraldDuskSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link EmeraldDuskSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class EmeraldDusk extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public EmeraldDusk() {
+ super(new EmeraldDuskSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/emeralddusk");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Gemini.java b/substance/src/tools/java/tools/docrobot/skins/Gemini.java
new file mode 100644
index 0000000..f090f51
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Gemini.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.GeminiSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link GeminiSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Gemini extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Gemini() {
+ super(new GeminiSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/gemini");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Graphite.java b/substance/src/tools/java/tools/docrobot/skins/Graphite.java
new file mode 100644
index 0000000..353fb17
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Graphite.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.GraphiteSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link GraphiteSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Graphite extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Graphite() {
+ super(new GraphiteSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/graphite");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/GraphiteAqua.java b/substance/src/tools/java/tools/docrobot/skins/GraphiteAqua.java
new file mode 100644
index 0000000..de95a01
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/GraphiteAqua.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.GraphiteAquaSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link GraphiteAquaSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class GraphiteAqua extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public GraphiteAqua() {
+ super(new GraphiteAquaSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/graphiteaqua");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/GraphiteGlass.java b/substance/src/tools/java/tools/docrobot/skins/GraphiteGlass.java
new file mode 100644
index 0000000..3660fed
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/GraphiteGlass.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.GraphiteGlassSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link GraphiteGlassSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class GraphiteGlass extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public GraphiteGlass() {
+ super(new GraphiteGlassSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/graphiteglass");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Magellan.java b/substance/src/tools/java/tools/docrobot/skins/Magellan.java
new file mode 100644
index 0000000..a4fbc2e
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Magellan.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.MagellanSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link MagellanSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Magellan extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Magellan() {
+ super(new MagellanSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/magellan");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Mariner.java b/substance/src/tools/java/tools/docrobot/skins/Mariner.java
new file mode 100644
index 0000000..9b1d1cb
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Mariner.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.MarinerSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link MarinerSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Mariner extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Mariner() {
+ super(new MarinerSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/mariner");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/MistAqua.java b/substance/src/tools/java/tools/docrobot/skins/MistAqua.java
new file mode 100644
index 0000000..0cc99b1
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/MistAqua.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.MistAquaSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link MistAquaSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class MistAqua extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public MistAqua() {
+ super(new MistAquaSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/mistaqua");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/MistSilver.java b/substance/src/tools/java/tools/docrobot/skins/MistSilver.java
new file mode 100644
index 0000000..ee45d01
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/MistSilver.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.MistSilverSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link MistSilverSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class MistSilver extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public MistSilver() {
+ super(new MistSilverSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/mistsilver");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Moderate.java b/substance/src/tools/java/tools/docrobot/skins/Moderate.java
new file mode 100644
index 0000000..2588ff7
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Moderate.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.ModerateSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link ModerateSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Moderate extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Moderate() {
+ super(new ModerateSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/moderate");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Nebula.java b/substance/src/tools/java/tools/docrobot/skins/Nebula.java
new file mode 100644
index 0000000..44727d6
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Nebula.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.NebulaSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link NebulaSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Nebula extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Nebula() {
+ super(new NebulaSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/nebula");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/NebulaBrickWall.java b/substance/src/tools/java/tools/docrobot/skins/NebulaBrickWall.java
new file mode 100644
index 0000000..c6c5a1b
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/NebulaBrickWall.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.NebulaBrickWallSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link NebulaBrickWallSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class NebulaBrickWall extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public NebulaBrickWall() {
+ super(
+ new NebulaBrickWallSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/nebulabrickwall");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Raven.java b/substance/src/tools/java/tools/docrobot/skins/Raven.java
new file mode 100644
index 0000000..341e41d
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Raven.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.RavenSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link RavenSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Raven extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Raven() {
+ super(new RavenSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/raven");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Sahara.java b/substance/src/tools/java/tools/docrobot/skins/Sahara.java
new file mode 100644
index 0000000..c4c0109
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Sahara.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.SaharaSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link SaharaSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Sahara extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Sahara() {
+ super(new SaharaSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/sahara");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/skins/Twilight.java b/substance/src/tools/java/tools/docrobot/skins/Twilight.java
new file mode 100644
index 0000000..6f82864
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/skins/Twilight.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.skins;
+
+import org.pushingpixels.substance.api.skin.TwilightSkin;
+
+import tools.docrobot.SkinRobot;
+
+/**
+ * Screenshot robot for {@link TwilightSkin}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Twilight extends SkinRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public Twilight() {
+ super(new TwilightSkin(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/skins/twilight");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/watermarks/CrosshatchWatermark.java b/substance/src/tools/java/tools/docrobot/watermarks/CrosshatchWatermark.java
new file mode 100644
index 0000000..28ba631
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/watermarks/CrosshatchWatermark.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.watermarks;
+
+import org.pushingpixels.substance.api.watermark.SubstanceCrosshatchWatermark;
+
+import tools.docrobot.WatermarkRobot;
+
+/**
+ * Screenshot robot for {@link SubstanceCrosshatchWatermark}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class CrosshatchWatermark extends WatermarkRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public CrosshatchWatermark() {
+ super(
+ new SubstanceCrosshatchWatermark(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/watermarks/crosshatch.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/watermarks/ImageWatermarkAngelina.java b/substance/src/tools/java/tools/docrobot/watermarks/ImageWatermarkAngelina.java
new file mode 100644
index 0000000..c226a0c
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/watermarks/ImageWatermarkAngelina.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.watermarks;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceConstants.ImageWatermarkKind;
+import org.pushingpixels.substance.api.colorscheme.EbonyColorScheme;
+import org.pushingpixels.substance.api.watermark.SubstanceImageWatermark;
+
+import tools.docrobot.ImageWatermarkRobot;
+import tools.docrobot.RobotDefaultDarkSkin;
+
+/**
+ * Screenshot robot for {@link SubstanceImageWatermark}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ImageWatermarkAngelina extends ImageWatermarkRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public ImageWatermarkAngelina() {
+ super(
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/watermarks/image-angelina.png");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see docrobot.ImageWatermarkRobot#apply()
+ */
+ @Override
+ protected void apply() {
+ SubstanceImageWatermark watermark = new SubstanceImageWatermark(
+ "tools/docrobot/watermarks/AngelinaJolie.jpg");
+ watermark.setKind(ImageWatermarkKind.APP_ANCHOR);
+ SubstanceLookAndFeel.setSkin(new RobotDefaultDarkSkin(
+ new EbonyColorScheme(), watermark));
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/watermarks/ImageWatermarkBeyonce.java b/substance/src/tools/java/tools/docrobot/watermarks/ImageWatermarkBeyonce.java
new file mode 100644
index 0000000..c4355bc
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/watermarks/ImageWatermarkBeyonce.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.watermarks;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceConstants.ImageWatermarkKind;
+import org.pushingpixels.substance.api.colorscheme.SunsetColorScheme;
+import org.pushingpixels.substance.api.watermark.SubstanceImageWatermark;
+
+import tools.docrobot.ImageWatermarkRobot;
+import tools.docrobot.RobotDefaultSkin;
+
+/**
+ * Screenshot robot for {@link SubstanceImageWatermark}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ImageWatermarkBeyonce extends ImageWatermarkRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public ImageWatermarkBeyonce() {
+ super(
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/watermarks/image-beyonce.png");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see docrobot.ImageWatermarkRobot#apply()
+ */
+ @Override
+ protected void apply() {
+ SubstanceImageWatermark watermark = new SubstanceImageWatermark(
+ "tools/docrobot/watermarks/BeyonceKnowles.jpg");
+ watermark.setKind(ImageWatermarkKind.APP_ANCHOR);
+ SubstanceLookAndFeel.setSkin(new RobotDefaultSkin(
+ new SunsetColorScheme(), watermark));
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/watermarks/ImageWatermarkDominic.java b/substance/src/tools/java/tools/docrobot/watermarks/ImageWatermarkDominic.java
new file mode 100644
index 0000000..78005d8
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/watermarks/ImageWatermarkDominic.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.watermarks;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceConstants.ImageWatermarkKind;
+import org.pushingpixels.substance.api.colorscheme.CharcoalColorScheme;
+import org.pushingpixels.substance.api.watermark.SubstanceImageWatermark;
+
+import tools.docrobot.ImageWatermarkRobot;
+import tools.docrobot.RobotDefaultDarkSkin;
+
+/**
+ * Screenshot robot for {@link SubstanceImageWatermark}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ImageWatermarkDominic extends ImageWatermarkRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public ImageWatermarkDominic() {
+ super(
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/watermarks/image-dominic.png");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see docrobot.ImageWatermarkRobot#apply()
+ */
+ @Override
+ protected void apply() {
+ SubstanceImageWatermark watermark = new SubstanceImageWatermark(
+ "tools/docrobot/watermarks/PrisonBreak3.jpg");
+ watermark.setKind(ImageWatermarkKind.APP_ANCHOR);
+ SubstanceLookAndFeel.setSkin(new RobotDefaultDarkSkin(
+ new CharcoalColorScheme(), watermark));
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/watermarks/ImageWatermarkTerry.java b/substance/src/tools/java/tools/docrobot/watermarks/ImageWatermarkTerry.java
new file mode 100644
index 0000000..6815866
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/watermarks/ImageWatermarkTerry.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.watermarks;
+
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceConstants.ImageWatermarkKind;
+import org.pushingpixels.substance.api.colorscheme.SepiaColorScheme;
+import org.pushingpixels.substance.api.watermark.SubstanceImageWatermark;
+
+import tools.docrobot.ImageWatermarkRobot;
+import tools.docrobot.RobotDefaultSkin;
+
+/**
+ * Screenshot robot for {@link SubstanceImageWatermark}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ImageWatermarkTerry extends ImageWatermarkRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public ImageWatermarkTerry() {
+ super(
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/watermarks/image-terry.png");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see docrobot.ImageWatermarkRobot#apply()
+ */
+ @Override
+ protected void apply() {
+ SubstanceImageWatermark watermark = new SubstanceImageWatermark(
+ "tools/docrobot/watermarks/LostLocke.jpg");
+ watermark.setKind(ImageWatermarkKind.APP_ANCHOR);
+ SubstanceLookAndFeel.setSkin(new RobotDefaultSkin(
+ new SepiaColorScheme(), watermark));
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/watermarks/NullWatermark.java b/substance/src/tools/java/tools/docrobot/watermarks/NullWatermark.java
new file mode 100644
index 0000000..8f07526
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/watermarks/NullWatermark.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.watermarks;
+
+import org.pushingpixels.substance.api.watermark.SubstanceNullWatermark;
+
+import tools.docrobot.WatermarkRobot;
+
+/**
+ * Screenshot robot for {@link SubstanceNullWatermark}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class NullWatermark extends WatermarkRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public NullWatermark() {
+ super(new SubstanceNullWatermark(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/watermarks/null.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/docrobot/watermarks/StripesWatermark.java b/substance/src/tools/java/tools/docrobot/watermarks/StripesWatermark.java
new file mode 100644
index 0000000..858ca5a
--- /dev/null
+++ b/substance/src/tools/java/tools/docrobot/watermarks/StripesWatermark.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.docrobot.watermarks;
+
+import org.pushingpixels.substance.api.watermark.SubstanceStripeWatermark;
+
+import tools.docrobot.WatermarkRobot;
+
+/**
+ * Screenshot robot for {@link SubstanceStripeWatermark}.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class StripesWatermark extends WatermarkRobot {
+ /**
+ * Creates the screenshot robot.
+ */
+ public StripesWatermark() {
+ super(
+ new SubstanceStripeWatermark(),
+ "/Users/kirillg/JProjects/substance/www/images/screenshots/watermarks/stripes.png");
+ }
+}
diff --git a/substance/src/tools/java/tools/electra/Electra.java b/substance/src/tools/java/tools/electra/Electra.java
new file mode 100644
index 0000000..482e784
--- /dev/null
+++ b/substance/src/tools/java/tools/electra/Electra.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.electra;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+
+import org.pushingpixels.substance.api.*;
+import org.pushingpixels.substance.api.skin.GeminiSkin;
+
+import test.SubstanceLogo;
+import tools.common.JImageComponent;
+
+public class Electra extends JFrame {
+ public Electra() {
+ super("Electra");
+ this.setIconImage(SubstanceLogo
+ .getLogoImage(SubstanceLookAndFeel.getCurrentSkin(
+ this.getRootPane())
+ .getColorScheme(DecorationAreaType.PRIMARY_TITLE_PANE,
+ ColorSchemeAssociationKind.FILL,
+ ComponentState.ENABLED)));
+
+ this.setSize(1200, 800);
+ this.setLocationRelativeTo(null);
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ this.setLayout(new GridLayout(1, 2));
+
+ final JImageComponent jic = new JImageComponent(false);
+ jic
+ .setLegend(new String[] { "\tDrag and drop an image file from local disk" });
+ jic.setBorder(new Border() {
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y,
+ int width, int height) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setColor(SubstanceLookAndFeel.getCurrentSkin(c)
+ .getColorScheme(c, ColorSchemeAssociationKind.BORDER,
+ ComponentState.ENABLED).getMidColor());
+ g2d.drawLine(x + width - 2, y, x + width - 2, y + height - 1);
+ g2d.setComposite(AlphaComposite.SrcOver.derive(0.8f));
+ g2d.setColor(SubstanceLookAndFeel.getCurrentSkin(c)
+ .getColorScheme(c, ColorSchemeAssociationKind.BORDER,
+ ComponentState.ENABLED).getExtraLightColor()
+ .brighter());
+ g2d.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
+ g2d.dispose();
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return false;
+ }
+
+ @Override
+ public Insets getBorderInsets(Component c) {
+ return new Insets(0, 0, 0, 2);
+ }
+ });
+ this.add(jic);
+
+ final JElectrifiedImageComponent jeic = new JElectrifiedImageComponent(
+ jic);
+
+ JPanel electrifiedContainer = new JPanel(new BorderLayout());
+ JScrollPane scroller = new JScrollPane(jeic,
+ JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+ JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ scroller.setBorder(new EmptyBorder(0, 0, 0, 0));
+ electrifiedContainer.add(scroller, BorderLayout.CENTER);
+
+ JButton saveElectrified = new JButton("save");
+ saveElectrified.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ File originalFile = jic.getOriginalFile();
+ if (originalFile != null) {
+ jeic.save(originalFile);
+ }
+ }
+ });
+ }
+ });
+ JPanel controls = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+ controls.add(saveElectrified);
+ electrifiedContainer.add(controls, BorderLayout.SOUTH);
+
+ this.add(electrifiedContainer);
+
+ jic.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ Point absPoint = jic.toOriginalImageCoords(e.getX(), e
+ .getY());
+ jeic.addZoomBubble(absPoint.x, absPoint.y, 80);
+ }
+ }
+ });
+
+ }
+
+ public static void main(String[] args) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ JDialog.setDefaultLookAndFeelDecorated(true);
+ SubstanceLookAndFeel.setSkin(new GeminiSkin());
+
+ new Electra().setVisible(true);
+ }
+ });
+ }
+
+}
diff --git a/substance/src/tools/java/tools/electra/JElectrifiedImageComponent.java b/substance/src/tools/java/tools/electra/JElectrifiedImageComponent.java
new file mode 100644
index 0000000..75d3ad3
--- /dev/null
+++ b/substance/src/tools/java/tools/electra/JElectrifiedImageComponent.java
@@ -0,0 +1,996 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.electra;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.geom.*;
+import java.awt.image.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.*;
+import java.util.*;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+
+import org.pushingpixels.lafwidget.utils.RenderingUtils;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+
+import tools.common.JImageComponent;
+
+public class JElectrifiedImageComponent extends JComponent {
+ private JImageComponent originalImageComponent;
+
+ private BufferedImage originalImage;
+
+ private static final int WIDTH = 500;
+
+ private BufferedImage electrifiedImage;
+
+ private float scaleFactor;
+
+ private int imageOffsetX;
+
+ private int imageOffsetY;
+
+ private List<ZoomBubble> zoomBubbles = new ArrayList<ZoomBubble>();
+
+ private JTextField captionEditor;
+
+ private MouseDragHandler mouseDragHandler;
+
+ private static final float RIM_THICKNESS = 3.0f;
+
+ private static final float OUTER_SHADOW_THICKNESS = 8.0f;
+
+ private static final float INNER_SHADOW_THICKNESS = 3.0f;
+
+ private static final int TEXT_OUTER_SHADOW_THICKNESS = 6;
+
+ private static class ZoomBubble {
+ double centerX;
+
+ double centerY;
+
+ double radius;
+
+ boolean isSelected;
+
+ boolean isInTextEdit;
+
+ String caption;
+
+ double captionOffsetX;
+
+ double captionOffsetY;
+
+ Rectangle captionRectangle;
+
+ boolean isInverted;
+ }
+
+ private static interface MouseDragHandler {
+ void onStart(Point point);
+
+ void onDrag(Point point);
+
+ void onEnd(Point point);
+ }
+
+ private class ZoomBubbleDragHandler implements MouseDragHandler {
+ private ZoomBubble zoomBubble;
+
+ private Point lastDragPoint;
+
+ public ZoomBubbleDragHandler(ZoomBubble zoomBubble) {
+ this.zoomBubble = zoomBubble;
+ }
+
+ @Override
+ public void onStart(Point point) {
+ this.lastDragPoint = point;
+ }
+
+ @Override
+ public void onDrag(Point point) {
+ double dx = ((point.x - lastDragPoint.x) / scaleFactor);
+ double dy = ((point.y - lastDragPoint.y) / scaleFactor);
+
+ zoomBubble.centerX += dx;
+ zoomBubble.centerY += dy;
+
+ lastDragPoint = point;
+ repaint();
+ }
+
+ @Override
+ public void onEnd(Point point) {
+ }
+ }
+
+ private class ZoomBubbleResizeHandler implements MouseDragHandler {
+ private ZoomBubble zoomBubble;
+
+ private Point lastDragPoint;
+
+ public ZoomBubbleResizeHandler(ZoomBubble zoomBubble) {
+ this.zoomBubble = zoomBubble;
+ }
+
+ @Override
+ public void onStart(Point point) {
+ this.lastDragPoint = point;
+ }
+
+ @Override
+ public void onDrag(Point point) {
+ Point2D bubbleCenterView = originalToView(new Point2D.Double(
+ zoomBubble.centerX, zoomBubble.centerY));
+ double ndx = point.x - bubbleCenterView.getX();
+ double ndy = point.y - bubbleCenterView.getY();
+ double newRadius = Math.sqrt(ndx * ndx + ndy * ndy);
+
+ double odx = lastDragPoint.x - bubbleCenterView.getX();
+ double ody = lastDragPoint.y - bubbleCenterView.getY();
+ double oldRadius = Math.sqrt(odx * odx + ody * ody);
+
+ zoomBubble.radius += (newRadius - oldRadius);
+
+ lastDragPoint = point;
+ repaint();
+ }
+
+ @Override
+ public void onEnd(Point point) {
+ }
+ }
+
+ private class ZoomBubbleCaptionDragHandler implements MouseDragHandler {
+ private ZoomBubble zoomBubble;
+
+ private Point lastDragPoint;
+
+ public ZoomBubbleCaptionDragHandler(ZoomBubble zoomBubble) {
+ this.zoomBubble = zoomBubble;
+ }
+
+ @Override
+ public void onStart(Point point) {
+ this.lastDragPoint = point;
+ }
+
+ @Override
+ public void onDrag(Point point) {
+ int dx = point.x - lastDragPoint.x;
+ int dy = point.y - lastDragPoint.y;
+
+ zoomBubble.captionOffsetX += dx;
+ zoomBubble.captionOffsetY += dy;
+
+ lastDragPoint = point;
+ repaint();
+ }
+
+ @Override
+ public void onEnd(Point point) {
+ }
+ }
+
+ private enum DragType {
+ BUBBLE_DRAG, BUBBLE_RESIZE, CAPTION_DRAG;
+ }
+
+ private class BubbleDragPair {
+ ZoomBubble zoomBubble;
+
+ DragType dragType;
+
+ public BubbleDragPair(ZoomBubble zoomBubble, DragType dragType) {
+ this.zoomBubble = zoomBubble;
+ this.dragType = dragType;
+ }
+ }
+
+ public JElectrifiedImageComponent(JImageComponent originalImageComponent) {
+ this.originalImageComponent = originalImageComponent;
+ this.originalImageComponent
+ .addPropertyChangeListener(new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("image".equals(evt.getPropertyName())) {
+ originalImage = (BufferedImage) evt.getNewValue();
+ reset();
+ }
+ if ("file".equals(evt.getPropertyName())) {
+ File file = (File) evt.getNewValue();
+ File layers = new File(file.getParent(), file
+ .getName()
+ + ".layers");
+ if (layers.exists()) {
+ loadBubbles(layers);
+ }
+ }
+ }
+ });
+
+ this.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (!e.isPopupTrigger()) {
+ // stop editing if any
+ stopCaptionEdit(true);
+ for (ZoomBubble zoomBubble : zoomBubbles) {
+ zoomBubble.isSelected = false;
+ }
+
+ BubbleDragPair pressed = getInfoForDrag(e.getPoint());
+ if (pressed == null) {
+ repaint();
+ return;
+ }
+
+ pressed.zoomBubble.isSelected = true;
+
+ switch (pressed.dragType) {
+ case BUBBLE_DRAG:
+ mouseDragHandler = new ZoomBubbleDragHandler(
+ pressed.zoomBubble);
+ break;
+ case BUBBLE_RESIZE:
+ mouseDragHandler = new ZoomBubbleResizeHandler(
+ pressed.zoomBubble);
+ break;
+ case CAPTION_DRAG:
+ mouseDragHandler = new ZoomBubbleCaptionDragHandler(
+ pressed.zoomBubble);
+ break;
+ }
+
+ mouseDragHandler.onStart(e.getPoint());
+ repaint();
+ } else {
+ final BubbleDragPair pressed = getInfoForDrag(e.getPoint());
+ if (pressed == null) {
+ return;
+ }
+ JPopupMenu popupMenu = new JPopupMenu();
+ popupMenu.add(new AbstractAction("remove") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ zoomBubbles.remove(pressed.zoomBubble);
+ repaint();
+ }
+ });
+ popupMenu.add(new AbstractAction("invert caption colors") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ pressed.zoomBubble.isInverted = !pressed.zoomBubble.isInverted;
+ repaint();
+ }
+ });
+ Point pt = SwingUtilities.convertPoint(e.getComponent(), e
+ .getPoint(), JElectrifiedImageComponent.this);
+ popupMenu.show(JElectrifiedImageComponent.this, pt.x, pt.y);
+ }
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() != 2)
+ return;
+
+ for (ZoomBubble zoomBubble : zoomBubbles) {
+ if ((zoomBubble.isSelected) && (zoomBubble.caption == null)) {
+ if (zoomBubble.centerX < originalImage.getWidth() / 2) {
+ zoomBubble.captionOffsetX = zoomBubble.radius + 30;
+ } else {
+ zoomBubble.captionOffsetX = -zoomBubble.radius - 30;
+ }
+ zoomBubble.captionOffsetY = 0;
+ startCaptionEdit(zoomBubble);
+ return;
+ }
+ }
+ for (ZoomBubble zoomBubble : zoomBubbles) {
+ if (zoomBubble.captionRectangle != null) {
+ if (zoomBubble.captionRectangle.contains(e.getPoint())) {
+ startCaptionEdit(zoomBubble);
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (mouseDragHandler != null) {
+ mouseDragHandler.onEnd(e.getPoint());
+ mouseDragHandler = null;
+ }
+ }
+ });
+
+ this.addMouseMotionListener(new MouseMotionAdapter() {
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ if (mouseDragHandler != null) {
+ mouseDragHandler.onDrag(e.getPoint());
+ }
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ BubbleDragPair underMouse = getInfoForDrag(e.getPoint());
+ if (underMouse == null) {
+ setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ return;
+ }
+
+ switch (underMouse.dragType) {
+ case BUBBLE_DRAG:
+ case CAPTION_DRAG:
+ setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ break;
+ case BUBBLE_RESIZE:
+ setCursor(Cursor
+ .getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
+ break;
+ }
+ }
+ });
+
+ this.captionEditor = new JTextField(25);
+
+ InputMap im = this.captionEditor.getInputMap();
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "enter");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "escape");
+
+ ActionMap am = this.captionEditor.getActionMap();
+ am.put("enter", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent ae) {
+ stopCaptionEdit(true);
+ }
+ });
+
+ am.put("escape", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent ae) {
+ stopCaptionEdit(false);
+ }
+ });
+
+ this.captionEditor.setVisible(false);
+ this.add(this.captionEditor);
+ this.setLayout(null);
+ }
+
+ BubbleDragPair getInfoForDrag(Point viewPoint) {
+ for (ZoomBubble zoomBubble : zoomBubbles) {
+ Point2D zoomViewCenter = originalToView(new Point2D.Double(
+ zoomBubble.centerX, zoomBubble.centerY));
+ double diffX = zoomViewCenter.getX() - viewPoint.x;
+ double diffY = zoomViewCenter.getY() - viewPoint.y;
+ double distFromCenter = Math.sqrt(diffX * diffX + diffY * diffY);
+ if (distFromCenter < zoomBubble.radius / 2) {
+ return new BubbleDragPair(zoomBubble, DragType.BUBBLE_DRAG);
+ }
+ if (distFromCenter < zoomBubble.radius) {
+ return new BubbleDragPair(zoomBubble, DragType.BUBBLE_RESIZE);
+ }
+ }
+
+ // caption?
+ for (ZoomBubble zoomBubble : zoomBubbles) {
+ if (zoomBubble.captionRectangle != null) {
+ if (zoomBubble.captionRectangle.contains(viewPoint)) {
+ return new BubbleDragPair(zoomBubble, DragType.CAPTION_DRAG);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method that returns a scaled instance of the provided {@code
+ * BufferedImage}. Adopted from <a href="http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html"
+ * >article by Chris Campbell</a>.
+ *
+ * @param img
+ * the original image to be scaled
+ * @param targetWidth
+ * the desired width of the scaled instance, in pixels
+ * @param targetHeight
+ * the desired height of the scaled instance, in pixels
+ * @return a scaled version of the original {@code BufferedImage}
+ */
+ static BufferedImage getScaledInstance(BufferedImage img, int targetWidth,
+ int targetHeight) {
+ int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB
+ : BufferedImage.TYPE_INT_ARGB;
+ BufferedImage ret = (BufferedImage) img;
+ int w, h;
+ // Use multi-step technique: start with original size, then
+ // scale down in multiple passes with drawImage()
+ // until the target size is reached
+ w = img.getWidth();
+ h = img.getHeight();
+
+ do {
+ if (w > targetWidth) {
+ w /= 2;
+ if (w < targetWidth) {
+ w = targetWidth;
+ }
+ }
+
+ if (h > targetHeight) {
+ h /= 2;
+ if (h < targetHeight) {
+ h = targetHeight;
+ }
+ }
+
+ BufferedImage tmp = new BufferedImage(w, h, type);
+ Graphics2D g2 = tmp.createGraphics();
+ g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ g2.drawImage(ret, 0, 0, w, h, null);
+ g2.dispose();
+
+ ret = tmp;
+ } while (w != targetWidth || h != targetHeight);
+
+ return ret;
+ }
+
+ private void reset() {
+ if (originalImage == null) {
+ electrifiedImage = null;
+ } else {
+ // scale down
+ this.scaleFactor = (float) WIDTH / (float) originalImage.getWidth();
+ if (this.scaleFactor < 1.0f) {
+ electrifiedImage = getScaledInstance(originalImage, WIDTH,
+ (int) (this.scaleFactor * originalImage.getHeight()));
+ } else {
+ this.scaleFactor = 1.0f;
+ electrifiedImage = originalImage;
+ }
+ // and blur
+ int kernelSide = 3;
+ float[] kernelData = new float[kernelSide * kernelSide];
+ for (int i = 0; i < kernelData.length; i++)
+ kernelData[i] = 1.0f / kernelData.length;
+ Kernel kernel = new Kernel(kernelSide, kernelSide, kernelData);
+ electrifiedImage = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP,
+ null).filter(electrifiedImage, null);
+ setPreferredSize(new Dimension(getSize().width, electrifiedImage
+ .getHeight() + 50));
+ ((JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class,
+ this)).revalidate();
+ }
+ zoomBubbles.clear();
+ repaint();
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+
+ if (electrifiedImage != null) {
+ int width = getWidth();
+ int height = getHeight();
+ this.imageOffsetX = (width - electrifiedImage.getWidth()) / 2;
+ this.imageOffsetY = (height - electrifiedImage.getHeight()) / 2;
+ paintElectrified(g2d, true, this.imageOffsetX, this.imageOffsetY);
+ }
+ g2d.dispose();
+ }
+
+ private void paintElectrified(Graphics2D g2d, boolean isOnScreen,
+ int offsetX, int offsetY) {
+ if (electrifiedImage != null) {
+ g2d.drawImage(this.electrifiedImage, offsetX, offsetY, null);
+
+ g2d.setColor(new Color(0, 0, 0, 32));
+ g2d.fillRect(offsetX, offsetY, this.electrifiedImage.getWidth(),
+ this.electrifiedImage.getHeight());
+
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ // bubbles
+ for (ZoomBubble zoomBubble : zoomBubbles) {
+ double bubbleCenterViewX = offsetX + zoomBubble.centerX
+ * this.scaleFactor;
+ double bubbleCenterViewY = offsetY + zoomBubble.centerY
+ * this.scaleFactor;
+
+ Shape currClip = g2d.getClip();
+
+ int centerViewX = (int) bubbleCenterViewX;
+ int centerViewY = (int) bubbleCenterViewY;
+ g2d.clip(new Ellipse2D.Double(centerViewX - zoomBubble.radius,
+ centerViewY - zoomBubble.radius, 2 * zoomBubble.radius,
+ 2 * zoomBubble.radius));
+
+ int sx1 = (int) (zoomBubble.centerX - zoomBubble.radius);
+ int dx1 = (int) (centerViewX - zoomBubble.radius);
+ int sx2 = (int) (zoomBubble.centerX + zoomBubble.radius);
+ int dx2 = dx1 + (sx2 - sx1);
+
+ if (sx1 < 0) {
+ dx1 = dx1 - sx1;
+ sx1 = 0;
+ }
+ if (sx2 > originalImage.getWidth()) {
+ dx2 = dx2 - (sx2 - originalImage.getWidth());
+ sx2 = originalImage.getWidth();
+ }
+
+ int sy1 = (int) (zoomBubble.centerY - zoomBubble.radius);
+ int dy1 = (int) (centerViewY - zoomBubble.radius);
+ int sy2 = (int) (zoomBubble.centerY + zoomBubble.radius);
+ int dy2 = dy1 + (sy2 - sy1);
+
+ if (sy1 < 0) {
+ dy1 = dy1 - sy1;
+ sy1 = 0;
+ }
+ if (sy2 > originalImage.getHeight()) {
+ dy2 = dy2 - (sy2 - originalImage.getHeight());
+ sy2 = originalImage.getHeight();
+ }
+
+ if ((sx2 - sx1) != (dx2 - dx1)) {
+ throw new IllegalStateException();
+ }
+ if ((sy2 - sy1) != (dy2 - dy1)) {
+ throw new IllegalStateException();
+ }
+
+ g2d.drawImage(this.originalImage, dx1, dy1, dx2, dy2, sx1, sy1,
+ sx2, sy2, null);
+ g2d.setClip(currClip);
+
+ float totalRadius = (float) (zoomBubble.radius + RIM_THICKNESS
+ / 2 + OUTER_SHADOW_THICKNESS);
+ RadialGradientPaint rimPaint = new RadialGradientPaint(
+ centerViewX,
+ centerViewY,
+ totalRadius,
+ new float[] {
+ 0.0f,
+ (float) ((zoomBubble.radius - RIM_THICKNESS / 2 - INNER_SHADOW_THICKNESS) / totalRadius),
+ (float) ((zoomBubble.radius - RIM_THICKNESS / 2) / totalRadius),
+ (float) ((zoomBubble.radius + RIM_THICKNESS / 2) / totalRadius),
+ (float) ((zoomBubble.radius + RIM_THICKNESS / 2 + OUTER_SHADOW_THICKNESS / 3) / totalRadius),
+ 1.0f }, new Color[] { new Color(0, 0, 0, 0),
+ new Color(0, 0, 0, 0), new Color(0, 0, 0, 128),
+ new Color(0, 0, 0, 128),
+ new Color(0, 0, 0, 32), new Color(0, 0, 0, 0) });
+ g2d.setPaint(rimPaint);
+ g2d.fill(new Ellipse2D.Double(centerViewX - totalRadius,
+ centerViewY - totalRadius, 2 * totalRadius,
+ 2 * totalRadius));
+ g2d.setColor(Color.white);
+ g2d.setStroke(new BasicStroke(RIM_THICKNESS));
+ g2d.draw(new Ellipse2D.Double(centerViewX - zoomBubble.radius,
+ centerViewY - zoomBubble.radius, 2 * zoomBubble.radius,
+ 2 * zoomBubble.radius));
+
+ if (zoomBubble.isSelected && isOnScreen) {
+ g2d.setColor(new Color(0, 0, 0, 196));
+
+ int selectionCornerSide = 6;
+
+ int selectionLeftX = (int) (centerViewX - zoomBubble.radius - RIM_THICKNESS / 2);
+ int selectionRightX = (int) (centerViewX
+ + zoomBubble.radius + RIM_THICKNESS / 2);
+ int selectionTopY = (int) (centerViewY - zoomBubble.radius - RIM_THICKNESS / 2);
+ int selectionBottomY = (int) (centerViewY
+ + zoomBubble.radius + RIM_THICKNESS / 2);
+
+ g2d.setStroke(new BasicStroke(1.2f));
+ g2d.drawRect(
+ (int) (selectionLeftX - selectionCornerSide / 2),
+ (int) (selectionTopY - selectionCornerSide / 2),
+ selectionCornerSide, selectionCornerSide);
+ g2d.drawRect(
+ (int) (selectionLeftX - selectionCornerSide / 2),
+ (int) (selectionBottomY - selectionCornerSide / 2),
+ selectionCornerSide, selectionCornerSide);
+ g2d.drawRect(
+ (int) (selectionRightX - selectionCornerSide / 2),
+ (int) (selectionTopY - selectionCornerSide / 2),
+ selectionCornerSide, selectionCornerSide);
+ g2d.drawRect(
+ (int) (selectionRightX - selectionCornerSide / 2),
+ (int) (selectionBottomY - selectionCornerSide / 2),
+ selectionCornerSide, selectionCornerSide);
+
+ g2d.setStroke(new BasicStroke(1.2f, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_ROUND, 0.0f, new float[] { 2.0f,
+ 1.0f }, 0.0f));
+ g2d.drawLine(
+ (int) (selectionLeftX + selectionCornerSide / 2),
+ selectionTopY,
+ (int) (selectionRightX - selectionCornerSide / 2),
+ selectionTopY);
+ g2d.drawLine(
+ (int) (selectionLeftX + selectionCornerSide / 2),
+ selectionBottomY,
+ (int) (selectionRightX - selectionCornerSide / 2),
+ selectionBottomY);
+ g2d.drawLine(selectionLeftX,
+ (int) (selectionTopY + selectionCornerSide / 2),
+ selectionLeftX,
+ (int) (selectionBottomY - selectionCornerSide / 2));
+ g2d.drawLine(selectionRightX,
+ (int) (selectionTopY + selectionCornerSide / 2),
+ selectionRightX,
+ (int) (selectionBottomY - selectionCornerSide / 2));
+ }
+
+ // caption
+ if (zoomBubble.caption != null && !zoomBubble.isInTextEdit) {
+ Font font = SubstanceLookAndFeel.getFontPolicy()
+ .getFontSet("Substance", null).getControlFont();
+ g2d.setFont(font);
+ int strWidth = g2d.getFontMetrics().stringWidth(
+ zoomBubble.caption);
+ int fontHeight = g2d.getFontMetrics().getHeight();
+
+ int captionHeight = fontHeight + 8;
+ int captionWidth = strWidth + 8;
+
+ int radius = 3;
+ int x = (int) (centerViewX + zoomBubble.captionOffsetX);
+ int y = (int) (centerViewY + zoomBubble.captionOffsetY);
+
+ Shape outerContour = (zoomBubble.captionOffsetX < 0) ? getCaptionOutlinePointingToRight(
+ captionHeight, captionWidth, radius, 0)
+ : getCaptionOutlinePointingToLeft(captionHeight,
+ captionWidth, radius, 0);
+ Shape innerContour = (zoomBubble.captionOffsetX < 0) ? getCaptionOutlinePointingToRight(
+ captionHeight, captionWidth, radius, 1)
+ : getCaptionOutlinePointingToLeft(captionHeight,
+ captionWidth, radius, 1);
+
+ boolean isInverted = zoomBubble.isInverted;
+
+ g2d.translate(x, y);
+ g2d.setPaint(new GradientPaint(0, 0,
+ isInverted ? new Color(224, 224, 224, 240)
+ : new Color(32, 32, 32, 240), 0,
+ captionHeight, isInverted ? new Color(255, 255,
+ 255, 240) : new Color(0, 0, 0, 240)));
+ g2d.fill(outerContour);
+
+ for (int i = TEXT_OUTER_SHADOW_THICKNESS; i >= 0; i--) {
+ g2d.setColor(new Color(0, 0, 0, 12));
+ g2d.setStroke(new BasicStroke(i));
+ g2d.draw(outerContour);
+ }
+ g2d.setColor(isInverted ? new Color(255, 255, 255, 196)
+ : new Color(0, 0, 0, 196));
+ g2d.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER));
+ g2d.draw(outerContour);
+
+ g2d.setPaint(new LinearGradientPaint(0, 0, 0,
+ captionHeight, new float[] { 0.0f, 0.8f, 1.0f },
+ new Color[] {
+ isInverted ? new Color(64, 64, 64, 64)
+ : new Color(192, 192, 192, 64),
+ isInverted ? new Color(64, 64, 64, 48)
+ : new Color(192, 192, 192, 48),
+ isInverted ? new Color(64, 64, 64, 16)
+ : new Color(192, 192, 192, 16) }));
+ g2d.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER));
+ g2d.draw(innerContour);
+
+ g2d.translate(-x, -y);
+
+ RenderingUtils.installDesktopHints(g2d, this);
+ int textY = y + 4 + g2d.getFontMetrics().getAscent();
+ int textX = (zoomBubble.captionOffsetX < 0) ? x
+ + captionHeight / 6 + 4 : x + captionHeight / 3 + 4;
+
+ g2d.setColor(isInverted ? new Color(255, 255, 255, 128)
+ : new Color(0, 0, 0, 196));
+ g2d.drawString(zoomBubble.caption, textX - 1, textY);
+ g2d.drawString(zoomBubble.caption, textX + 1, textY);
+ g2d.drawString(zoomBubble.caption, textX, textY - 1);
+ g2d.drawString(zoomBubble.caption, textX, textY + 1);
+ g2d.setColor(isInverted ? new Color(0, 0, 0) : new Color(
+ 224, 224, 224));
+ g2d.drawString(zoomBubble.caption, textX, textY);
+
+ if (zoomBubble.captionOffsetX < 0) {
+ zoomBubble.captionRectangle = new Rectangle(x
+ + captionHeight / 6, y, captionWidth,
+ captionHeight);
+ } else {
+ zoomBubble.captionRectangle = new Rectangle(x
+ + captionHeight / 3, y, captionWidth,
+ captionHeight);
+ }
+ }
+ }
+ }
+ }
+
+ private Shape getCaptionOutlinePointingToRight(int captionHeight,
+ int captionWidth, int radius, int insets) {
+ GeneralPath contour = new GeneralPath();
+ contour.moveTo(radius, insets);
+ contour.lineTo(captionWidth, insets);
+ contour.lineTo(captionWidth + captionHeight / 2 - insets,
+ captionHeight / 2);
+ contour.lineTo(captionWidth, captionHeight - insets);
+ // bottom left corner
+ contour.append(new Arc2D.Double(insets, captionHeight - 2 * radius
+ + insets, 2 * radius - 2 * insets, 2 * radius - 2 * insets,
+ 270, -90, Arc2D.OPEN), true);
+ contour.lineTo(insets, radius);
+ // top left corner
+ contour.append(new Arc2D.Double(insets, insets,
+ 2 * radius - 2 * insets, 2 * radius - 2 * insets, 180, -90,
+ Arc2D.OPEN), true);
+ contour.closePath();
+ return contour;
+ }
+
+ private Shape getCaptionOutlinePointingToLeft(int captionHeight,
+ int captionWidth, int radius, int insets) {
+ GeneralPath contour = new GeneralPath();
+ contour.moveTo(insets, captionHeight / 2);
+ contour.lineTo(captionHeight / 2, insets);
+ contour.lineTo(captionWidth + captionHeight / 2 - radius, insets);
+ // top right corner
+ contour.append(new Arc2D.Double(captionWidth + captionHeight / 2 - 2
+ * radius + insets, insets, 2 * radius - 2 * insets, 2 * radius
+ - 2 * insets, 90, -90, Arc2D.OPEN), true);
+ contour.lineTo(captionWidth + captionHeight / 2 - insets, captionHeight
+ - radius - insets);
+ // bottom right corner
+ contour.append(new Arc2D.Double(captionWidth + captionHeight / 2 - 2
+ * radius + insets, captionHeight - 2 * radius + insets, 2
+ * radius - 2 * insets, 2 * radius - 2 * insets, 0, -90,
+ Arc2D.OPEN), true);
+ contour.lineTo(captionHeight / 2, captionHeight - insets);
+ contour.closePath();
+ return contour;
+ }
+
+ void addZoomBubble(int x, int y, int radius) {
+ ZoomBubble zoomBubble = new ZoomBubble();
+ zoomBubble.centerX = x;
+ zoomBubble.centerY = y;
+ zoomBubble.radius = radius;
+ zoomBubble.isInverted = false;
+ this.zoomBubbles.add(zoomBubble);
+ repaint();
+ }
+
+ Point2D originalToView(Point2D original) {
+ double viewX = this.imageOffsetX + original.getX() * this.scaleFactor;
+ double viewY = this.imageOffsetY + original.getY() * this.scaleFactor;
+ return new Point2D.Double(viewX, viewY);
+ }
+
+ Point2D viewToOriginal(Point2D view) {
+ double origX = (view.getX() - imageOffsetX) / scaleFactor;
+ double origY = (view.getY() - imageOffsetY) / scaleFactor;
+ return new Point2D.Double(origX, origY);
+ }
+
+ void startCaptionEdit(final ZoomBubble bubble) {
+ bubble.isInTextEdit = true;
+
+ if (bubble.caption != null) {
+ captionEditor.setText(bubble.caption);
+ captionEditor.selectAll();
+ } else {
+ captionEditor.setText("");
+ }
+ Dimension pref = captionEditor.getPreferredSize();
+ Point2D bubbleCenterView = originalToView(new Point2D.Double(
+ bubble.centerX, bubble.centerY));
+ captionEditor.setBounds(
+ (int) (bubbleCenterView.getX() + bubble.captionOffsetX),
+ (int) (bubbleCenterView.getY() + bubble.captionOffsetY),
+ pref.width, pref.height);
+ captionEditor.setVisible(true);
+ captionEditor.requestFocus();
+ repaint();
+ }
+
+ void stopCaptionEdit(boolean saveChanges) {
+ if (!this.captionEditor.isVisible())
+ return;
+
+ // get the text
+ String text = captionEditor.getText();
+ if (text.length() == 0) {
+ text = null;
+ }
+ for (ZoomBubble zoomBubble : zoomBubbles) {
+ if (zoomBubble.isInTextEdit) {
+ zoomBubble.caption = text;
+ zoomBubble.isInTextEdit = false;
+ }
+ }
+
+ captionEditor.setVisible(false);
+ repaint();
+ }
+
+ void save(File originalFile) {
+ int extraTop = 0;
+ int extraBottom = 0;
+ int extraLeft = 0;
+ int extraRight = 0;
+ for (ZoomBubble zoomBubble : zoomBubbles) {
+ Point2D bubbleCenterView = originalToView(new Point2D.Double(
+ zoomBubble.centerX, zoomBubble.centerY));
+ double l = bubbleCenterView.getX() - zoomBubble.radius
+ - imageOffsetX - RIM_THICKNESS / 2 - OUTER_SHADOW_THICKNESS;
+ double r = bubbleCenterView.getX() + zoomBubble.radius
+ - imageOffsetX + RIM_THICKNESS / 2 + OUTER_SHADOW_THICKNESS;
+ double t = bubbleCenterView.getY() - zoomBubble.radius
+ - imageOffsetY - RIM_THICKNESS / 2 - OUTER_SHADOW_THICKNESS;
+ double b = bubbleCenterView.getY() + zoomBubble.radius
+ - imageOffsetY + RIM_THICKNESS / 2 + OUTER_SHADOW_THICKNESS;
+
+ if (zoomBubble.captionRectangle != null) {
+ l = Math.min(l, zoomBubble.captionRectangle.getMinX()
+ - imageOffsetX - TEXT_OUTER_SHADOW_THICKNESS);
+ r = Math.max(r, zoomBubble.captionRectangle.getMaxX()
+ - imageOffsetX + TEXT_OUTER_SHADOW_THICKNESS);
+ t = Math.min(t, zoomBubble.captionRectangle.getMinY()
+ - imageOffsetY - TEXT_OUTER_SHADOW_THICKNESS);
+ b = Math.max(b, zoomBubble.captionRectangle.getMaxY()
+ - imageOffsetY + TEXT_OUTER_SHADOW_THICKNESS);
+ }
+
+ if (l < 0) {
+ extraLeft = Math.max(extraLeft, (int) Math.ceil(-l));
+ }
+ if (r > WIDTH) {
+ extraRight = Math.max(extraRight, (int) Math.ceil(r - WIDTH));
+ }
+ if (t < 0) {
+ extraTop = Math.max(extraTop, (int) Math.ceil(-t));
+ }
+ if (b > electrifiedImage.getHeight()) {
+ extraBottom = Math.max(extraBottom, (int) Math.ceil(b
+ - electrifiedImage.getHeight()));
+ }
+ }
+
+ int finalWidth = WIDTH + extraLeft + extraRight;
+ int finalHeight = electrifiedImage.getHeight() + extraTop + extraBottom;
+
+ GraphicsEnvironment e = GraphicsEnvironment
+ .getLocalGraphicsEnvironment();
+ GraphicsDevice d = e.getDefaultScreenDevice();
+ GraphicsConfiguration c = d.getDefaultConfiguration();
+ BufferedImage compatibleImage = c.createCompatibleImage(finalWidth,
+ finalHeight, Transparency.TRANSLUCENT);
+
+ Graphics2D g2d = compatibleImage.createGraphics();
+ g2d.translate(extraLeft, extraTop);
+ this.paintElectrified(g2d, false, 0, 0);
+ g2d.dispose();
+
+ try {
+ String origFileName = originalFile.getName();
+ String targetFileName = origFileName.substring(0, origFileName
+ .lastIndexOf('.'))
+ + ".electra.png";
+ ImageIO.write(compatibleImage, "png", new File(originalFile
+ .getParentFile(), targetFileName));
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ saveBubbles(new File(originalFile.getParentFile(), originalFile
+ .getName()
+ + ".layers"));
+ }
+
+ private void saveBubbles(File file) {
+ try {
+ PrintWriter pw = new PrintWriter(file);
+ pw.println("count=" + this.zoomBubbles.size());
+ for (int i = 0; i < this.zoomBubbles.size(); i++) {
+ ZoomBubble zoomBubble = this.zoomBubbles.get(i);
+ pw.println("bubble" + i + ".centerX=" + zoomBubble.centerX);
+ pw.println("bubble" + i + ".centerY=" + zoomBubble.centerY);
+ pw.println("bubble" + i + ".radius=" + zoomBubble.radius);
+ if (zoomBubble.caption != null) {
+ pw.println("bubble" + i + ".caption=" + zoomBubble.caption);
+ pw.println("bubble" + i + ".captionOffsetX="
+ + zoomBubble.captionOffsetX);
+ pw.println("bubble" + i + ".captionOffsetY="
+ + zoomBubble.captionOffsetY);
+ }
+ pw.println("bubble" + i + ".isInverted="
+ + zoomBubble.isInverted);
+ }
+ pw.flush();
+ pw.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void loadBubbles(File file) {
+ try {
+ Properties props = new Properties();
+ props.load(new FileReader(file));
+
+ zoomBubbles.clear();
+ int count = Integer.parseInt(props.getProperty("count"));
+ for (int i = 0; i < count; i++) {
+ ZoomBubble zoomBubble = new ZoomBubble();
+ zoomBubble.centerX = Double.parseDouble(props
+ .getProperty("bubble" + i + ".centerX"));
+ zoomBubble.centerY = Double.parseDouble(props
+ .getProperty("bubble" + i + ".centerY"));
+ zoomBubble.radius = Double.parseDouble(props
+ .getProperty("bubble" + i + ".radius"));
+ zoomBubble.caption = props.getProperty("bubble" + i
+ + ".caption");
+ if (zoomBubble.caption != null) {
+ zoomBubble.captionOffsetX = Double.parseDouble(props
+ .getProperty("bubble" + i + ".captionOffsetX"));
+ zoomBubble.captionOffsetY = Double.parseDouble(props
+ .getProperty("bubble" + i + ".captionOffsetY"));
+ }
+ String invertedKey = "bubble" + i + ".isInverted";
+ if (props.containsKey(invertedKey)) {
+ zoomBubble.isInverted = Boolean.parseBoolean(props
+ .getProperty(invertedKey));
+ } else {
+ zoomBubble.isInverted = false;
+ }
+ zoomBubbles.add(zoomBubble);
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/substance/src/tools/java/tools/jitterbug/JColorComponent.java b/substance/src/tools/java/tools/jitterbug/JColorComponent.java
new file mode 100644
index 0000000..8885b69
--- /dev/null
+++ b/substance/src/tools/java/tools/jitterbug/JColorComponent.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.jitterbug;
+
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.*;
+
+import org.pushingpixels.lafwidget.utils.RenderingUtils;
+
+public class JColorComponent extends JComponent {
+ private JRadioButton radio;
+
+ private Color selectedColor;
+
+ private String name;
+
+ private ColorVisualizer visualizer;
+
+ private class ColorVisualizer extends JComponent {
+ boolean isRollover;
+
+ public ColorVisualizer() {
+ this.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (!isEnabled())
+ return;
+
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ radio.setSelected(true);
+ Color selected = JColorChooser.showDialog(
+ ColorVisualizer.this, "Color chooser",
+ selectedColor);
+ if (selected != null) {
+ Color old = selectedColor;
+ selectedColor = selected;
+ JColorComponent.this.firePropertyChange(
+ "selectedColor", old, selectedColor);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ if (!isEnabled())
+ return;
+
+ isRollover = true;
+ repaint();
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (!isEnabled())
+ return;
+
+ isRollover = false;
+ repaint();
+ }
+ });
+ this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+ this.setToolTipText("Open color chooser and change the color");
+ this.isRollover = false;
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ RenderingUtils.installDesktopHints(g2d, this);
+ g2d.setFont(UIManager.getFont("Label.font"));
+
+ if (selectedColor != null) {
+ g2d.setColor(selectedColor);
+ g2d.fillRect(2, 2, 100, getHeight() - 4);
+ g2d.setStroke(new BasicStroke(isRollover ? 2.5f : 1.0f));
+ g2d.setColor(selectedColor.darker());
+ g2d.drawRect(2, 2, 99, getHeight() - 5);
+
+ g2d.setColor(Color.black);
+ g2d.drawString(getEncodedColor(), 108, (getHeight() + g2d
+ .getFontMetrics().getHeight())
+ / 2 - g2d.getFontMetrics().getDescent());
+ } else {
+ g2d.setColor(isEnabled() ? Color.gray : Color.lightGray);
+ g2d.drawString("click to choose", 5, (getHeight() + g2d
+ .getFontMetrics().getHeight())
+ / 2 - g2d.getFontMetrics().getDescent());
+ }
+
+ g2d.dispose();
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(200, 25);
+ }
+ }
+
+ public JColorComponent(String name, Color color) {
+ this.radio = new JRadioButton(name);
+ this.radio.setFocusable(false);
+ this.selectedColor = color;
+ this.visualizer = new ColorVisualizer();
+ this.setLayout(new ColorComponentLayout());
+
+ this.add(this.radio);
+ this.add(this.visualizer);
+ }
+
+ private class ColorComponentLayout implements LayoutManager {
+ @Override
+ public void addLayoutComponent(String name, Component comp) {
+ }
+
+ @Override
+ public void layoutContainer(Container parent) {
+ JColorComponent cc = (JColorComponent) parent;
+ int width = cc.getWidth();
+ int height = cc.getHeight();
+
+ ColorVisualizer cv = cc.visualizer;
+ Dimension cvPref = cv.getPreferredSize();
+ cv.setBounds(width - cvPref.width, 0, cvPref.width, height);
+ cc.radio.setBounds(0, 0, width - cvPref.width, height);
+ }
+
+ @Override
+ public Dimension minimumLayoutSize(Container parent) {
+ return preferredLayoutSize(parent);
+ }
+
+ @Override
+ public Dimension preferredLayoutSize(Container parent) {
+ JColorComponent cc = (JColorComponent) parent;
+ ColorVisualizer cv = cc.visualizer;
+ Dimension cvPref = cv.getPreferredSize();
+ return new Dimension(100 + cvPref.width, cvPref.height);
+ }
+
+ @Override
+ public void removeLayoutComponent(Component comp) {
+ }
+ }
+
+ public String getEncodedColor() {
+ return "#" + encodeColorComponent(selectedColor.getRed())
+ + encodeColorComponent(selectedColor.getGreen())
+ + encodeColorComponent(selectedColor.getBlue());
+ }
+
+ private static String encodeColorComponent(int colorComp) {
+ String hex = "0123456789ABCDEF";
+ return "" + hex.charAt(colorComp / 16) + hex.charAt(colorComp % 16);
+ }
+
+ public JRadioButton getRadio() {
+ return radio;
+ }
+
+ public void setColor(Color color, boolean firePropertyChange) {
+ Color old = this.selectedColor;
+ this.selectedColor = color;
+ this.repaint();
+ if (firePropertyChange) {
+ this.firePropertyChange("selectedColor", old, selectedColor);
+ }
+ }
+
+ public Color getColor() {
+ return this.selectedColor;
+ }
+
+ public boolean isDefined() {
+ return (this.selectedColor != null);
+ }
+}
diff --git a/substance/src/tools/java/tools/jitterbug/JColorSchemeComponent.java b/substance/src/tools/java/tools/jitterbug/JColorSchemeComponent.java
new file mode 100644
index 0000000..d3ce533
--- /dev/null
+++ b/substance/src/tools/java/tools/jitterbug/JColorSchemeComponent.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.jitterbug;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.*;
+import javax.swing.event.ChangeListener;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+
+import tools.jitterbug.StateChangeEvent.StateChangeType;
+
+import com.jgoodies.forms.builder.DefaultFormBuilder;
+import com.jgoodies.forms.layout.FormLayout;
+
+public class JColorSchemeComponent extends JPanel {
+ private JCheckBox isLight;
+ private JLabel name;
+ private JColorComponent ultraLight;
+ private JColorComponent extraLight;
+ private JColorComponent light;
+ private JColorComponent mid;
+ private JColorComponent dark;
+ private JColorComponent ultraDark;
+ private JColorComponent foreground;
+ private ButtonGroup bg;
+
+ public JColorSchemeComponent() {
+ FormLayout layout = new FormLayout("fill:pref", "");
+ DefaultFormBuilder formBuilder = new DefaultFormBuilder(layout, this);
+
+ this.bg = new ButtonGroup();
+
+ this.ultraLight = createColorComponent("ultra light");
+ this.extraLight = createColorComponent("extra light");
+ this.light = createColorComponent("light");
+ this.mid = createColorComponent("mid");
+ this.dark = createColorComponent("dark");
+ this.ultraDark = createColorComponent("ultra dark");
+ this.foreground = createColorComponent("foreground");
+
+ JPanel header = new JPanel();
+ FormLayout hLayout = new FormLayout(
+ "fill:pref,2dlu,fill:min(150px;pref):grow,4dlu,right:pref", "");
+ DefaultFormBuilder headerBuilder = new DefaultFormBuilder(hLayout,
+ header);
+ name = new JLabel("");
+ name.setFont(name.getFont().deriveFont(Font.BOLD));
+ isLight = new JCheckBox("light");
+ isLight.setSelected(true);
+ isLight.setFocusable(false);
+ headerBuilder.append(new JLabel("Display name:"));
+ headerBuilder.append(name);
+ headerBuilder.append(isLight);
+
+ isLight.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ fireStateChanged(StateChangeType.MODIFIED);
+ }
+ });
+
+ formBuilder.append(header);
+
+ formBuilder.append(ultraLight);
+ formBuilder.append(extraLight);
+ formBuilder.append(light);
+ formBuilder.append(mid);
+ formBuilder.append(dark);
+ formBuilder.append(ultraDark);
+ formBuilder.append(foreground);
+ }
+
+ private JColorComponent createColorComponent(String label) {
+ JColorComponent result = new JColorComponent(label, null);
+ result.addPropertyChangeListener(new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("selectedColor".equals(evt.getPropertyName())) {
+ fireStateChanged(StateChangeType.MODIFIED);
+ }
+ }
+ });
+ this.bg.add(result.getRadio());
+ return result;
+ }
+
+ public void setContent(SubstanceColorScheme scheme) {
+ this.setEnabled(true);
+
+ ultraLight.setColor(scheme.getUltraLightColor(), false);
+ extraLight.setColor(scheme.getExtraLightColor(), false);
+ light.setColor(scheme.getLightColor(), false);
+ mid.setColor(scheme.getMidColor(), false);
+ dark.setColor(scheme.getDarkColor(), false);
+ ultraDark.setColor(scheme.getUltraDarkColor(), false);
+ foreground.setColor(scheme.getForegroundColor(), false);
+ isLight.setSelected(!scheme.isDark());
+ name.setText(scheme.getDisplayName());
+
+ fireStateChanged(StateChangeType.INITIALIZED);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ this.setEnabledChildren(this, enabled);
+ }
+
+ private void setEnabledChildren(Component c, boolean enabled) {
+ if (c instanceof Container) {
+ Container cont = (Container) c;
+ for (int i = 0; i < cont.getComponentCount(); i++) {
+ Component child = cont.getComponent(i);
+ child.setEnabled(enabled);
+ setEnabledChildren(child, enabled);
+ }
+ }
+ }
+
+ public void clearContent() {
+ this.setEnabled(false);
+
+ ultraLight.setColor(null, false);
+ extraLight.setColor(null, false);
+ light.setColor(null, false);
+ mid.setColor(null, false);
+ dark.setColor(null, false);
+ ultraDark.setColor(null, false);
+ foreground.setColor(null, false);
+ isLight.setSelected(true);
+ name.setText("");
+
+ fireStateChanged(StateChangeType.RESET);
+ }
+
+ public boolean isLight() {
+ return this.isLight.isSelected();
+ }
+
+ public String getDisplayName() {
+ return this.name.getText();
+ }
+
+ public Color getUltraLightColor() {
+ return ultraLight.getColor();
+ }
+
+ public Color getExtraLightColor() {
+ return extraLight.getColor();
+ }
+
+ public Color getLightColor() {
+ return light.getColor();
+ }
+
+ public Color getMidColor() {
+ return mid.getColor();
+ }
+
+ public Color getDarkColor() {
+ return dark.getColor();
+ }
+
+ public Color getUltraDarkColor() {
+ return ultraDark.getColor();
+ }
+
+ public Color getForegroundColor() {
+ return foreground.getColor();
+ }
+
+ public String getEncoded() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(this.getDisplayName() + " {\n");
+ sb.append("\tkind=" + (this.isLight() ? "Light" : "Dark") + "\n");
+ sb.append("\tcolorUltraLight=" + this.ultraLight.getEncodedColor()
+ + "\n");
+ sb.append("\tcolorExtraLight=" + this.extraLight.getEncodedColor()
+ + "\n");
+ sb.append("\tcolorLight=" + this.light.getEncodedColor() + "\n");
+ sb.append("\tcolorMid=" + this.mid.getEncodedColor() + "\n");
+ sb.append("\tcolorDark=" + this.dark.getEncodedColor() + "\n");
+ sb
+ .append("\tcolorUltraDark=" + this.ultraDark.getEncodedColor()
+ + "\n");
+ sb.append("\tcolorForeground=" + this.foreground.getEncodedColor()
+ + "\n");
+ sb.append("}\n");
+ return sb.toString();
+ }
+
+ /**
+ * Adds the specified change listener to track changes to this ribbon.
+ *
+ * @param l
+ * Change listener to add.
+ * @see #addStateChangeListener(StateChangeListener)
+ */
+ public void addStateChangeListener(StateChangeListener l) {
+ this.listenerList.add(StateChangeListener.class, l);
+ }
+
+ /**
+ * Removes the specified change listener from tracking changes to this
+ * ribbon.
+ *
+ * @param l
+ * Change listener to remove.
+ * @see #addStateChangeListener(StateChangeListener)
+ */
+ public void removeStateChangeListener(StateChangeListener l) {
+ this.listenerList.remove(StateChangeListener.class, l);
+ }
+
+ /**
+ * Notifies all registered listener that the state of this component has
+ * changed.
+ */
+ protected void fireStateChanged(
+ StateChangeEvent.StateChangeType stateChangeType) {
+ // Guaranteed to return a non-null array
+ Object[] listeners = this.listenerList.getListenerList();
+ // Process the listeners last to first, notifying
+ // those that are interested in this event
+ StateChangeEvent event = new StateChangeEvent(this, stateChangeType);
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == StateChangeListener.class) {
+ ((StateChangeListener) listeners[i + 1]).stateChanged(event);
+ }
+ }
+ }
+
+ public boolean isDefined() {
+ if ((this.name.getText() == null)
+ || (this.name.getText().trim().length() == 0))
+ return false;
+ if (!this.ultraLight.isDefined())
+ return false;
+ if (!this.extraLight.isDefined())
+ return false;
+ if (!this.light.isDefined())
+ return false;
+ if (!this.mid.isDefined())
+ return false;
+ if (!this.dark.isDefined())
+ return false;
+ if (!this.ultraDark.isDefined())
+ return false;
+ if (!this.foreground.isDefined())
+ return false;
+ return true;
+ }
+
+ public JColorComponent getSelectedColorComponent() {
+ if (this.ultraLight.getRadio().isSelected())
+ return this.ultraLight;
+ if (this.extraLight.getRadio().isSelected())
+ return this.extraLight;
+ if (this.light.getRadio().isSelected())
+ return this.light;
+ if (this.mid.getRadio().isSelected())
+ return this.mid;
+ if (this.dark.getRadio().isSelected())
+ return this.dark;
+ if (this.ultraDark.getRadio().isSelected())
+ return this.ultraDark;
+ if (this.foreground.getRadio().isSelected())
+ return this.foreground;
+ return null;
+ }
+}
diff --git a/substance/src/tools/java/tools/jitterbug/JColorSchemeList.java b/substance/src/tools/java/tools/jitterbug/JColorSchemeList.java
new file mode 100644
index 0000000..4dcc0f9
--- /dev/null
+++ b/substance/src/tools/java/tools/jitterbug/JColorSchemeList.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.jitterbug;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.filechooser.FileNameExtensionFilter;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.renderers.SubstanceDefaultListCellRenderer;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+
+public class JColorSchemeList extends JComponent {
+ private SubstanceSkin.ColorSchemes schemes;
+
+ private JList schemeList;
+
+ private JPanel cardPanel;
+
+ private ColorSchemeListModel schemeListModel;
+
+ static final String LIST = "List";
+
+ static final String INSTRUCTIONAL = "Instructional";
+
+ private File schemesFile;
+
+ private boolean isModified;
+
+ private JButton moveUpButton;
+
+ private JButton moveDownButton;
+
+ private JButton renameButton;
+
+ private JButton deleteButton;
+
+ class ColorSchemeListModel extends AbstractListModel {
+ @Override
+ public int getSize() {
+ return (schemes == null) ? 0 : schemes.size();
+ }
+
+ @Override
+ public Object getElementAt(int index) {
+ if (schemes == null)
+ return null;
+ return schemes.get(index);
+ }
+
+ void fireContentsChanged() {
+ this.fireContentsChanged(schemeList, 0, getSize());
+ }
+ }
+
+ class ColorSchemeListRenderer extends SubstanceDefaultListCellRenderer {
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ SubstanceColorScheme scheme = (SubstanceColorScheme) value;
+ return super.getListCellRendererComponent(list, scheme
+ .getDisplayName(), index, isSelected, cellHasFocus);
+ }
+ }
+
+ public JColorSchemeList() {
+ this.schemes = new SubstanceSkin.ColorSchemes();
+
+ this.schemeList = new JList();
+ this.schemeListModel = new ColorSchemeListModel();
+ this.schemeList.setModel(schemeListModel);
+ this.schemeList.setCellRenderer(new ColorSchemeListRenderer());
+ this.schemeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ this.schemeList.getSelectionModel().addListSelectionListener(
+ new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ Object selected = schemeList.getSelectedValue();
+ firePropertyChange("selectedColorScheme", null,
+ selected);
+ }
+ });
+
+ JPanel bottomButtonsPanel = new JPanel(new FlowLayout(
+ FlowLayout.TRAILING));
+ JButton addColorScheme = new JButton();
+ addColorScheme.setToolTipText("Adds a new color scheme");
+ addColorScheme.setIcon(new ImageIcon(JColorSchemeList.class
+ .getClassLoader().getResource("tools/jitterbug/add.png")));
+ addColorScheme.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ String newName = getNewColorSchemeName(null);
+ if (newName == null)
+ return;
+
+ Color[] colors = new Color[] { Color.white,
+ Color.white, Color.white, Color.white,
+ Color.white, Color.white, Color.black };
+ SubstanceColorScheme newScheme = SubstanceColorSchemeUtilities
+ .getLightColorScheme(newName, colors);
+
+ schemes.add(newScheme);
+ schemeListModel.fireContentsChanged();
+ schemeList.setSelectedValue(newScheme, true);
+ setModified(true);
+ }
+ });
+ }
+ });
+
+ bottomButtonsPanel.add(addColorScheme);
+
+ bottomButtonsPanel.add(Box.createHorizontalStrut(10));
+
+ moveUpButton = new JButton();
+ moveUpButton
+ .setToolTipText("Moves the currently selected color scheme up");
+ moveUpButton.setEnabled(false);
+ moveUpButton.setIcon(new ImageIcon(JColorSchemeList.class
+ .getClassLoader().getResource("tools/jitterbug/arrow_up.png")));
+ moveUpButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ SubstanceColorScheme selected = (SubstanceColorScheme) schemeList
+ .getSelectedValue();
+ schemes.switchWithPrevious(selected.getDisplayName());
+ schemeListModel.fireContentsChanged();
+ schemeList.setSelectedValue(selected, true);
+ setModified(true);
+ }
+ });
+ }
+ });
+ bottomButtonsPanel.add(moveUpButton);
+
+ moveDownButton = new JButton();
+ moveDownButton
+ .setToolTipText("Moves the currently selected color scheme down");
+ moveDownButton.setEnabled(false);
+ moveDownButton
+ .setIcon(new ImageIcon(JColorSchemeList.class.getClassLoader()
+ .getResource("tools/jitterbug/arrow_down.png")));
+ moveDownButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ SubstanceColorScheme selected = (SubstanceColorScheme) schemeList
+ .getSelectedValue();
+ schemes.switchWithNext(selected.getDisplayName());
+ schemeListModel.fireContentsChanged();
+ schemeList.setSelectedValue(selected, true);
+ setModified(true);
+ }
+ });
+ }
+ });
+ bottomButtonsPanel.add(moveDownButton);
+
+ bottomButtonsPanel.add(Box.createHorizontalStrut(10));
+
+ renameButton = new JButton();
+ renameButton
+ .setToolTipText("Rename the currently selected color scheme");
+ renameButton.setEnabled(false);
+ renameButton.setIcon(new ImageIcon(JColorSchemeList.class
+ .getClassLoader().getResource(
+ "tools/jitterbug/chart_line_edit.png")));
+ renameButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ SubstanceColorScheme selected = (SubstanceColorScheme) schemeList
+ .getSelectedValue();
+ String newName = getNewColorSchemeName(selected
+ .getDisplayName());
+ if (newName == null)
+ return;
+
+ if (newName.equals(selected.getDisplayName()))
+ return;
+
+ boolean isLight = !selected.isDark();
+ Color ultraDark = selected.getUltraDarkColor();
+ Color dark = selected.getDarkColor();
+ Color mid = selected.getMidColor();
+ Color light = selected.getLightColor();
+ Color extraLight = selected.getExtraLightColor();
+ Color ultraLight = selected.getUltraLightColor();
+ Color foreground = selected.getForegroundColor();
+
+ Color[] colors = new Color[] { ultraLight, extraLight,
+ light, mid, dark, ultraDark, foreground };
+ SubstanceColorScheme renamedScheme = isLight ? SubstanceColorSchemeUtilities
+ .getLightColorScheme(newName, colors)
+ : SubstanceColorSchemeUtilities
+ .getDarkColorScheme(newName, colors);
+
+ schemes.replace(selected.getDisplayName(),
+ renamedScheme);
+ schemeListModel.fireContentsChanged();
+ schemeList.getSelectionModel().clearSelection();
+ schemeList.setSelectedValue(renamedScheme, true);
+ setModified(true);
+ }
+ });
+ }
+ });
+
+ bottomButtonsPanel.add(renameButton);
+
+ deleteButton = new JButton();
+ deleteButton
+ .setToolTipText("Removes the currently selected color scheme");
+ deleteButton.setEnabled(false);
+ deleteButton.setIcon(new ImageIcon(JColorSchemeList.class
+ .getClassLoader().getResource("tools/jitterbug/delete.png")));
+ deleteButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ SubstanceColorScheme selected = (SubstanceColorScheme) schemeList
+ .getSelectedValue();
+ schemes.delete(selected.getDisplayName());
+ schemeListModel.fireContentsChanged();
+ schemeList.getSelectionModel().clearSelection();
+ setModified(true);
+ }
+ });
+ }
+ });
+
+ bottomButtonsPanel.add(deleteButton);
+
+ this.schemeList.getSelectionModel().addListSelectionListener(
+ new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ int selectedIndex = schemeList.getSelectedIndex();
+ boolean hasSelection = (selectedIndex >= 0);
+ renameButton.setEnabled(hasSelection);
+ deleteButton.setEnabled(hasSelection);
+
+ moveUpButton.setEnabled(hasSelection
+ && (selectedIndex > 0));
+ moveDownButton.setEnabled(hasSelection
+ && (selectedIndex < (schemes.size() - 1)));
+ }
+ });
+
+ JPanel mainControlPanel = new JPanel(new BorderLayout());
+ mainControlPanel.add(new JScrollPane(this.schemeList),
+ BorderLayout.CENTER);
+ mainControlPanel.add(bottomButtonsPanel, BorderLayout.SOUTH);
+
+ // Set up a card panel. It has two cards:
+ // * instructional text
+ // * list of color schemes with button controls
+ this.cardPanel = new JPanel(new CardLayout());
+ JTextArea textArea = new JTextArea(
+ "List of color schemes. Use one of the following:\n * Drag a .colorschemes file here\n * Click the \"New\" button below");
+ textArea.setBorder(new EmptyBorder(10, 8, 0, 8));
+ textArea.setEditable(false);
+ textArea.setOpaque(false);
+ textArea.setRows(9);
+ // textArea.setColumns(20);
+ this.cardPanel.add(textArea, INSTRUCTIONAL);
+ this.cardPanel.add(mainControlPanel, LIST);
+
+ this.setLayout(new BorderLayout());
+ this.add(this.cardPanel, BorderLayout.CENTER);
+
+ // constrain the preferred size so that the layout doesn't
+ // change once the control panel is shown
+ this.setPreferredSize(textArea.getPreferredSize());
+ }
+
+ public void setColorSchemeList(File file) {
+ if (!this.checkModifiedStateAndSaveIfNecessary()) {
+ return;
+ }
+ try {
+ this.schemesFile = file;
+ if (file != null) {
+ this.schemes = SubstanceSkin.getColorSchemes(file.toURI()
+ .toURL());
+ } else {
+ this.schemes = new SubstanceSkin.ColorSchemes();
+ }
+ // switch to the list view
+ CardLayout cl = (CardLayout) (cardPanel.getLayout());
+ cl.show(cardPanel, LIST);
+ // let the view know that the model has changed
+ schemeListModel.fireContentsChanged();
+ // update the buttons state
+ if (file == null) {
+ moveUpButton.setEnabled(false);
+ moveDownButton.setEnabled(false);
+ deleteButton.setEnabled(false);
+ renameButton.setEnabled(false);
+ }
+ this.setModified(false);
+ } catch (Exception exc) {
+ exc.printStackTrace();
+ }
+ }
+
+ public boolean checkModifiedStateAndSaveIfNecessary() {
+ if (!this.isModified) {
+ return true;
+ }
+
+ // does the user want to save modifications?
+ String fileName = (this.schemesFile == null) ? "Unsaved"
+ : this.schemesFile.getName();
+ int userSelection = JOptionPane.showConfirmDialog(SwingUtilities
+ .getWindowAncestor(this),
+ "Do you want to save the changes to '" + fileName + "'?",
+ "Modified contents", JOptionPane.YES_NO_CANCEL_OPTION);
+ if (userSelection == JOptionPane.CANCEL_OPTION) {
+ return false;
+ }
+ if (userSelection == JOptionPane.YES_OPTION) {
+ if (this.schemesFile != null) {
+ // ask to save
+ this.save();
+ } else {
+ this.saveAs();
+ }
+ }
+ return true;
+ }
+
+ private String getFileNameForSaving() {
+ JFileChooser jfc = new JFileChooser();
+ FileNameExtensionFilter filter = new FileNameExtensionFilter(
+ "Color scheme files", "colorschemes");
+ jfc.setFileFilter(filter);
+ int returnVal = jfc.showSaveDialog(SwingUtilities
+ .getWindowAncestor(this));
+ if (returnVal == JFileChooser.APPROVE_OPTION) {
+ String res = jfc.getSelectedFile().getAbsolutePath();
+ if (!res.endsWith(".colorschemes"))
+ res += ".colorschemes";
+ return res;
+ }
+ return null;
+ }
+
+ private String getNewColorSchemeName(String nameToStartWith) {
+ String result = JOptionPane.showInputDialog(SwingUtilities
+ .getWindowAncestor(this), "Type the color scheme name",
+ nameToStartWith);
+ if (result == null)
+ return null;
+
+ if (result.equals(nameToStartWith))
+ return result;
+
+ result = result.trim();
+
+ // check characters
+ Pattern pattern = Pattern.compile("[a-zA-Z ]+");
+ Matcher matcher = pattern.matcher(result);
+ if (!matcher.matches()) {
+ JOptionPane.showMessageDialog(SwingUtilities
+ .getWindowAncestor(this), "Only use letters and spaces",
+ "Name invalid", JOptionPane.ERROR_MESSAGE);
+ return null;
+ }
+
+ // check for uniqueness
+ SubstanceColorScheme existing = this.schemes.get(result);
+ if (existing != null) {
+ JOptionPane.showMessageDialog(SwingUtilities
+ .getWindowAncestor(this), "Name already exists",
+ "Name clash", JOptionPane.ERROR_MESSAGE);
+ return null;
+ }
+ return result;
+ }
+
+ public void save() {
+ this.saveAs(this.schemesFile);
+ }
+
+ public void saveAs() {
+ // ask for a filename for the save
+ String fileName = this.getFileNameForSaving();
+ if (fileName != null) {
+ File file = new File(fileName);
+ this.saveAs(file);
+ }
+ }
+
+ private void saveAs(File file) {
+ PrintStream printStream = null;
+
+ try {
+ printStream = new PrintStream(new FileOutputStream(file));
+ for (int i = 0; i < this.schemes.size(); i++) {
+ SubstanceColorScheme colorScheme = this.schemes.get(i);
+ String encodedColorScheme = colorScheme.toString();
+ printStream.println(encodedColorScheme);
+ printStream.println();
+ }
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ if (printStream != null) {
+ printStream.close();
+ }
+ this.schemesFile = file;
+ this.setModified(false);
+ }
+
+ public void setModified(boolean isModified) {
+ if (this.isModified == isModified)
+ return;
+
+ boolean old = this.isModified;
+ this.isModified = isModified;
+ this.firePropertyChange("modified", old, isModified);
+ }
+
+ public File getCurrentFile() {
+ return this.schemesFile;
+ }
+
+ public void updateColorScheme(SubstanceColorScheme colorScheme) {
+ SubstanceColorScheme existing = this.schemes.get(colorScheme
+ .getDisplayName());
+ if (existing == null)
+ throw new IllegalArgumentException("Color scheme "
+ + colorScheme.getDisplayName() + " not found");
+
+ this.schemes.replace(colorScheme.getDisplayName(), colorScheme);
+ }
+
+ public boolean isModified() {
+ return isModified;
+ }
+}
diff --git a/substance/src/tools/java/tools/jitterbug/JHsvGraph.java b/substance/src/tools/java/tools/jitterbug/JHsvGraph.java
new file mode 100644
index 0000000..1327046
--- /dev/null
+++ b/substance/src/tools/java/tools/jitterbug/JHsvGraph.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.jitterbug;
+
+import java.awt.*;
+
+import javax.swing.JComponent;
+import javax.swing.UIManager;
+
+import org.pushingpixels.lafwidget.utils.RenderingUtils;
+
+public class JHsvGraph extends JComponent {
+ private static Color COLOR_HUE = new Color(159, 41, 54);
+ private static Color COLOR_SAT = new Color(27, 88, 124);
+ private static Color COLOR_BRI = new Color(241, 135, 23);
+
+ private Color[] colors;
+
+ public JHsvGraph() {
+ }
+
+ public void setColors(Color[] colors) {
+ this.colors = colors;
+ this.repaint();
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ int w = getWidth();
+ int h = getHeight();
+
+ Graphics2D g2d = (Graphics2D) g.create();
+ RenderingUtils.installDesktopHints(g2d, this);
+
+ g2d.setColor(Color.black);
+
+ int xOffset = g2d.getFontMetrics().stringWidth("255") + 5;
+ int yOffset = 2 * g2d.getFontMetrics().getHeight();
+
+ g2d.drawLine(xOffset, yOffset / 2, xOffset, h - yOffset);
+ g2d.drawLine(xOffset, h - yOffset, w - 1, h - yOffset);
+
+ String keys = "0123456789ABCDEF";
+ for (int i = 0; i < keys.length(); i++) {
+ String key = keys.charAt(i) + "F";
+ int y = yOffset + (h - 2 * yOffset) - (h - 2 * yOffset) * (i + 1)
+ / 16;
+ g2d.setColor(Color.lightGray);
+ g2d.drawLine(xOffset + 1, y, w - 1, y);
+ g2d.setColor(Color.gray);
+ g2d.drawString(key, xOffset - 5
+ - g2d.getFontMetrics().stringWidth(key), y
+ + g2d.getFontMetrics().getDescent());
+ }
+
+ if (this.colors != null) {
+ float[] hue = new float[this.colors.length];
+ float[] sat = new float[this.colors.length];
+ float[] bri = new float[this.colors.length];
+ int index = 0;
+ for (Color c : this.colors) {
+ float[] hsb = Color.RGBtoHSB(c.getRed(), c.getGreen(), c
+ .getBlue(), null);
+ hue[index] = hsb[0];
+ sat[index] = hsb[1];
+ bri[index] = hsb[2];
+ index++;
+ }
+
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ // Hue
+ g2d.setColor(COLOR_HUE);
+ for (int i = 0; i < this.colors.length; i++) {
+ int x = xOffset + 5 + (w - 1 - xOffset - 10) * i
+ / (this.colors.length - 1);
+ int yHue = yOffset + (h - 2 * yOffset)
+ - (int) ((h - 2 * yOffset) * hue[i]);
+ g2d.fillRect(x - 2, yHue - 2, 5, 5);
+
+ if (i > 0) {
+ int prevHue = yOffset + (h - 2 * yOffset)
+ - (int) ((h - 2 * yOffset) * hue[i - 1]);
+ int xPrev = xOffset + 5 + (w - 1 - xOffset - 10) * (i - 1)
+ / (this.colors.length - 1);
+ g2d.drawLine(xPrev, prevHue, x, yHue);
+ }
+ }
+
+ // Saturation
+ g2d.setColor(COLOR_SAT);
+ for (int i = 0; i < this.colors.length; i++) {
+ int x = xOffset + 5 + (w - 1 - xOffset - 10) * i
+ / (this.colors.length - 1);
+ int ySat = yOffset + (h - 2 * yOffset)
+ - (int) ((h - 2 * yOffset) * sat[i]);
+ g2d.fillRect(x - 2, ySat - 2, 5, 5);
+
+ if (i > 0) {
+ int prevSat = yOffset + (h - 2 * yOffset)
+ - (int) ((h - 2 * yOffset) * sat[i - 1]);
+ int xPrev = xOffset + 5 + (w - 1 - xOffset - 10) * (i - 1)
+ / (this.colors.length - 1);
+ g2d.drawLine(xPrev, prevSat, x, ySat);
+ }
+ }
+
+ // Brightness
+ g2d.setColor(COLOR_BRI);
+ for (int i = 0; i < this.colors.length; i++) {
+ int x = xOffset + 5 + (w - 1 - xOffset - 10) * i
+ / (this.colors.length - 1);
+ int yBri = yOffset + (h - 2 * yOffset)
+ - (int) ((h - 2 * yOffset) * bri[i]);
+ g2d.fillRect(x - 2, yBri - 2, 5, 5);
+
+ if (i > 0) {
+ int prevBri = yOffset + (h - 2 * yOffset)
+ - (int) ((h - 2 * yOffset) * bri[i - 1]);
+ int xPrev = xOffset + 5 + (w - 1 - xOffset - 10) * (i - 1)
+ / (this.colors.length - 1);
+ g2d.drawLine(xPrev, prevBri, x, yBri);
+ }
+ }
+
+ g2d.setFont(UIManager.getFont("Label.font").deriveFont(Font.BOLD));
+ int labelWidth = g2d.getFontMetrics().stringWidth("WWW") + 20;
+ int xLabel = xOffset + (w - xOffset - 3 * labelWidth) / 2;
+
+ int lh = h - g2d.getFontMetrics().getAscent() / 2 + 2 - yOffset / 2;
+ g2d.setColor(COLOR_HUE);
+ g2d.drawLine(xLabel, lh, xLabel + 15, lh);
+ g2d.fillRect(xLabel + 6, lh - 2, 5, 5);
+ g2d.drawString("Hue", xLabel + 20, h - yOffset / 2);
+ xLabel += labelWidth;
+
+ g2d.setColor(COLOR_SAT);
+ g2d.drawLine(xLabel, lh, xLabel + 15, lh);
+ g2d.fillRect(xLabel + 6, lh - 2, 5, 5);
+ g2d.drawString("Sat", xLabel + 20, h - yOffset / 2);
+ xLabel += labelWidth;
+
+ g2d.setColor(COLOR_BRI);
+ g2d.drawLine(xLabel, lh, xLabel + 15, lh);
+ g2d.fillRect(xLabel + 6, lh - 2, 5, 5);
+ g2d.drawString("Bri", xLabel + 20, h - yOffset / 2);
+ xLabel += labelWidth;
+ }
+
+ g2d.dispose();
+ }
+}
diff --git a/substance/src/tools/java/tools/jitterbug/JitterbugEditor.java b/substance/src/tools/java/tools/jitterbug/JitterbugEditor.java
new file mode 100644
index 0000000..a336167
--- /dev/null
+++ b/substance/src/tools/java/tools/jitterbug/JitterbugEditor.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.jitterbug;
+
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.dnd.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.InputStream;
+
+import javax.swing.*;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.skin.BusinessSkin;
+import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
+
+import tools.common.JImageComponent;
+import tools.jitterbug.StateChangeEvent.StateChangeType;
+
+import com.jgoodies.forms.builder.DefaultFormBuilder;
+import com.jgoodies.forms.layout.FormLayout;
+
+public class JitterbugEditor extends JFrame implements ClipboardOwner {
+ private static final String APP_TITLE = "Jitterbug color scheme editor";
+ private JColorSchemeList colorSchemeList;
+ private JColorSchemeComponent colorSchemeComp;
+ private JHsvGraph hsvGraph;
+
+ private class JitterbugLogo implements Icon {
+ @Override
+ public int getIconHeight() {
+ return 16;
+ }
+
+ @Override
+ public int getIconWidth() {
+ return 16;
+ }
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ Graphics2D g2d = (Graphics2D) g.create();
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.translate(x, y);
+
+ double coef1 = (double) getIconWidth()
+ / (double) substance.getOrigWidth();
+ double coef2 = (double) getIconHeight()
+ / (double) substance.getOrigHeight();
+ double coef = Math.min(coef1, coef2);
+ g2d.scale(coef, coef);
+ g2d.translate(substance.getOrigX(), substance.getOrigY());
+ substance.paint(g2d);
+ g2d.dispose();
+ }
+ }
+
+ protected class JitterbugDropHandler extends DropTargetAdapter {
+ @Override
+ public void drop(DropTargetDropEvent dtde) {
+ Transferable t = dtde.getTransferable();
+ if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
+ try {
+ dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+ java.util.List files = (java.util.List) t
+ .getTransferData(DataFlavor.javaFileListFlavor);
+ File f = (File) files.get(0);
+ System.out.println("Reading from " + f.getAbsolutePath());
+ colorSchemeList.setColorSchemeList(f);
+ colorSchemeComp.clearContent();
+ dtde.dropComplete(true);
+ JitterbugEditor.this.setTitle(APP_TITLE + " - "
+ + f.getAbsolutePath());
+ return;
+ } catch (Exception exc) {
+ exc.printStackTrace();
+ }
+ }
+ // if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
+ // try {
+ // dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+ // String content = (String) t
+ // .getTransferData(DataFlavor.stringFlavor);
+ // colorSchemeComp.setContent(new ByteArrayInputStream(content
+ // .getBytes()));
+ // dtde.dropComplete(true);
+ // return;
+ // } catch (Exception exc) {
+ // exc.printStackTrace();
+ // }
+ // }
+ }
+
+ @Override
+ public void dragEnter(DropTargetDragEvent dtde) {
+ for (DataFlavor df : dtde.getCurrentDataFlavors()) {
+ Class<?> repClass = df.getDefaultRepresentationClass();
+ boolean canDrop = InputStream.class.isAssignableFrom(repClass);
+ if (canDrop) {
+ dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
+ return;
+ }
+ }
+ }
+ }
+
+ public JitterbugEditor() {
+ super();
+
+ BufferedImage iconImage = GraphicsEnvironment
+ .getLocalGraphicsEnvironment().getDefaultScreenDevice()
+ .getDefaultConfiguration().createCompatibleImage(16, 16,
+ Transparency.TRANSLUCENT);
+ new JitterbugLogo().paintIcon(this, iconImage.getGraphics(), 0, 0);
+ this.setIconImage(iconImage);
+
+ FormLayout leftPanelLayout = new FormLayout("fill:pref",
+ "fill:pref, fill:pref, fill:pref:grow, fill:pref");
+ DefaultFormBuilder leftPanelBuilder = new DefaultFormBuilder(
+ leftPanelLayout);
+
+ this.colorSchemeList = new JColorSchemeList();
+ this.colorSchemeList.setDropTarget(new DropTarget(this,
+ new JitterbugDropHandler()));
+ leftPanelBuilder.append(this.colorSchemeList);
+
+ this.colorSchemeComp = new JColorSchemeComponent();
+ this.colorSchemeComp.setEnabled(false);
+ this.colorSchemeComp.setDropTarget(new DropTarget(this,
+ new JitterbugDropHandler()));
+ leftPanelBuilder.append(this.colorSchemeComp);
+
+ this.hsvGraph = new JHsvGraph();
+ this.hsvGraph.setDropTarget(new DropTarget(this,
+ new JitterbugDropHandler()));
+ leftPanelBuilder.append(this.hsvGraph);
+
+ JPanel controlsPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
+
+ final JButton saveButton = new JButton("save");
+ saveButton.setIcon(new ImageIcon(JitterbugEditor.class.getClassLoader()
+ .getResource("tools/jitterbug/page_save.png")));
+ saveButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ colorSchemeList.save();
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ saveButton.setEnabled(false);
+ }
+ });
+ }
+ });
+ saveButton.setEnabled(false);
+ controlsPanel.add(saveButton);
+
+ JButton saveAsButton = new JButton("save as...");
+ saveAsButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ colorSchemeList.saveAs();
+ updateMainWindowTitle(colorSchemeList.isModified());
+ }
+ });
+ controlsPanel.add(saveAsButton);
+
+ JButton newButton = new JButton("new");
+ newButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ colorSchemeList.setColorSchemeList(null);
+ colorSchemeComp.clearContent();
+ updateMainWindowTitle(colorSchemeList.isModified());
+ }
+ });
+
+ controlsPanel.add(Box.createHorizontalStrut(20));
+ controlsPanel.add(newButton);
+
+ leftPanelBuilder.append(controlsPanel);
+
+ JPanel leftPanel = leftPanelBuilder.getPanel();
+ // wire drag and drop
+ wireDragAndDrop(leftPanel);
+
+ // wire color scheme selection in the list to the
+ // color scheme component
+ this.colorSchemeList.addPropertyChangeListener("selectedColorScheme",
+ new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ SubstanceColorScheme newSelection = (SubstanceColorScheme) evt
+ .getNewValue();
+ if (newSelection != null)
+ colorSchemeComp.setContent(newSelection);
+ else
+ colorSchemeComp.clearContent();
+ }
+ });
+ // track color modifications of the currently selected
+ // color scheme
+ this.colorSchemeComp.addStateChangeListener(new StateChangeListener() {
+ @Override
+ public void stateChanged(StateChangeEvent event) {
+ if (event.getStateChangeType() == StateChangeType.MODIFIED) {
+ // let the color scheme list know that there was change
+ colorSchemeList.setModified(true);
+ }
+ // update the HSV graph component
+ if (colorSchemeComp.isDefined()) {
+ Color[] colors = new Color[] {
+ colorSchemeComp.getUltraLightColor(),
+ colorSchemeComp.getExtraLightColor(),
+ colorSchemeComp.getLightColor(),
+ colorSchemeComp.getMidColor(),
+ colorSchemeComp.getDarkColor(),
+ colorSchemeComp.getUltraDarkColor() };
+ hsvGraph.setColors(colors);
+ } else {
+ hsvGraph.setColors(null);
+ }
+
+ if (event.getStateChangeType() == StateChangeType.MODIFIED) {
+ if (colorSchemeComp.isDefined()) {
+ boolean isLight = colorSchemeComp.isLight();
+ Color ultraDark = colorSchemeComp.getUltraDarkColor();
+ Color dark = colorSchemeComp.getDarkColor();
+ Color mid = colorSchemeComp.getMidColor();
+ Color light = colorSchemeComp.getLightColor();
+ Color extraLight = colorSchemeComp.getExtraLightColor();
+ Color ultraLight = colorSchemeComp.getUltraLightColor();
+ Color foreground = colorSchemeComp.getForegroundColor();
+ String name = colorSchemeComp.getDisplayName();
+
+ Color[] colors = new Color[] { ultraLight, extraLight,
+ light, mid, dark, ultraDark, foreground };
+ SubstanceColorScheme scheme = isLight ? SubstanceColorSchemeUtilities
+ .getLightColorScheme(name, colors)
+ : SubstanceColorSchemeUtilities
+ .getDarkColorScheme(name, colors);
+ colorSchemeList.updateColorScheme(scheme);
+ }
+ }
+ }
+ });
+ // track modification changes on the scheme list and any scheme in it
+ this.colorSchemeList.addPropertyChangeListener("modified",
+ new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ boolean isModified = (Boolean) evt.getNewValue();
+ getRootPane().putClientProperty(
+ SubstanceLookAndFeel.WINDOW_MODIFIED,
+ isModified);
+
+ // update the main frame title
+ updateMainWindowTitle(isModified);
+
+ File currFile = colorSchemeList.getCurrentFile();
+ saveButton.setEnabled(currFile != null);
+ }
+ });
+
+ this.add(leftPanel, BorderLayout.WEST);
+
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ JImageComponent imageComp = new JImageComponent(true);
+ imageComp
+ .setLegend(new String[] {
+ "Image panel. Use one of the following to show an image:",
+ "\t* Right-click to paste an image from the clipboard",
+ "\t* Drag and drop an image file from local disk or another app",
+ "\t* Drag and drop a URL pointing to an image" });
+
+ imageComp.addPropertyChangeListener("selectedColor",
+ new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ Color selectedImageColor = (Color) evt.getNewValue();
+ JColorComponent selectedColorComp = colorSchemeComp
+ .getSelectedColorComponent();
+ if (selectedColorComp != null) {
+ selectedColorComp
+ .setColor(selectedImageColor, true);
+ }
+ }
+ });
+
+ mainPanel.add(imageComp, BorderLayout.CENTER);
+
+ this.add(mainPanel, BorderLayout.CENTER);
+
+ this.setSize(800, 700);
+ this.setExtendedState(JFrame.MAXIMIZED_BOTH);
+ this.setLocationRelativeTo(null);
+ this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+
+ this.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ // do we need to save the modified scheme list?
+ if (colorSchemeList.checkModifiedStateAndSaveIfNecessary()) {
+ dispose();
+ }
+ }
+ });
+
+ this.updateMainWindowTitle(false);
+ }
+
+ private void wireDragAndDrop(Component comp) {
+ comp.setDropTarget(new DropTarget(this, new JitterbugDropHandler()));
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++) {
+ wireDragAndDrop(cont.getComponent(i));
+ }
+ }
+ }
+
+ @Override
+ public void lostOwnership(Clipboard clipboard, Transferable contents) {
+ }
+
+ private void updateMainWindowTitle(boolean isModified) {
+ File schemesFile = colorSchemeList.getCurrentFile();
+ String title = APP_TITLE + " - ";
+ if (isModified) {
+ title += "* ";
+ }
+ if (schemesFile != null) {
+ title += schemesFile.getAbsolutePath();
+ } else {
+ title += "Unsaved";
+ }
+ setTitle(title);
+ }
+
+ public static void main(String[] args) {
+ JDialog.setDefaultLookAndFeelDecorated(true);
+ JFrame.setDefaultLookAndFeelDecorated(true);
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ SubstanceLookAndFeel.setSkin(new BusinessSkin());
+ new JitterbugEditor().setVisible(true);
+ }
+ });
+ }
+
+}
diff --git a/substance/src/tools/java/tools/jitterbug/StateChangeEvent.java b/substance/src/tools/java/tools/jitterbug/StateChangeEvent.java
new file mode 100644
index 0000000..e87050b
--- /dev/null
+++ b/substance/src/tools/java/tools/jitterbug/StateChangeEvent.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.jitterbug;
+
+import java.util.EventObject;
+
+public class StateChangeEvent extends EventObject {
+ private StateChangeType stateChangeType;
+
+ public static enum StateChangeType {
+ INITIALIZED, MODIFIED, RESET
+ }
+
+ public StateChangeEvent(Object source, StateChangeType stateChangeType) {
+ super(source);
+ this.stateChangeType = stateChangeType;
+ }
+
+ public StateChangeType getStateChangeType() {
+ return stateChangeType;
+ }
+}
diff --git a/substance/src/tools/java/tools/jitterbug/StateChangeListener.java b/substance/src/tools/java/tools/jitterbug/StateChangeListener.java
new file mode 100644
index 0000000..9d1eaef
--- /dev/null
+++ b/substance/src/tools/java/tools/jitterbug/StateChangeListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.jitterbug;
+
+import java.util.EventListener;
+
+public interface StateChangeListener extends EventListener {
+ public void stateChanged(StateChangeEvent event);
+}
diff --git a/substance/src/tools/java/tools/jitterbug/substance.java b/substance/src/tools/java/tools/jitterbug/substance.java
new file mode 100644
index 0000000..98f2e35
--- /dev/null
+++ b/substance/src/tools/java/tools/jitterbug/substance.java
@@ -0,0 +1,116 @@
+package tools.jitterbug;
+
+import java.awt.*;
+import java.awt.geom.*;
+
+/**
+ * This class has been automatically generated using <a
+ * href="https://flamingo.dev.java.net">Flamingo SVG transcoder</a>.
+ */
+public class substance {
+ /**
+ * Paints the transcoded SVG image on the specified graphics context. You
+ * can install a custom transformation on the graphics context to scale the
+ * image.
+ *
+ * @param g
+ * Graphics context.
+ */
+ public static void paint(Graphics2D g) {
+ Shape shape = null;
+ Paint paint = null;
+ Stroke stroke = null;
+
+ float origAlpha = 1.0f;
+ Composite origComposite = ((Graphics2D)g).getComposite();
+ if (origComposite instanceof AlphaComposite) {
+ AlphaComposite origAlphaComposite =
+ (AlphaComposite)origComposite;
+ if (origAlphaComposite.getRule() == AlphaComposite.SRC_OVER) {
+ origAlpha = origAlphaComposite.getAlpha();
+ }
+ }
+
+ AffineTransform defaultTransform_ = g.getTransform();
+//
+g.setComposite(AlphaComposite.getInstance(3, 1.0f * origAlpha));
+AffineTransform defaultTransform__0 = g.getTransform();
+g.transform(new AffineTransform(1.3333326578140259f, 0.0f, 0.0f, 1.3333326578140259f, 5.900062114960747E-4f, -0.0f));
+// _0
+g.setComposite(AlphaComposite.getInstance(3, 1.0f * origAlpha));
+AffineTransform defaultTransform__0_0 = g.getTransform();
+g.transform(new AffineTransform(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f));
+// _0_0
+g.setComposite(AlphaComposite.getInstance(3, 1.0f * origAlpha));
+AffineTransform defaultTransform__0_0_0 = g.getTransform();
+g.transform(new AffineTransform(-0.752575781317554f, 0.6585056517405703f, -0.6585056517405703f, -0.752575781317554f, 310.2049865722656f, 167.59800720214844f));
+// _0_0_0
+g.setComposite(AlphaComposite.getInstance(3, 1.0f * origAlpha));
+AffineTransform defaultTransform__0_0_0_0 = g.getTransform();
+g.transform(new AffineTransform(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f));
+// _0_0_0_0
+paint = new Color(0, 0, 0, 255);
+stroke = new BasicStroke(20.0f,1,1,3.0f,null,0.0f);
+shape = new GeneralPath();
+((GeneralPath)shape).moveTo(0.0, 139.41);
+((GeneralPath)shape).curveTo(23.64, 156.92, 46.62, 183.6, 74.01, 184.91);
+((GeneralPath)shape).curveTo(95.69, 185.94, 120.12, 171.07, 142.34, 167.23);
+((GeneralPath)shape).curveTo(158.16, 164.5, 172.85, 167.36, 184.27, 175.05);
+((GeneralPath)shape).curveTo(195.56, 182.66, 203.65, 195.0, 201.61, 205.97);
+((GeneralPath)shape).curveTo(200.21, 213.5, 194.06, 220.39, 186.75, 222.32);
+((GeneralPath)shape).curveTo(176.88, 224.94, 164.91, 218.53, 157.54, 209.84);
+((GeneralPath)shape).curveTo(148.63, 199.34, 146.44, 185.49, 147.06, 170.49);
+((GeneralPath)shape).curveTo(147.72, 154.46, 151.57, 137.12, 150.12, 118.98);
+((GeneralPath)shape).curveTo(148.86, 103.29, 143.64, 86.99, 134.79, 74.2);
+((GeneralPath)shape).curveTo(120.67, 53.81, 97.33, 42.33, 75.36, 45.25);
+((GeneralPath)shape).curveTo(63.41, 46.84, 51.87, 52.69, 44.12, 61.07);
+((GeneralPath)shape).curveTo(31.55, 74.67, 28.96, 94.96, 39.07, 110.9);
+((GeneralPath)shape).curveTo(50.18, 128.41, 76.62, 140.68, 95.67, 139.41);
+g.setPaint(paint);
+g.setStroke(stroke);
+g.draw(shape);
+g.setTransform(defaultTransform__0_0_0_0);
+g.setTransform(defaultTransform__0_0_0);
+g.setTransform(defaultTransform__0_0);
+g.setTransform(defaultTransform__0);
+g.setTransform(defaultTransform_);
+
+ }
+
+ /**
+ * Returns the X of the bounding box of the original SVG image.
+ *
+ * @return The X of the bounding box of the original SVG image.
+ */
+ public static int getOrigX() {
+ return -5;
+ }
+
+ /**
+ * Returns the Y of the bounding box of the original SVG image.
+ *
+ * @return The Y of the bounding box of the original SVG image.
+ */
+ public static int getOrigY() {
+ return -21;
+ }
+
+ /**
+ * Returns the width of the bounding box of the original SVG image.
+ *
+ * @return The width of the bounding box of the original SVG image.
+ */
+ public static int getOrigWidth() {
+ return 403;
+ }
+
+ /**
+ * Returns the height of the bounding box of the original SVG image.
+ *
+ * @return The height of the bounding box of the original SVG image.
+ */
+ public static int getOrigHeight() {
+ return 400;
+ }
+}
+
diff --git a/substance/src/tools/java/tools/uidebug/ColorBlindColorScheme.java b/substance/src/tools/java/tools/uidebug/ColorBlindColorScheme.java
new file mode 100644
index 0000000..7810b2d
--- /dev/null
+++ b/substance/src/tools/java/tools/uidebug/ColorBlindColorScheme.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.uidebug;
+
+import java.awt.Color;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.colorscheme.BaseColorScheme;
+
+/**
+ * Base class for color schemes simulating color-blind users.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class ColorBlindColorScheme extends BaseColorScheme {
+ /**
+ * Matrix for converting RGB to LMS.
+ */
+ public double[][] _rgbToLms = { { 0.05059983, 0.08585369, 0.00952420 },
+ { 0.01893033, 0.08925308, 0.01370054 },
+ { 0.00292202, 0.00975732, 0.07145979 } };
+
+ /**
+ * Matrix for converting LMS to RGB.
+ */
+ public double[][] _lmsToRgb = { { 30.830854, -29.832659, 1.610474 },
+ { -6.481468, 17.715578, -2.532642 },
+ { -0.375690, -1.199062, 14.273846 } };
+
+ /**
+ * The main ultra-light color.
+ */
+ private Color mainUltraLightColor;
+
+ /**
+ * The main extra-light color.
+ */
+ private Color mainExtraLightColor;
+
+ /**
+ * The main light color.
+ */
+ private Color mainLightColor;
+
+ /**
+ * The main medium color.
+ */
+ private Color mainMidColor;
+
+ /**
+ * The main dark color.
+ */
+ private Color mainDarkColor;
+
+ /**
+ * The main ultra-dark color.
+ */
+ private Color mainUltraDarkColor;
+
+ /**
+ * The foreground color.
+ */
+ private Color foregroundColor;
+
+ /**
+ * The original color scheme.
+ */
+ private SubstanceColorScheme origScheme;
+
+ /**
+ * Blindness kind.
+ *
+ * @author Kirill Grouchnikov
+ */
+ public enum BlindnessKind {
+ /**
+ * Protanopia color blindness.
+ */
+ PROTANOPIA,
+
+ /**
+ * Deuteranopia color blindness.
+ */
+ DEUTERANOPIA,
+
+ /**
+ * Tritanopia color blindness.
+ */
+ TRITANOPIA
+ }
+
+ /**
+ * Creates a new color scheme that simulates color-blindness.
+ *
+ * @param origScheme
+ * Original color scheme.
+ * @param kind
+ * Color-blindness kind.
+ */
+ public ColorBlindColorScheme(SubstanceColorScheme origScheme,
+ BlindnessKind kind) {
+ super(kind.name() + " " + origScheme.getDisplayName(), origScheme
+ .isDark());
+ this.origScheme = origScheme;
+ this.foregroundColor = getColorBlindColor(origScheme
+ .getForegroundColor(), _rgbToLms, kind, _lmsToRgb);
+ this.mainUltraDarkColor = getColorBlindColor(origScheme
+ .getUltraDarkColor(), _rgbToLms, kind, _lmsToRgb);
+ this.mainDarkColor = getColorBlindColor(origScheme.getDarkColor(),
+ _rgbToLms, kind, _lmsToRgb);
+ this.mainMidColor = getColorBlindColor(origScheme.getMidColor(),
+ _rgbToLms, kind, _lmsToRgb);
+ this.mainLightColor = getColorBlindColor(origScheme.getLightColor(),
+ _rgbToLms, kind, _lmsToRgb);
+ this.mainExtraLightColor = getColorBlindColor(origScheme
+ .getExtraLightColor(), _rgbToLms, kind, _lmsToRgb);
+ this.mainUltraLightColor = getColorBlindColor(origScheme
+ .getUltraLightColor(), _rgbToLms, kind, _lmsToRgb);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getForegroundColor()
+ */
+ @Override
+ public Color getForegroundColor() {
+ return this.foregroundColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraLightColor()
+ */
+ @Override
+ public Color getUltraLightColor() {
+ return this.mainUltraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getExtraLightColor()
+ */
+ @Override
+ public Color getExtraLightColor() {
+ return this.mainExtraLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getLightColor()
+ */
+ @Override
+ public Color getLightColor() {
+ return this.mainLightColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getMidColor()
+ */
+ @Override
+ public Color getMidColor() {
+ return this.mainMidColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getDarkColor()
+ */
+ @Override
+ public Color getDarkColor() {
+ return this.mainDarkColor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.pushingpixels.substance.color.ColorScheme#getUltraDarkColor()
+ */
+ @Override
+ public Color getUltraDarkColor() {
+ return this.mainUltraDarkColor;
+ }
+
+ /**
+ * Returns the original color scheme.
+ *
+ * @return The original color scheme.
+ */
+ public SubstanceColorScheme getOrigScheme() {
+ return this.origScheme;
+ }
+
+ /**
+ * Converts the specified color into color-blind version.
+ *
+ * @param orig
+ * The original color.
+ * @param rgbToLms
+ * RGB to LMS conversion matrix.
+ * @param kind
+ * Color-blindness kind.
+ * @param lmsToRgb
+ * LMS to RGB conversion matrix.
+ * @return Color-blind version of the original color.
+ */
+ private static Color getColorBlindColor(Color orig, double[][] rgbToLms,
+ BlindnessKind kind, double[][] lmsToRgb) {
+ double r = orig.getRed();
+ double g = orig.getGreen();
+ double b = orig.getBlue();
+
+ double[] rgbOrig = new double[] { r, g, b };
+ double[] lms = mult3(rgbToLms, rgbOrig);
+ double tmp = 0.0;
+
+ double[] anchor = { 0.08008, 0.1579, 0.5897, 0.1284, 0.2237, 0.3636,
+ 0.9856, 0.7325, 0.001079, 0.0914, 0.007009, 0.0 };
+
+ double[] rgbAnchor = {
+ rgbToLms[0][0] + rgbToLms[0][1] + rgbToLms[0][2],
+ rgbToLms[1][0] + rgbToLms[1][1] + rgbToLms[1][2],
+ rgbToLms[2][0] + rgbToLms[2][1] + rgbToLms[2][2] };
+
+ double a1, a2, b1, b2, c1, c2, inflection;
+
+ switch (kind) {
+ case PROTANOPIA:
+ a1 = rgbAnchor[1] * anchor[8] - rgbAnchor[2] * anchor[7];
+ b1 = rgbAnchor[2] * anchor[6] - rgbAnchor[0] * anchor[8];
+ c1 = rgbAnchor[0] * anchor[7] - rgbAnchor[1] * anchor[6];
+ a2 = rgbAnchor[1] * anchor[2] - rgbAnchor[2] * anchor[1];
+ b2 = rgbAnchor[2] * anchor[0] - rgbAnchor[0] * anchor[2];
+ c2 = rgbAnchor[0] * anchor[1] - rgbAnchor[1] * anchor[0];
+ inflection = rgbAnchor[2] / rgbAnchor[1];
+ tmp = lms[2] / lms[1];
+ if (tmp < inflection)
+ lms[0] = -(b1 * lms[1] + c1 * lms[2]) / a1;
+ else
+ lms[0] = -(b2 * lms[1] + c2 * lms[2]) / a2;
+ break;
+
+ case DEUTERANOPIA:
+ a1 = rgbAnchor[1] * anchor[8] - rgbAnchor[2] * anchor[7];
+ b1 = rgbAnchor[2] * anchor[6] - rgbAnchor[0] * anchor[8];
+ c1 = rgbAnchor[0] * anchor[7] - rgbAnchor[1] * anchor[6];
+ a2 = rgbAnchor[1] * anchor[2] - rgbAnchor[2] * anchor[1];
+ b2 = rgbAnchor[2] * anchor[0] - rgbAnchor[0] * anchor[2];
+ c2 = rgbAnchor[0] * anchor[1] - rgbAnchor[1] * anchor[0];
+ inflection = rgbAnchor[2] / rgbAnchor[0];
+ tmp = lms[2] / lms[0];
+ /* See which side of the inflection line we fall... */
+ if (tmp < inflection)
+ lms[1] = -(a1 * lms[0] + c1 * lms[2]) / b1;
+ else
+ lms[1] = -(a2 * lms[0] + c2 * lms[2]) / b2;
+ break;
+
+ case TRITANOPIA:
+ a1 = rgbAnchor[1] * anchor[11] - rgbAnchor[2] * anchor[10];
+ b1 = rgbAnchor[2] * anchor[9] - rgbAnchor[0] * anchor[11];
+ c1 = rgbAnchor[0] * anchor[10] - rgbAnchor[1] * anchor[9];
+ a2 = rgbAnchor[1] * anchor[5] - rgbAnchor[2] * anchor[4];
+ b2 = rgbAnchor[2] * anchor[3] - rgbAnchor[0] * anchor[5];
+ c2 = rgbAnchor[0] * anchor[4] - rgbAnchor[1] * anchor[3];
+ inflection = (rgbAnchor[1] / rgbAnchor[0]);
+ tmp = lms[1] / lms[0];
+ if (tmp < inflection)
+ lms[2] = -(a1 * lms[0] + b1 * lms[1]) / c1;
+ else
+ lms[2] = -(a2 * lms[0] + b2 * lms[1]) / c2;
+ break;
+
+ default:
+ break;
+ }
+ double[] rgbCb = mult3(lmsToRgb, lms);
+
+ double nr = Math.min(255.0, Math.max(0.0, rgbCb[0]));
+ double ng = Math.min(255.0, Math.max(0.0, rgbCb[1]));
+ double nb = Math.min(255.0, Math.max(0.0, rgbCb[2]));
+ return new Color((int) nr, (int) ng, (int) nb);
+ }
+
+ /**
+ * Multiplies the specified 3x3 matrix by the specified 3x1 vector.
+ *
+ * @param matrix
+ * Matrix.
+ * @param vector
+ * Vector.
+ * @return Vector multiplication.
+ */
+ private static double[] mult3(double[][] matrix, double[] vector) {
+ double[] res = new double[3];
+ res[0] = matrix[0][0] * vector[0] + matrix[0][1] * vector[1]
+ + matrix[0][2] * vector[2];
+ res[1] = matrix[1][0] * vector[0] + matrix[1][1] * vector[1]
+ + matrix[1][2] * vector[2];
+ res[2] = matrix[2][0] * vector[0] + matrix[2][1] * vector[1]
+ + matrix[2][2] * vector[2];
+ return res;
+ }
+}
diff --git a/substance/src/tools/java/tools/uidebug/DeuteranopiaColorScheme.java b/substance/src/tools/java/tools/uidebug/DeuteranopiaColorScheme.java
new file mode 100644
index 0000000..e7fbf7c
--- /dev/null
+++ b/substance/src/tools/java/tools/uidebug/DeuteranopiaColorScheme.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.uidebug;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+
+
+/**
+ * Color scheme for deuteranopia color blindness.
+ *
+ * @author Kirill Grouchnikov
+ * @see ColorBlindColorScheme
+ */
+public class DeuteranopiaColorScheme extends ColorBlindColorScheme {
+ /**
+ * Creates a new deuteranopia color blindness color scheme.
+ *
+ * @param origColorScheme
+ * The original color scheme.
+ */
+ public DeuteranopiaColorScheme(SubstanceColorScheme origColorScheme) {
+ super(origColorScheme, BlindnessKind.DEUTERANOPIA);
+ }
+}
diff --git a/substance/src/tools/java/tools/uidebug/ProtanopiaColorScheme.java b/substance/src/tools/java/tools/uidebug/ProtanopiaColorScheme.java
new file mode 100644
index 0000000..e888d0b
--- /dev/null
+++ b/substance/src/tools/java/tools/uidebug/ProtanopiaColorScheme.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.uidebug;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+
+
+/**
+ * Color scheme for protanopia color blindness.
+ *
+ * @author Kirill Grouchnikov
+ * @see ColorBlindColorScheme
+ */
+public class ProtanopiaColorScheme extends ColorBlindColorScheme {
+ /**
+ * Creates a new protanopia color blindness color scheme.
+ *
+ * @param origColorScheme
+ * The original color scheme.
+ */
+ public ProtanopiaColorScheme(SubstanceColorScheme origColorScheme) {
+ super(origColorScheme, BlindnessKind.PROTANOPIA);
+ }
+}
diff --git a/substance/src/tools/java/tools/uidebug/RootPaneTitlePaneUiDebugger.java b/substance/src/tools/java/tools/uidebug/RootPaneTitlePaneUiDebugger.java
new file mode 100644
index 0000000..e4617fb
--- /dev/null
+++ b/substance/src/tools/java/tools/uidebug/RootPaneTitlePaneUiDebugger.java
@@ -0,0 +1,464 @@
+package tools.uidebug;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.ComponentOrientation;
+import java.awt.Container;
+import java.awt.FlowLayout;
+import java.awt.Dialog.ModalityType;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JRootPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.SwingUtilities;
+import javax.swing.TransferHandler;
+import javax.swing.UIManager;
+
+import org.pushingpixels.lafwidget.LafWidgetAdapter;
+import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
+import org.pushingpixels.lafwidget.animation.effects.GhostPaintingUtils;
+import org.pushingpixels.substance.api.ColorSchemeTransform;
+import org.pushingpixels.substance.api.ComponentState;
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceSkin;
+import org.pushingpixels.substance.api.SubstanceConstants.FocusKind;
+import org.pushingpixels.substance.internal.utils.LazyResettableHashMap;
+import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
+
+public class RootPaneTitlePaneUiDebugger extends LafWidgetAdapter<JRootPane> {
+ protected MouseListener substanceDebugUiListener;
+
+ protected JComponent titlePane;
+
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+
+ @Override
+ public void installUI() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (!(UIManager.getLookAndFeel() instanceof SubstanceLookAndFeel))
+ return;
+
+ titlePane = SubstanceLookAndFeel
+ .getTitlePaneComponent(SwingUtilities
+ .getWindowAncestor(jcomp));
+ if (titlePane != null) {
+ substanceDebugUiListener = new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ process(e);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ process(e);
+ }
+
+ protected void process(MouseEvent e) {
+ if (e.isPopupTrigger()) {
+ JPopupMenu popup = new JPopupMenu();
+ JMenu cbMenu = new JMenu("Color blindness");
+ JMenuItem protanopiaCurrent = new JMenuItem(
+ "Protanopia current");
+ protanopiaCurrent
+ .addActionListener(new SkinChanger(
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return new ProtanopiaColorScheme(
+ scheme);
+ }
+ }, "Protanopia current"));
+ cbMenu.add(protanopiaCurrent);
+ JMenuItem deuteranopiaCurrent = new JMenuItem(
+ "Deuteranopia current");
+ deuteranopiaCurrent
+ .addActionListener(new SkinChanger(
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return new DeuteranopiaColorScheme(
+ scheme);
+ }
+ }, "Deuteranopia current"));
+ cbMenu.add(deuteranopiaCurrent);
+ JMenuItem tritanopiaCurrent = new JMenuItem(
+ "Tritanopia current");
+ tritanopiaCurrent
+ .addActionListener(new SkinChanger(
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ return new TritanopiaColorScheme(
+ scheme);
+ }
+ }, "Tritanopia current"));
+ cbMenu.add(tritanopiaCurrent);
+
+ cbMenu.addSeparator();
+
+ JMenuItem restoreOriginal = new JMenuItem(
+ "Restore original");
+ if (SubstanceLookAndFeel.getCurrentSkin(null)
+ .getColorScheme(null,
+ ComponentState.ENABLED) instanceof ColorBlindColorScheme) {
+ restoreOriginal
+ .addActionListener(new SkinChanger(
+ new ColorSchemeTransform() {
+ @Override
+ public SubstanceColorScheme transform(
+ SubstanceColorScheme scheme) {
+ if (scheme instanceof ColorBlindColorScheme)
+ return ((ColorBlindColorScheme) scheme)
+ .getOrigScheme();
+ return scheme;
+ }
+ }, "Current"));
+ } else {
+ restoreOriginal.setEnabled(false);
+ }
+ cbMenu.add(restoreOriginal);
+
+ popup.add(cbMenu);
+
+ JMenu animMenu = new JMenu("Animation rate");
+ JMenuItem debugNone = new JMenuItem("None");
+ debugNone
+ .addActionListener(new AnimationChanger(
+ 0));
+ animMenu.add(debugNone);
+ JMenuItem debugAnim = new JMenuItem(
+ "Debug rate (extra slow)");
+ debugAnim
+ .addActionListener(new AnimationChanger(
+ 5000));
+ animMenu.add(debugAnim);
+ JMenuItem debugAnimFast = new JMenuItem(
+ "Debug rate (faster)");
+ debugAnimFast
+ .addActionListener(new AnimationChanger(
+ 2500));
+ animMenu.add(debugAnimFast);
+ JMenuItem debugSlow = new JMenuItem("Slow rate");
+ debugSlow
+ .addActionListener(new AnimationChanger(
+ 1000));
+ animMenu.add(debugSlow);
+ JMenuItem debugRegular = new JMenuItem(
+ "Regular rate");
+ debugRegular
+ .addActionListener(new AnimationChanger(
+ 250));
+ animMenu.add(debugRegular);
+ JMenuItem debugFast = new JMenuItem("Fast rate");
+ debugFast
+ .addActionListener(new AnimationChanger(
+ 100));
+ animMenu.add(debugFast);
+
+ popup.add(animMenu);
+
+ JMenu focusMenu = new JMenu("Focus kind");
+ for (FocusKind fKind : FocusKind.values()) {
+ JMenuItem focusMenuItem = new JMenuItem(
+ fKind.name().toLowerCase());
+ focusMenuItem
+ .addActionListener(new FocusKindChanger(
+ fKind));
+ focusMenu.add(focusMenuItem);
+ }
+ popup.add(focusMenu);
+
+ JMenuItem dumpHierarchy = new JMenuItem(
+ "Dump hierarchy");
+ dumpHierarchy
+ .addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(
+ ActionEvent e) {
+ dump(jcomp, 0);
+ }
+ });
+ popup.add(dumpHierarchy);
+
+ final JCheckBoxMenuItem ltrChange = new JCheckBoxMenuItem(
+ "Is left-to-right");
+ ltrChange.setSelected(jcomp
+ .getComponentOrientation()
+ .isLeftToRight());
+ ltrChange
+ .addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(
+ ActionEvent e) {
+ SwingUtilities
+ .invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ jcomp
+ .applyComponentOrientation(ltrChange
+ .isSelected() ? ComponentOrientation.LEFT_TO_RIGHT
+ : ComponentOrientation.RIGHT_TO_LEFT);
+ }
+ });
+ }
+ });
+ popup.add(ltrChange);
+
+ final JCheckBoxMenuItem useThemedIcons = new JCheckBoxMenuItem(
+ "Use themed icons");
+ useThemedIcons
+ .setSelected(SubstanceCoreUtilities
+ .useThemedDefaultIcon(null));
+ useThemedIcons
+ .addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(
+ ActionEvent e) {
+ SwingUtilities
+ .invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ UIManager
+ .put(
+ SubstanceLookAndFeel.USE_THEMED_DEFAULT_ICONS,
+ useThemedIcons
+ .isSelected() ? Boolean.TRUE
+ : null);
+ jcomp.repaint();
+ }
+ });
+ }
+ });
+ popup.add(useThemedIcons);
+
+ final JCheckBoxMenuItem ghostDebugMode = new JCheckBoxMenuItem(
+ "Ghost debug mode");
+ ghostDebugMode
+ .addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(
+ ActionEvent e) {
+ SwingUtilities
+ .invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ ghostDebugMode
+ .setEnabled(false);
+ GhostPaintingUtils.MAX_ICON_GHOSTING_ALPHA = 0.8f;
+ GhostPaintingUtils.MIN_ICON_GHOSTING_ALPHA = 0.6f;
+ GhostPaintingUtils.MAX_PRESS_GHOSTING_ALPHA = 0.8f;
+ GhostPaintingUtils.MIN_PRESS_GHOSTING_ALPHA = 0.6f;
+ GhostPaintingUtils.DECAY_FACTOR = 0.7f;
+ }
+ });
+ }
+ });
+ popup.add(ghostDebugMode);
+
+ JMenuItem showCacheStats = new JMenuItem(
+ "Show cache stats");
+ showCacheStats
+ .addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(
+ ActionEvent e) {
+ SwingUtilities
+ .invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ final JTextArea textArea = new JTextArea();
+ java.util.List<String> stats = LazyResettableHashMap
+ .getStats();
+ if (stats != null) {
+ for (String stat : stats) {
+ textArea
+ .append(stat
+ + "\n");
+ }
+ }
+ final JDialog dialog = new JDialog(
+ SwingUtilities
+ .getWindowAncestor(jcomp),
+ ModalityType.APPLICATION_MODAL);
+ dialog
+ .setTitle("Substance cache stats");
+ dialog
+ .setLayout(new BorderLayout());
+ dialog
+ .add(
+ new JScrollPane(
+ textArea),
+ BorderLayout.CENTER);
+ JButton dismiss = new JButton(
+ "Dismiss");
+ dismiss
+ .addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(
+ ActionEvent e) {
+ dialog
+ .dispose();
+ }
+ });
+ JButton copyToClipboard = new JButton(
+ "Copy to clipboard");
+ copyToClipboard
+ .addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(
+ ActionEvent e) {
+ textArea
+ .selectAll();
+ TransferHandler
+ .getCopyAction()
+ .actionPerformed(
+ new ActionEvent(
+ textArea,
+ ActionEvent.ACTION_PERFORMED,
+ "Copy"));
+ }
+ });
+ JPanel controls = new JPanel(
+ new FlowLayout(
+ FlowLayout.RIGHT));
+ controls
+ .add(copyToClipboard);
+ controls
+ .add(dismiss);
+ dialog
+ .add(
+ controls,
+ BorderLayout.SOUTH);
+
+ dialog.setSize(
+ 500,
+ 400);
+ dialog
+ .setLocationRelativeTo(SwingUtilities
+ .getRootPane(jcomp));
+ dialog
+ .setVisible(true);
+ }
+ });
+ }
+ });
+ popup.add(showCacheStats);
+
+ popup.show(titlePane, e.getX(), e.getY());
+ }
+ }
+ };
+ titlePane.addMouseListener(substanceDebugUiListener);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void uninstallUI() {
+ if (this.substanceDebugUiListener != null) {
+ titlePane.removeMouseListener(this.substanceDebugUiListener);
+ this.substanceDebugUiListener = null;
+ }
+ }
+
+ protected static class SkinChanger implements ActionListener {
+ protected ColorSchemeTransform transform;
+
+ protected String name;
+
+ public SkinChanger(ColorSchemeTransform transform, String name) {
+ super();
+ this.transform = transform;
+ this.name = name;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ SubstanceSkin newSkin = SubstanceLookAndFeel
+ .getCurrentSkin(null).transform(transform, name);
+ SubstanceLookAndFeel.setSkin(newSkin);
+ }
+ });
+ }
+ }
+
+ protected static class AnimationChanger implements ActionListener {
+ protected long newAnimationDuration;
+
+ public AnimationChanger(long newAnimationDuration) {
+ super();
+ this.newAnimationDuration = newAnimationDuration;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ AnimationConfigurationManager.getInstance()
+ .setTimelineDuration(newAnimationDuration);
+ }
+ });
+ }
+ }
+
+ protected static class FocusKindChanger implements ActionListener {
+ protected FocusKind newFocusKind;
+
+ public FocusKindChanger(FocusKind newFocusKind) {
+ super();
+ this.newFocusKind = newFocusKind;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ UIManager
+ .put(SubstanceLookAndFeel.FOCUS_KIND, newFocusKind);
+ }
+ });
+ }
+ }
+
+ public static void dump(Component comp, int level) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < level; i++)
+ sb.append(" ");
+ sb.append(comp.toString());
+ System.out.println(sb);
+ if (comp instanceof Container) {
+ Container cont = (Container) comp;
+ for (int i = 0; i < cont.getComponentCount(); i++) {
+ dump(cont.getComponent(i), level + 1);
+ }
+ }
+ }
+}
diff --git a/substance/src/tools/java/tools/uidebug/ScrollBarUiDebugger.java b/substance/src/tools/java/tools/uidebug/ScrollBarUiDebugger.java
new file mode 100644
index 0000000..150ef91
--- /dev/null
+++ b/substance/src/tools/java/tools/uidebug/ScrollBarUiDebugger.java
@@ -0,0 +1,114 @@
+package tools.uidebug;
+
+import java.awt.event.*;
+
+import javax.swing.*;
+
+import org.pushingpixels.lafwidget.LafWidgetAdapter;
+import org.pushingpixels.substance.api.SubstanceLookAndFeel;
+import org.pushingpixels.substance.api.SubstanceConstants.ScrollPaneButtonPolicyKind;
+
+public class ScrollBarUiDebugger extends LafWidgetAdapter<JScrollBar> {
+ protected MouseListener substanceDebugUiListener;
+
+ @Override
+ public boolean requiresCustomLafSupport() {
+ return false;
+ }
+
+ @Override
+ public void installListeners() {
+
+ this.substanceDebugUiListener = new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ this.process(e);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ this.process(e);
+ }
+
+ protected void process(MouseEvent e) {
+ if (e.isPopupTrigger()) {
+ JPopupMenu popup = new JPopupMenu();
+ JMenuItem policyNone = new JMenuItem("Empty policy");
+ policyNone.addActionListener(new PolicyChanger(
+ ScrollPaneButtonPolicyKind.NONE));
+ popup.add(policyNone);
+ JMenuItem policyOpposite = new JMenuItem("Opposite policy");
+ policyOpposite.addActionListener(new PolicyChanger(
+ ScrollPaneButtonPolicyKind.OPPOSITE));
+ popup.add(policyOpposite);
+ JMenuItem policyAdjacent = new JMenuItem("Adjacent policy");
+ policyAdjacent.addActionListener(new PolicyChanger(
+ ScrollPaneButtonPolicyKind.ADJACENT));
+ popup.add(policyAdjacent);
+ JMenuItem policyMultiple = new JMenuItem("Multiple policy");
+ policyMultiple.addActionListener(new PolicyChanger(
+ ScrollPaneButtonPolicyKind.MULTIPLE));
+ popup.add(policyMultiple);
+ JMenuItem policyMultipleBoth = new JMenuItem(
+ "Multiple both policy");
+ policyMultipleBoth.addActionListener(new PolicyChanger(
+ ScrollPaneButtonPolicyKind.MULTIPLE_BOTH));
+ popup.add(policyMultipleBoth);
+ popup.show(jcomp, e.getX(), e.getY());
+ }
+ }
+ };
+ this.jcomp.addMouseListener(this.substanceDebugUiListener);
+ }
+
+ @Override
+ public void uninstallListeners() {
+ if (this.substanceDebugUiListener != null) {
+ this.jcomp.removeMouseListener(this.substanceDebugUiListener);
+ this.substanceDebugUiListener = null;
+ }
+
+ }
+
+ /**
+ * Listener on policy change menu items in debug UI mode.
+ *
+ * @author Kirill Grouchnikov
+ */
+ protected class PolicyChanger implements ActionListener {
+ /**
+ * Policy to set.
+ */
+ protected ScrollPaneButtonPolicyKind newPolicy;
+
+ /**
+ * Creates a new policy change listener.
+ *
+ * @param newPolicy
+ * Policy to set.
+ */
+ public PolicyChanger(ScrollPaneButtonPolicyKind newPolicy) {
+ super();
+ this.newPolicy = newPolicy;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ ((JScrollPane) jcomp.getParent()).putClientProperty(
+ SubstanceLookAndFeel.SCROLL_PANE_BUTTONS_POLICY,
+ PolicyChanger.this.newPolicy);
+ jcomp.getParent().doLayout();
+ jcomp.getParent().repaint();
+ }
+ });
+ }
+ }
+}
diff --git a/substance/src/tools/java/tools/uidebug/TritanopiaColorScheme.java b/substance/src/tools/java/tools/uidebug/TritanopiaColorScheme.java
new file mode 100644
index 0000000..e9402cc
--- /dev/null
+++ b/substance/src/tools/java/tools/uidebug/TritanopiaColorScheme.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Substance Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tools.uidebug;
+
+import org.pushingpixels.substance.api.SubstanceColorScheme;
+
+
+/**
+ * Color scheme for tritanopia color blindness.
+ *
+ * @author Kirill Grouchnikov
+ * @see ColorBlindColorScheme
+ */
+public class TritanopiaColorScheme extends ColorBlindColorScheme {
+ /**
+ * Creates a new tritanopia color blindness color scheme.
+ *
+ * @param origColorScheme
+ * The original color scheme.
+ */
+ public TritanopiaColorScheme(SubstanceColorScheme origColorScheme) {
+ super(origColorScheme, BlindnessKind.TRITANOPIA);
+ }
+}
diff --git a/substance/src/tools/resources/META-INF/lafwidget.properties b/substance/src/tools/resources/META-INF/lafwidget.properties
new file mode 100644
index 0000000..e10b9bc
--- /dev/null
+++ b/substance/src/tools/resources/META-INF/lafwidget.properties
@@ -0,0 +1,2 @@
+tools.uidebug.RootPaneTitlePaneUiDebugger = javax.swing.JRootPane
+tools.uidebug.ScrollBarUiDebugger = javax.swing.JScrollBar
diff --git a/substance/src/tools/resources/tools/jitterbug/add.png b/substance/src/tools/resources/tools/jitterbug/add.png
new file mode 100644
index 0000000..6332fef
Binary files /dev/null and b/substance/src/tools/resources/tools/jitterbug/add.png differ
diff --git a/substance/src/tools/resources/tools/jitterbug/arrow_down.png b/substance/src/tools/resources/tools/jitterbug/arrow_down.png
new file mode 100644
index 0000000..2c4e279
Binary files /dev/null and b/substance/src/tools/resources/tools/jitterbug/arrow_down.png differ
diff --git a/substance/src/tools/resources/tools/jitterbug/arrow_up.png b/substance/src/tools/resources/tools/jitterbug/arrow_up.png
new file mode 100644
index 0000000..1ebb193
Binary files /dev/null and b/substance/src/tools/resources/tools/jitterbug/arrow_up.png differ
diff --git a/substance/src/tools/resources/tools/jitterbug/chart_line_edit.png b/substance/src/tools/resources/tools/jitterbug/chart_line_edit.png
new file mode 100644
index 0000000..9cf6607
Binary files /dev/null and b/substance/src/tools/resources/tools/jitterbug/chart_line_edit.png differ
diff --git a/substance/src/tools/resources/tools/jitterbug/delete.png b/substance/src/tools/resources/tools/jitterbug/delete.png
new file mode 100644
index 0000000..08f2493
Binary files /dev/null and b/substance/src/tools/resources/tools/jitterbug/delete.png differ
diff --git a/substance/src/tools/resources/tools/jitterbug/exclamation.png b/substance/src/tools/resources/tools/jitterbug/exclamation.png
new file mode 100644
index 0000000..c37bd06
Binary files /dev/null and b/substance/src/tools/resources/tools/jitterbug/exclamation.png differ
diff --git a/substance/src/tools/resources/tools/jitterbug/page_save.png b/substance/src/tools/resources/tools/jitterbug/page_save.png
new file mode 100644
index 0000000..caea546
Binary files /dev/null and b/substance/src/tools/resources/tools/jitterbug/page_save.png differ
diff --git a/trident/build.gradle b/trident/build.gradle
new file mode 100755
index 0000000..6b3da6d
--- /dev/null
+++ b/trident/build.gradle
@@ -0,0 +1,161 @@
+dependencies {
+ compile group: 'com.google.android', name: 'android', version: '2.3.1', transitive: false
+ compile group: 'org.eclipse.swt.win32.win32', name: 'x86', version: '3.3.0-v3346', transitive: false
+ compile group: 'net.jcip', name:'jcip-annotations', version: '1.0'
+}
+
+jar {
+ exclude 'META-INF/trident-plugin-*.properties'
+
+ manifest {
+ attributes(
+ "Trident-Version": version,
+ "Trident-VersionName": versionKey,
+ )
+ }
+}
+
+task testJar(type: Jar) {
+ classifier = 'tst'
+
+ from sourceSets.test.output
+
+ manifest {
+ attributes(
+ "Trident-Version": version,
+ "Trident-VersionName": versionKey,
+ )
+ }
+}
+
+
+def stripper = {part -> {it -> it - part}}
+
+task baseJar(type: Jar) {
+ classifier = 'base'
+
+ from sourceSets.main.output
+ exclude 'org/pushingpixels/trident/android/**'
+ exclude 'org/pushingpixels/trident/swing/**'
+ exclude 'org/pushingpixels/trident/swt/**'
+ exclude 'META-INF/trident-plugin.properties'
+ exclude 'META-INF/trident-plugin-android.properties'
+ //exclude 'META-INF/trident-plugin-base.properties'
+ exclude 'META-INF/trident-plugin-swing.properties'
+ exclude 'META-INF/trident-plugin-swt.properties'
+ rename stripper('-base')
+
+ manifest {
+ attributes(
+ "Trident-Version": version,
+ "Trident-VersionName": versionKey,
+ )
+ }
+}
+
+task androidJar(type: Jar) {
+ classifier = 'android'
+
+ from sourceSets.main.output
+ exclude 'org/pushingpixels/trident/swing/**'
+ exclude 'org/pushingpixels/trident/swt/**'
+ exclude 'META-INF/trident-plugin.properties'
+ //exclude 'META-INF/trident-plugin-android.properties'
+ exclude 'META-INF/trident-plugin-base.properties'
+ exclude 'META-INF/trident-plugin-swing.properties'
+ exclude 'META-INF/trident-plugin-swt.properties'
+ rename stripper('-android')
+
+ manifest {
+ attributes(
+ "Trident-Version": version,
+ "Trident-VersionName": versionKey,
+ )
+ }
+}
+
+task swingJar(type: Jar) {
+ classifier = 'swing'
+
+ from sourceSets.main.output
+ exclude 'org/pushingpixels/trident/android/**'
+ exclude 'org/pushingpixels/trident/swt/**'
+ exclude 'META-INF/trident-plugin.properties'
+ exclude 'META-INF/trident-plugin-android.properties'
+ exclude 'META-INF/trident-plugin-base.properties'
+ //exclude 'META-INF/trident-plugin-swing.properties'
+ exclude 'META-INF/trident-plugin-swt.properties'
+ rename stripper('-swing')
+
+ manifest {
+ attributes(
+ "Trident-Version": version,
+ "Trident-VersionName": versionKey,
+ )
+ }
+}
+
+task swtJar(type: Jar) {
+ classifier = 'swt'
+
+ from sourceSets.main.output
+ exclude 'org/pushingpixels/trident/android/**'
+ exclude 'org/pushingpixels/trident/swing/**'
+ exclude 'META-INF/trident-plugin.properties'
+ exclude 'META-INF/trident-plugin-android.properties'
+ exclude 'META-INF/trident-plugin-base.properties'
+ exclude 'META-INF/trident-plugin-swing.properties'
+ //exclude 'META-INF/trident-plugin-swt.properties'
+ rename stripper('-swt')
+
+ manifest {
+ attributes(
+ "Trident-Version": version,
+ "Trident-VersionName": versionKey,
+ )
+ }
+}
+
+artifacts {
+ archives baseJar
+ archives androidJar
+ archives swingJar
+ archives swtJar
+ distro baseJar
+ distro androidJar
+ distro swingJar
+ distro swtJar
+}
+
+
+uploadPublished {
+ repositories {
+ mavenDeployer {
+ configurePOM(pom)
+ }
+ }
+}
+
+install {
+ configurePOM(repositories.mavenInstaller.pom)
+}
+
+private def configurePOM(def pom) {
+ configureBasePom(pom)
+ pom.project {
+ name "trident"
+ description "A fork of @kirilcool's trident project"
+ url "https://github.com/Insubstantial/insubstantial/tree/master/trident"
+ licenses {
+ license {
+ name 'The BSD License'
+ url 'http://www.opensource.org/licenses/bsd-license.php'
+ distribution 'repo'
+ }
+ }
+ }
+ // deal with a gradle bug where transitive=false is not passed into the generated POM
+ pom.whenConfigured {cpom ->
+ cpom.dependencies*.scope = 'provided'
+ }
+}
diff --git a/trident/settings.gradle b/trident/settings.gradle
new file mode 100755
index 0000000..7bebd46
--- /dev/null
+++ b/trident/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'trident'
\ No newline at end of file
diff --git a/trident/src/main/java/org/pushingpixels/trident/Timeline.java b/trident/src/main/java/org/pushingpixels/trident/Timeline.java
new file mode 100644
index 0000000..6a1066c
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/Timeline.java
@@ -0,0 +1,626 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident;
+
+import java.util.*;
+
+import org.pushingpixels.trident.TimelineEngine.FullObjectID;
+import org.pushingpixels.trident.TimelineEngine.TimelineOperationKind;
+import org.pushingpixels.trident.TimelinePropertyBuilder.AbstractFieldInfo;
+import org.pushingpixels.trident.callback.*;
+import org.pushingpixels.trident.ease.Linear;
+import org.pushingpixels.trident.ease.TimelineEase;
+import org.pushingpixels.trident.interpolator.KeyFrames;
+
+public class Timeline implements TimelineScenario.TimelineScenarioActor {
+ Object mainObject;
+
+ Comparable<?> secondaryId;
+
+ FullObjectID fullObjectID;
+
+ long duration;
+
+ long initialDelay;
+
+ long cycleDelay;
+
+ boolean isLooping;
+
+ int repeatCount;
+
+ RepeatBehavior repeatBehavior;
+
+ UIToolkitHandler uiToolkitHandler;
+
+ Chain callback;
+
+ String name;
+
+ List<AbstractFieldInfo> propertiesToInterpolate;
+
+ /**
+ * Is used to create unique value for the {@link #id} field.
+ */
+ static long counter;
+
+ /**
+ * Unique ID.
+ */
+ protected long id;
+
+ /**
+ * Timeline position.
+ */
+ float durationFraction;
+
+ /**
+ * Timeline position.
+ */
+ float timelinePosition;
+
+ long timeUntilPlay;
+
+ /**
+ * Indication whether the looping timeline should stop at reaching the end
+ * of the cycle. Relevant only when {@link #isLooping} is <code>true</code>.
+ */
+ boolean toCancelAtCycleBreak;
+
+ Stack<TimelineState> stateStack;
+
+ TimelineEase ease;
+
+ private int doneCount;
+
+ public enum RepeatBehavior {
+ LOOP, REVERSE
+ }
+
+ public enum TimelineState {
+ IDLE(false), READY(false), PLAYING_FORWARD(true), PLAYING_REVERSE(true), SUSPENDED(
+ false), CANCELLED(false), DONE(false);
+
+ private boolean isActive;
+
+ private TimelineState(boolean isActive) {
+ this.isActive = isActive;
+ }
+ }
+
+ private class Setter extends TimelineCallbackAdapter {
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ if (newState == TimelineState.READY) {
+ for (AbstractFieldInfo fInfo : propertiesToInterpolate) {
+ // check whether the object is in the ready state
+ if ((uiToolkitHandler != null)
+ && !uiToolkitHandler.isInReadyState(fInfo.object))
+ continue;
+ fInfo.onStart();
+ }
+ }
+
+ // Fix for issue 5 - update field values only when
+ // either old or new state (or both) are active. Otherwise
+ // it's a transition between inactive states (such as from
+ // DONE to IDLE) that shouldn't trigger the property changes
+ if (oldState.isActive || newState.isActive) {
+ for (AbstractFieldInfo fInfo : propertiesToInterpolate) {
+ // check whether the object is in the ready state
+ if ((uiToolkitHandler != null)
+ && !uiToolkitHandler.isInReadyState(fInfo.object))
+ continue;
+ fInfo.updateFieldValue(timelinePosition);
+ }
+ }
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ for (AbstractFieldInfo fInfo : propertiesToInterpolate) {
+ // check whether the object is in the ready state
+ if ((uiToolkitHandler != null)
+ && !uiToolkitHandler.isInReadyState(fInfo.object))
+ continue;
+ // System.err.println("Timeline @" + Timeline.this.hashCode()
+ // + " at position " + timelinePosition);
+ fInfo.updateFieldValue(timelinePosition);
+ }
+ }
+ }
+
+ @RunOnUIThread
+ private class UISetter extends Setter {
+ }
+
+ class Chain implements TimelineCallback {
+ private List<TimelineCallback> callbacks;
+
+ public Chain(TimelineCallback... callbacks) {
+ this.callbacks = new ArrayList<TimelineCallback>();
+ for (TimelineCallback callback : callbacks)
+ this.callbacks.add(callback);
+ }
+
+ public void addCallback(TimelineCallback callback) {
+ this.callbacks.add(callback);
+ }
+
+ public void removeCallback(TimelineCallback callback) {
+ this.callbacks.remove(callback);
+ }
+
+ @Override
+ public void onTimelineStateChanged(final TimelineState oldState,
+ final TimelineState newState, final float durationFraction,
+ final float timelinePosition) {
+ if ((uiToolkitHandler != null)
+ && !uiToolkitHandler.isInReadyState(mainObject))
+ return;
+ for (int i = this.callbacks.size() - 1; i >= 0; i--) {
+ final TimelineCallback callback = this.callbacks.get(i);
+ // special handling for chained callbacks not running on UI
+ // thread
+ boolean shouldRunOnUIThread = false;
+ Class<?> clazz = callback.getClass();
+ while ((clazz != null) && !shouldRunOnUIThread) {
+ shouldRunOnUIThread = clazz
+ .isAnnotationPresent(RunOnUIThread.class);
+ clazz = clazz.getSuperclass();
+ }
+ if (shouldRunOnUIThread
+ && (Timeline.this.uiToolkitHandler != null)) {
+ Timeline.this.uiToolkitHandler.runOnUIThread(mainObject,
+ new Runnable() {
+ @Override
+ public void run() {
+ callback.onTimelineStateChanged(oldState,
+ newState, durationFraction,
+ timelinePosition);
+ }
+ });
+ } else {
+ callback.onTimelineStateChanged(oldState, newState,
+ durationFraction, timelinePosition);
+ }
+ }
+ }
+
+ @Override
+ public void onTimelinePulse(final float durationFraction,
+ final float timelinePosition) {
+ if ((uiToolkitHandler != null)
+ && !uiToolkitHandler.isInReadyState(mainObject))
+ return;
+ for (int i = this.callbacks.size() - 1; i >= 0; i--) {
+ final TimelineCallback callback = this.callbacks.get(i);
+ // special handling for chained callbacks not running on UI
+ // thread
+ boolean shouldRunOnUIThread = false;
+ Class<?> clazz = callback.getClass();
+ while ((clazz != null) && !shouldRunOnUIThread) {
+ shouldRunOnUIThread = clazz
+ .isAnnotationPresent(RunOnUIThread.class);
+ clazz = clazz.getSuperclass();
+ }
+ if (shouldRunOnUIThread
+ && (Timeline.this.uiToolkitHandler != null)) {
+ Timeline.this.uiToolkitHandler.runOnUIThread(mainObject,
+ new Runnable() {
+ @Override
+ public void run() {
+ if (Timeline.this.getState() == TimelineState.CANCELLED)
+ return;
+ // System.err.println("Timeline @"
+ // + Timeline.this.hashCode());
+ callback.onTimelinePulse(durationFraction,
+ timelinePosition);
+ }
+ });
+ } else {
+ // System.err.println("Timeline @" +
+ // Timeline.this.hashCode());
+ callback
+ .onTimelinePulse(durationFraction, timelinePosition);
+ }
+ }
+ }
+ }
+
+ public Timeline() {
+ this(null);
+ }
+
+ public Timeline(Object mainTimelineObject) {
+ this.mainObject = mainTimelineObject;
+
+ for (UIToolkitHandler uiToolkitHandler : TridentConfig.getInstance()
+ .getUIToolkitHandlers()) {
+ if (uiToolkitHandler.isHandlerFor(mainTimelineObject)) {
+ this.uiToolkitHandler = uiToolkitHandler;
+ break;
+ }
+ }
+
+ // if the main timeline object is handled by a UI toolkit handler,
+ // the setters registered with the different addProperty
+ // APIs need to run with the matching threading policy
+ TimelineCallback setterCallback = (this.uiToolkitHandler != null) ? new UISetter()
+ : new Setter();
+ this.callback = new Chain(setterCallback);
+
+ this.duration = 500;
+ this.propertiesToInterpolate = new ArrayList<AbstractFieldInfo>();
+ this.id = Timeline.getId();
+ // this.loopsToLive = -1;
+
+ this.stateStack = new Stack<TimelineState>();
+ this.stateStack.push(TimelineState.IDLE);
+ this.doneCount = 0;
+
+ this.ease = new Linear();
+ }
+
+ public final void setSecondaryID(Comparable<?> secondaryId) {
+ if (this.getState() != TimelineState.IDLE) {
+ throw new IllegalArgumentException(
+ "Cannot change state of non-idle timeline ["
+ + this.toString() + "]");
+ }
+ this.secondaryId = secondaryId;
+ }
+
+ public final void setDuration(long durationMs) {
+ if (this.getState() != TimelineState.IDLE) {
+ throw new IllegalArgumentException(
+ "Cannot change state of non-idle timeline ["
+ + this.toString() + "]");
+ }
+ this.duration = durationMs;
+ }
+
+ public final void setInitialDelay(long initialDelay) {
+ if (this.getState() != TimelineState.IDLE) {
+ throw new IllegalArgumentException(
+ "Cannot change state of non-idle timeline ["
+ + this.toString() + "]");
+ }
+ this.initialDelay = initialDelay;
+ }
+
+ public final void setCycleDelay(long cycleDelay) {
+ if (this.getState() != TimelineState.IDLE) {
+ throw new IllegalArgumentException(
+ "Cannot change state of non-idle timeline ["
+ + this.toString() + "]");
+ }
+ this.cycleDelay = cycleDelay;
+ }
+
+ public final void addCallback(TimelineCallback callback) {
+ if (this.getState() != TimelineState.IDLE) {
+ throw new IllegalArgumentException(
+ "Cannot change state of non-idle timeline ["
+ + this.toString() + "]");
+ }
+ this.callback.addCallback(callback);
+ }
+
+ public final void removeCallback(TimelineCallback callback) {
+ if (this.getState() != TimelineState.IDLE) {
+ throw new IllegalArgumentException(
+ "Cannot change state of non-idle timeline ["
+ + this.toString() + "]");
+ }
+ this.callback.removeCallback(callback);
+ }
+
+ public static <T> TimelinePropertyBuilder<T> property(String propertyName) {
+ return new TimelinePropertyBuilder<T>(propertyName);
+ }
+
+ public final <T> void addPropertyToInterpolate(
+ TimelinePropertyBuilder<T> propertyBuilder) {
+ this.propertiesToInterpolate.add(propertyBuilder.getFieldInfo(this));
+ }
+
+ public final <T> void addPropertyToInterpolate(String propName,
+ KeyFrames<T> keyFrames) {
+ this.addPropertyToInterpolate(Timeline.<T> property(propName)
+ .goingThrough(keyFrames));
+ }
+
+ public final <T> void addPropertyToInterpolate(String propName, T from, T to) {
+ this.addPropertyToInterpolate(Timeline.<T> property(propName)
+ .from(from).to(to));
+ }
+
+ @Override
+ public void play() {
+ this.playSkipping(0);
+ }
+
+ public void playSkipping(final long msToSkip) {
+ if ((this.initialDelay + this.duration) < msToSkip) {
+ throw new IllegalArgumentException(
+ "Required skip longer than initial delay + duration");
+ }
+ TimelineEngine.getInstance().runTimelineOperation(this,
+ TimelineOperationKind.PLAY, new Runnable() {
+ @Override
+ public void run() {
+ Timeline.this.isLooping = false;
+ TimelineEngine.getInstance().play(Timeline.this, false,
+ msToSkip);
+ }
+ });
+ }
+
+ public void playReverse() {
+ playReverseSkipping(0);
+ }
+
+ public void playReverseSkipping(final long msToSkip) {
+ if ((this.initialDelay + this.duration) < msToSkip) {
+ throw new IllegalArgumentException(
+ "Required skip longer than initial delay + duration");
+ }
+ TimelineEngine.getInstance().runTimelineOperation(this,
+ TimelineOperationKind.PLAY, new Runnable() {
+ @Override
+ public void run() {
+ Timeline.this.isLooping = false;
+ TimelineEngine.getInstance().playReverse(Timeline.this,
+ false, msToSkip);
+ }
+ });
+ }
+
+ public void replay() {
+ TimelineEngine.getInstance().runTimelineOperation(this,
+ TimelineOperationKind.PLAY, new Runnable() {
+ @Override
+ public void run() {
+ Timeline.this.isLooping = false;
+ TimelineEngine.getInstance().play(Timeline.this, true,
+ 0);
+ }
+ });
+ }
+
+ public void replayReverse() {
+ TimelineEngine.getInstance().runTimelineOperation(this,
+ TimelineOperationKind.PLAY, new Runnable() {
+ @Override
+ public void run() {
+ Timeline.this.isLooping = false;
+ TimelineEngine.getInstance().playReverse(Timeline.this,
+ true, 0);
+ }
+ });
+ }
+
+ public void playLoop(RepeatBehavior repeatBehavior) {
+ this.playLoop(-1, repeatBehavior);
+ }
+
+ public void playLoopSkipping(RepeatBehavior repeatBehavior,
+ final long msToSkip) {
+ this.playLoopSkipping(-1, repeatBehavior, msToSkip);
+ }
+
+ public void playLoop(int loopCount, RepeatBehavior repeatBehavior) {
+ this.playLoopSkipping(loopCount, repeatBehavior, 0);
+ }
+
+ public void playLoopSkipping(final int loopCount,
+ final RepeatBehavior repeatBehavior, final long msToSkip) {
+ if ((this.initialDelay + this.duration) < msToSkip) {
+ throw new IllegalArgumentException(
+ "Required skip longer than initial delay + duration");
+ }
+ TimelineEngine.getInstance().runTimelineOperation(this,
+ TimelineOperationKind.PLAY, new Runnable() {
+ @Override
+ public void run() {
+ Timeline.this.isLooping = true;
+ Timeline.this.repeatCount = loopCount;
+ Timeline.this.repeatBehavior = repeatBehavior;
+ TimelineEngine.getInstance().playLoop(Timeline.this,
+ msToSkip);
+ }
+ });
+ }
+
+ /**
+ * Cancels this timeline. The timeline transitions to the
+ * {@link TimelineState#CANCELLED} state, preserving its current timeline
+ * position. After application callbacks and field interpolations are done
+ * on the {@link TimelineState#CANCELLED} state, the timeline transitions to
+ * the {@link TimelineState#IDLE} state. Application callbacks and field
+ * interpolations are done on this state as well.
+ *
+ * @see #end()
+ * @see #abort()
+ */
+ public void cancel() {
+ TimelineEngine.getInstance().runTimelineOperation(this,
+ TimelineOperationKind.CANCEL, null);
+ }
+
+ /**
+ * Ends this timeline. The timeline transitions to the
+ * {@link TimelineState#DONE} state, with the timeline position set to 0.0
+ * or 1.0 - based on the direction of the timeline. After application
+ * callbacks and field interpolations are done on the
+ * {@link TimelineState#DONE} state, the timeline transitions to the
+ * {@link TimelineState#IDLE} state. Application callbacks and field
+ * interpolations are done on this state as well.
+ *
+ * @see #cancel()
+ * @see #abort()
+ */
+ public void end() {
+ TimelineEngine.getInstance().runTimelineOperation(this,
+ TimelineOperationKind.END, null);
+ }
+
+ /**
+ * Aborts this timeline. The timeline transitions to the
+ * {@link TimelineState#IDLE} state. No application callbacks or field
+ * interpolations are done.
+ *
+ * @see #cancel()
+ * @see #end()
+ */
+ public void abort() {
+ TimelineEngine.getInstance().runTimelineOperation(this,
+ TimelineOperationKind.ABORT, null);
+ }
+
+ public void suspend() {
+ TimelineEngine.getInstance().runTimelineOperation(this,
+ TimelineOperationKind.SUSPEND, null);
+ }
+
+ public void resume() {
+ TimelineEngine.getInstance().runTimelineOperation(this,
+ TimelineOperationKind.RESUME, null);
+ }
+
+ /**
+ * Requests that the specified timeline should stop at the end of the cycle.
+ * This method should be called only on looping timelines.
+ */
+ public void cancelAtCycleBreak() {
+ if (!this.isLooping)
+ throw new IllegalArgumentException(
+ "Can only be called on looping timelines");
+ this.toCancelAtCycleBreak = true;
+ }
+
+ /**
+ * Returns a unique ID.
+ *
+ * @return Unique ID.
+ */
+ protected static synchronized long getId() {
+ return counter++;
+ }
+
+ public final float getTimelinePosition() {
+ return this.timelinePosition;
+ }
+
+ public final float getDurationFraction() {
+ return this.durationFraction;
+ }
+
+ public final TimelineState getState() {
+ return this.stateStack.peek();
+ }
+
+ public final void setEase(TimelineEase ease) {
+ if (this.getState() != TimelineState.IDLE) {
+ throw new IllegalArgumentException(
+ "Cannot change state of non-idle timeline");
+ }
+ this.ease = ease;
+ }
+
+ @Override
+ public boolean isDone() {
+ return (this.doneCount > 0);
+ }
+
+ @Override
+ public boolean supportsReplay() {
+ return true;
+ }
+
+ @Override
+ public void resetDoneFlag() {
+ this.doneCount = 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer res = new StringBuffer();
+ if (this.name != null) {
+ res.append(this.name);
+ }
+ if (this.mainObject != null) {
+ res.append(":" + this.mainObject.getClass().getName());
+ }
+ if (this.secondaryId != null) {
+ res.append(":" + this.secondaryId.toString());
+ }
+
+ res.append(" " + this.getState().name());
+ res.append(":" + this.timelinePosition);
+
+ return res.toString();
+ }
+
+ void replaceState(TimelineState state) {
+ this.stateStack.pop();
+ this.pushState(state);
+ }
+
+ void pushState(TimelineState state) {
+ if (state == TimelineState.DONE)
+ this.doneCount++;
+ this.stateStack.add(state);
+ }
+
+ TimelineState popState() {
+ return this.stateStack.pop();
+ }
+
+ public final long getDuration() {
+ return this.duration;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Object getMainObject() {
+ return this.mainObject;
+ }
+}
\ No newline at end of file
diff --git a/trident/src/main/java/org/pushingpixels/trident/TimelineEngine.java b/trident/src/main/java/org/pushingpixels/trident/TimelineEngine.java
new file mode 100644
index 0000000..0d7e6eb
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/TimelineEngine.java
@@ -0,0 +1,1030 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident;
+
+import net.jcip.annotations.GuardedBy;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.TimelineScenario.TimelineScenarioState;
+import org.pushingpixels.trident.callback.RunOnUIThread;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The Trident timeline engine. This is the main entry point to play
+ * {@link Timeline}s and {@link TimelineScenario}s. Use the
+ * {@link #getInstance()} method to get the timeline engine.
+ *
+ * @author Kirill Grouchnikov
+ */
+class TimelineEngine {
+ /**
+ * Debug mode indicator. Set to <code>true</code> to have trace messages on
+ * console.
+ */
+ public static boolean DEBUG_MODE = false;
+
+ /**
+ * Single instance of <code>this</code> class.
+ */
+ private static TimelineEngine instance;
+
+ /**
+ * All currently running timelines.
+ */
+ private Set<Timeline> runningTimelines;
+
+ enum TimelineOperationKind {
+ PLAY, CANCEL, RESUME, SUSPEND, ABORT, END
+ }
+
+ class TimelineOperation {
+ public TimelineOperationKind operationKind;
+
+ Runnable operationRunnable;
+
+ public TimelineOperation(TimelineOperationKind operationKind,
+ Runnable operationRunnable) {
+ this.operationKind = operationKind;
+ this.operationRunnable = operationRunnable;
+ }
+ }
+
+ private Set<TimelineScenario> runningScenarios;
+
+ long lastIterationTimeStamp;
+ long scheduledPulseShutdown = Long.MAX_VALUE;
+ boolean callbackWasIdle;
+
+ /**
+ * Identifies a main object and an optional secondary ID.
+ *
+ * @author Kirill Grouchnikov
+ */
+ static class FullObjectID {
+ /**
+ * Main object for the timeline.
+ */
+ public Object mainObj;
+
+ /**
+ * ID to distinguish between different sub-components of
+ * {@link #mainObj}. For example, the tabbed pane uses this field to
+ * make tab-specific animations.
+ */
+ @SuppressWarnings("unchecked")
+ public Comparable subID;
+
+ /**
+ * Creates a new object ID.
+ *
+ * @param mainObj
+ * The main object.
+ * @param subID
+ * ID to distinguish between different sub-components of
+ * <code>mainObj</code>. Can be <code>null</code>.
+ */
+ @SuppressWarnings("unchecked")
+ public FullObjectID(Object mainObj, Comparable subID) {
+ this.mainObj = mainObj;
+ this.subID = subID;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ int result = this.mainObj.hashCode();
+ if (this.subID != null)
+ result &= (this.subID.hashCode());
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object obj) {
+ if (obj instanceof FullObjectID) {
+ FullObjectID cid = (FullObjectID) obj;
+ try {
+ boolean result = (this.mainObj == cid.mainObj);
+ if (this.subID == null) {
+ result = result && (cid.subID == null);
+ } else {
+ result = result
+ && (this.subID.compareTo(cid.subID) == 0);
+ }
+ return result;
+ } catch (Exception exc) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return this.mainObj.getClass().getSimpleName() + ":" + this.subID;
+ }
+ }
+
+ private final Object threadSemaphore = new Object();
+
+ /**
+ * The timeline thread.
+ */
+ @GuardedBy ("threadSemaphore")
+ TridentAnimationThread animatorThread;
+
+ private BlockingQueue<Runnable> callbackQueue;
+
+ @GuardedBy ("threadSemaphore")
+ private TimelineCallbackThread callbackThread;
+
+ class TridentAnimationThread extends Thread {
+ public TridentAnimationThread() {
+ super();
+ this.setName("Trident pulse source thread");
+ this.setDaemon(true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Thread#run()
+ */
+ @Override
+ public final void run() {
+ try {
+ TridentConfig.PulseSource pulseSource = TridentConfig.getInstance()
+ .getPulseSource();
+ lastIterationTimeStamp = System.currentTimeMillis();
+ while (!isTimelinesEmpty() || (lastIterationTimeStamp < scheduledPulseShutdown)) {
+ pulseSource.waitUntilNextPulse();
+ updateTimelines();
+ // engine.currLoopId++;
+ }
+ } finally {
+ synchronized (threadSemaphore) {
+ animatorThread = null;
+ checkAnimatorThread();
+ }
+ }
+ }
+
+ @Override
+ public void interrupt() {
+ System.err.println("Interrupted");
+ super.interrupt();
+ }
+ }
+
+
+ private void checkAnimatorThread() {
+ if (!isTimelinesEmpty()) {
+ getAnimatorThread();
+ }
+ }
+
+ private class TimelineCallbackThread extends Thread {
+ public TimelineCallbackThread() {
+ super();
+ this.setName("Trident callback thread");
+ this.setDaemon(true);
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ try {
+ Runnable runnable = callbackQueue.poll(30, TimeUnit.SECONDS); // half a minute, twice to shut down
+ if (runnable != null) {
+ callbackWasIdle = false;
+ runnable.run();
+ } else if (callbackWasIdle) {
+ break;
+ } else {
+ callbackWasIdle = true;
+ }
+ } catch (Error e) {
+ e.printStackTrace();
+ throw e;
+ } catch (RuntimeException re) {
+ re.printStackTrace();
+ throw re;
+ } catch (InterruptedException ie) {
+ break;
+ }
+ }
+ } finally {
+ synchronized (threadSemaphore) {
+ callbackThread = null;
+ checkCallbackThread();
+ }
+ }
+ }
+ }
+
+ private void checkCallbackThread() {
+ if (!callbackQueue.isEmpty()) {
+ getCallbackThread();
+ }
+ }
+
+
+ /**
+ * Simple constructor. Defined private for singleton.
+ *
+ * @see #getInstance()
+ */
+ private TimelineEngine() {
+ this.runningTimelines = new HashSet<Timeline>();
+ this.runningScenarios = new HashSet<TimelineScenario>();
+
+ this.callbackQueue = new LinkedBlockingQueue<Runnable>();
+ this.callbackThread = this.getCallbackThread();
+ }
+
+ /**
+ * Gets singleton instance.
+ *
+ * @return Singleton instance.
+ */
+ public synchronized static TimelineEngine getInstance() {
+ if (TimelineEngine.instance == null) {
+ TimelineEngine.instance = new TimelineEngine();
+ }
+ return TimelineEngine.instance;
+ }
+
+ /**
+ * Updates all timelines that are currently registered with
+ * <code>this</code> tracker.
+ */
+ void updateTimelines() {
+ synchronized (LOCK) {
+ try {
+ if (isTimelinesEmpty()) {
+ return;
+ }
+
+ long passedSinceLastIteration = (System.currentTimeMillis() - this.lastIterationTimeStamp);
+ if (passedSinceLastIteration < 0) {
+ // ???
+ passedSinceLastIteration = 0;
+ }
+ if (DEBUG_MODE) {
+ System.out.println("Elapsed since last iteration: "
+ + passedSinceLastIteration + "ms");
+ }
+
+ // System.err.println("Periodic update on "
+ // + this.runningTimelines.size() + " timelines; "
+ // + passedSinceLastIteration + " ms passed since last");
+ // for (Timeline t : runningTimelines) {
+ // if (t.mainObject != null
+ // && t.mainObject.getClass().getName().indexOf(
+ // "ProgressBar") >= 0) {
+ // continue;
+ // }
+ // System.err.println("\tTimeline @"
+ // + t.hashCode()
+ // + " ["
+ // + t.getName()
+ // + "] on "
+ // + (t.mainObject == null ? "null" : t.mainObject
+ // .getClass().getName()));
+ // }
+ for (Iterator<Timeline> itTimeline = this.runningTimelines
+ .iterator(); itTimeline.hasNext();) {
+ Timeline timeline = itTimeline.next();
+ if (timeline.getState() == TimelineState.SUSPENDED)
+ continue;
+
+ boolean timelineWasInReadyState = false;
+ if (timeline.getState() == TimelineState.READY) {
+ if ((timeline.timeUntilPlay - passedSinceLastIteration) > 0) {
+ // still needs to wait in the READY state
+ timeline.timeUntilPlay -= passedSinceLastIteration;
+ continue;
+ }
+
+ // can go from READY to PLAYING
+ timelineWasInReadyState = true;
+ timeline.popState();
+ this.callbackCallTimelineStateChanged(timeline,
+ TimelineState.READY);
+ }
+
+ boolean hasEnded = false;
+ if (DEBUG_MODE) {
+ System.out.println("Processing " + timeline.id + "["
+ + timeline.mainObject.getClass().getSimpleName()
+ + "] from " + timeline.durationFraction
+ + ". Callback - "
+ + (timeline.callback == null ? "no" : "yes"));
+ }
+ // Component comp = entry.getKey();
+
+ // at this point, the timeline must be playing
+ switch (timeline.getState()) {
+ case PLAYING_FORWARD:
+ if (!timelineWasInReadyState) {
+ timeline.durationFraction = timeline.durationFraction
+ + (float) passedSinceLastIteration
+ / (float) timeline.duration;
+ }
+ timeline.timelinePosition = timeline.ease
+ .map(timeline.durationFraction);
+ if (DEBUG_MODE) {
+ System.out
+ .println("Timeline position: "
+ + ((long) (timeline.durationFraction * timeline.duration))
+ + "/" + timeline.duration + " = "
+ + timeline.durationFraction);
+ }
+ if (timeline.durationFraction > 1.0f) {
+ timeline.durationFraction = 1.0f;
+ timeline.timelinePosition = 1.0f;
+ if (timeline.isLooping) {
+ boolean stopLoopingAnimation = timeline.toCancelAtCycleBreak;
+ int loopsToLive = timeline.repeatCount;
+ if (loopsToLive > 0) {
+ loopsToLive--;
+ stopLoopingAnimation = stopLoopingAnimation
+ || (loopsToLive == 0);
+ timeline.repeatCount = loopsToLive;
+ }
+ if (stopLoopingAnimation) {
+ // end looping animation
+ hasEnded = true;
+ itTimeline.remove();
+ } else {
+ if (timeline.repeatBehavior == Timeline.RepeatBehavior.REVERSE) {
+ timeline
+ .replaceState(TimelineState.PLAYING_REVERSE);
+ if (timeline.cycleDelay > 0) {
+ timeline.pushState(TimelineState.READY);
+ timeline.timeUntilPlay = timeline.cycleDelay;
+ }
+ this.callbackCallTimelineStateChanged(
+ timeline,
+ TimelineState.PLAYING_FORWARD);
+ } else {
+ timeline.durationFraction = 0.0f;
+ timeline.timelinePosition = 0.0f;
+ if (timeline.cycleDelay > 0) {
+ timeline.pushState(TimelineState.READY);
+ timeline.timeUntilPlay = timeline.cycleDelay;
+ this.callbackCallTimelineStateChanged(
+ timeline,
+ TimelineState.PLAYING_FORWARD);
+ } else {
+ // it's still playing forward, but lets
+ // the app code know
+ // that the new loop has begun
+ this.callbackCallTimelineStateChanged(
+ timeline,
+ TimelineState.PLAYING_FORWARD);
+ }
+ }
+ }
+ } else {
+ hasEnded = true;
+ itTimeline.remove();
+ }
+ }
+ break;
+ case PLAYING_REVERSE:
+ if (!timelineWasInReadyState) {
+ timeline.durationFraction = timeline.durationFraction
+ - (float) passedSinceLastIteration
+ / (float) timeline.duration;
+ }
+ timeline.timelinePosition = timeline.ease
+ .map(timeline.durationFraction);
+ // state.timelinePosition = state.timelinePosition
+ // - stepFactor
+ // * state.fadeStep.getNextStep(state.timelineKind,
+ // state.timelinePosition,
+ // state.isPlayingForward, state.isLooping);
+ if (DEBUG_MODE) {
+ System.out
+ .println("Timeline position: "
+ + ((long) (timeline.durationFraction * timeline.duration))
+ + "/" + timeline.duration + " = "
+ + timeline.durationFraction);
+ }
+ if (timeline.durationFraction < 0) {
+ timeline.durationFraction = 0.0f;
+ timeline.timelinePosition = 0.0f;
+ if (timeline.isLooping) {
+ boolean stopLoopingAnimation = timeline.toCancelAtCycleBreak;
+ int loopsToLive = timeline.repeatCount;
+ if (loopsToLive > 0) {
+ loopsToLive--;
+ stopLoopingAnimation = stopLoopingAnimation
+ || (loopsToLive == 0);
+ timeline.repeatCount = loopsToLive;
+ }
+ if (stopLoopingAnimation) {
+ // end looping animation
+ hasEnded = true;
+ itTimeline.remove();
+ } else {
+ timeline
+ .replaceState(TimelineState.PLAYING_FORWARD);
+ if (timeline.cycleDelay > 0) {
+ timeline.pushState(TimelineState.READY);
+ timeline.timeUntilPlay = timeline.cycleDelay;
+ }
+ this.callbackCallTimelineStateChanged(timeline,
+ TimelineState.PLAYING_REVERSE);
+ }
+ } else {
+ hasEnded = true;
+ itTimeline.remove();
+ }
+ }
+ break;
+ default:
+ throw new IllegalStateException("Timeline cannot be in "
+ + timeline.getState() + " state");
+ }
+ if (hasEnded) {
+ if (DEBUG_MODE) {
+ System.out.println("Ending " + timeline.id + " on "
+ // + timeline.timelineKind.toString()
+ + " in state " + timeline.getState().name()
+ + " at position " + timeline.durationFraction);
+ }
+ TimelineState oldState = timeline.getState();
+ timeline.replaceState(TimelineState.DONE);
+ this.callbackCallTimelineStateChanged(timeline, oldState);
+ timeline.popState();
+ if (timeline.getState() != TimelineState.IDLE) {
+ throw new IllegalStateException(
+ "Timeline should be IDLE at this point");
+ }
+ this.callbackCallTimelineStateChanged(timeline,
+ TimelineState.DONE);
+ } else {
+ if (DEBUG_MODE) {
+ System.out.println("Calling " + timeline.id + " on "
+ // + timeline.timelineKind.toString() + " at "
+ + timeline.durationFraction);
+ }
+ this.callbackCallTimelinePulse(timeline);
+ }
+ }
+
+ if (this.runningScenarios.size() > 0) {
+ // System.err.println(Thread.currentThread().getName()
+ // + " : updating");
+ for (Iterator<TimelineScenario> it = this.runningScenarios
+ .iterator(); it.hasNext();) {
+ TimelineScenario scenario = it.next();
+ if (scenario.state == TimelineScenarioState.DONE) {
+ it.remove();
+ this.callbackCallTimelineScenarioEnded(scenario);
+ continue;
+ }
+ Set<TimelineScenario.TimelineScenarioActor> readyActors = scenario
+ .getReadyActors();
+ if (readyActors != null) {
+ // if (readyActors.size() > 0)
+ // System.out.println("Scenario : " + scenario.state +
+ // ":"
+ // + readyActors.size());
+ for (TimelineScenario.TimelineScenarioActor readyActor : readyActors) {
+ readyActor.play();
+ }
+ }
+ }
+ }
+ // System.err.println("Periodic update done");
+ } finally {
+ this.lastIterationTimeStamp = System.currentTimeMillis();
+ }
+ }
+ }
+
+ private boolean isTimelinesEmpty() {
+ boolean empty = (this.runningTimelines.size() == 0)
+ && (this.runningScenarios.size() == 0);
+ if (empty) {
+ if (scheduledPulseShutdown == Long.MAX_VALUE) {
+ scheduledPulseShutdown = lastIterationTimeStamp + 60000; // one minute;
+ }
+ } else {
+ scheduledPulseShutdown = Long.MAX_VALUE;
+ }
+ return empty;
+ }
+
+
+ private void callbackCallTimelineStateChanged(final Timeline timeline,
+ final TimelineState oldState) {
+ final TimelineState newState = timeline.getState();
+ final float durationFraction = timeline.durationFraction;
+ final float timelinePosition = timeline.timelinePosition;
+ Runnable callbackRunnable = new Runnable() {
+ @Override
+ public void run() {
+ boolean shouldRunOnUIThread = false;
+ Class<?> clazz = timeline.callback.getClass();
+ while ((clazz != null) && !shouldRunOnUIThread) {
+ shouldRunOnUIThread = clazz
+ .isAnnotationPresent(RunOnUIThread.class);
+ clazz = clazz.getSuperclass();
+ }
+ if (shouldRunOnUIThread && (timeline.uiToolkitHandler != null)) {
+ timeline.uiToolkitHandler.runOnUIThread(
+ timeline.mainObject, new Runnable() {
+ @Override
+ public void run() {
+ timeline.callback.onTimelineStateChanged(
+ oldState, newState,
+ durationFraction, timelinePosition);
+ }
+ });
+ } else {
+ timeline.callback.onTimelineStateChanged(oldState,
+ newState, durationFraction, timelinePosition);
+ }
+ }
+ };
+ this.callbackQueue.offer(callbackRunnable);
+ checkCallbackThread();
+ }
+
+ private void callbackCallTimelinePulse(final Timeline timeline) {
+ final float durationFraction = timeline.durationFraction;
+ final float timelinePosition = timeline.timelinePosition;
+ Runnable callbackRunnable = new Runnable() {
+ @Override
+ public void run() {
+ boolean shouldRunOnUIThread = false;
+ Class<?> clazz = timeline.callback.getClass();
+ while ((clazz != null) && !shouldRunOnUIThread) {
+ shouldRunOnUIThread = clazz
+ .isAnnotationPresent(RunOnUIThread.class);
+ clazz = clazz.getSuperclass();
+ }
+ if (shouldRunOnUIThread && (timeline.uiToolkitHandler != null)) {
+ timeline.uiToolkitHandler.runOnUIThread(
+ timeline.mainObject, new Runnable() {
+ @Override
+ public void run() {
+ // System.err.println("Timeline @"
+ // + timeline.hashCode());
+ timeline.callback.onTimelinePulse(
+ durationFraction, timelinePosition);
+ }
+ });
+ } else {
+ // System.err.println("Timeline @" + timeline.hashCode());
+ timeline.callback.onTimelinePulse(durationFraction,
+ timelinePosition);
+ }
+ }
+ };
+ this.callbackQueue.offer(callbackRunnable);
+ checkCallbackThread();
+ }
+
+ private void callbackCallTimelineScenarioEnded(
+ final TimelineScenario timelineScenario) {
+ Runnable callbackRunnable = new Runnable() {
+ @Override
+ public void run() {
+ timelineScenario.callback.onTimelineScenarioDone();
+ }
+ };
+ this.callbackQueue.offer(callbackRunnable);
+ checkCallbackThread();
+ }
+
+ /**
+ * Returns an existing running timeline that matches the specified
+ * parameters.
+ *
+ * @param timeline
+ * Timeline
+ * @return An existing running timeline that matches the specified
+ * parameters.
+ */
+ private Timeline getRunningTimeline(Timeline timeline) {
+ synchronized (LOCK) {
+ if (this.runningTimelines.contains(timeline))
+ return timeline;
+ return null;
+ }
+ }
+
+ /**
+ * Adds the specified timeline.
+ *
+ * @param timeline
+ * Timeline to add.
+ */
+ private void addTimeline(Timeline timeline) {
+ synchronized (LOCK) {
+ timeline.fullObjectID = new FullObjectID(timeline.mainObject,
+ timeline.secondaryId);
+ this.runningTimelines.add(timeline);
+ checkAnimatorThread();
+ // this.nothingTracked = false;
+ if (DEBUG_MODE) {
+ System.out.println("Added (" + timeline.id + ") on "
+ + timeline.fullObjectID + "]. Fade "
+ // + timeline.timelineKind.toString() + " with state "
+ + timeline.getState().name() + ". Callback - "
+ + (timeline.callback == null ? "no" : "yes"));
+ }
+ }
+ }
+
+ void play(Timeline timeline, boolean reset, long msToSkip) {
+ synchronized (LOCK) {
+ getAnimatorThread();
+
+ // see if it's already tracked
+ Timeline existing = this.getRunningTimeline(timeline);
+ if (existing == null) {
+ TimelineState oldState = timeline.getState();
+ timeline.timeUntilPlay = timeline.initialDelay - msToSkip;
+ if (timeline.timeUntilPlay < 0) {
+ timeline.durationFraction = (float) -timeline.timeUntilPlay
+ / (float) timeline.duration;
+ timeline.timelinePosition = timeline.ease
+ .map(timeline.durationFraction);
+ timeline.timeUntilPlay = 0;
+ } else {
+ timeline.durationFraction = 0.0f;
+ timeline.timelinePosition = 0.0f;
+ }
+ timeline.pushState(TimelineState.PLAYING_FORWARD);
+ timeline.pushState(TimelineState.READY);
+ this.addTimeline(timeline);
+
+ this.callbackCallTimelineStateChanged(timeline, oldState);
+ } else {
+ TimelineState oldState = existing.getState();
+ if (oldState == TimelineState.READY) {
+ // the timeline remains READY, but after that it will be
+ // PLAYING_FORWARD
+ existing.popState();
+ existing.replaceState(TimelineState.PLAYING_FORWARD);
+ existing.pushState(TimelineState.READY);
+ } else {
+ // change the timeline state
+ existing.replaceState(TimelineState.PLAYING_FORWARD);
+ if (oldState != existing.getState()) {
+ this.callbackCallTimelineStateChanged(timeline,
+ oldState);
+ }
+ }
+ if (reset) {
+ existing.durationFraction = 0.0f;
+ existing.timelinePosition = 0.0f;
+ this.callbackCallTimelinePulse(existing);
+ }
+ }
+ }
+ }
+
+ void playScenario(TimelineScenario scenario) {
+ synchronized (LOCK) {
+ getAnimatorThread();
+ Set<TimelineScenario.TimelineScenarioActor> readyActors = scenario
+ .getReadyActors();
+
+ // System.err.println(Thread.currentThread().getName() +
+ // " : adding");
+ this.runningScenarios.add(scenario);
+ checkAnimatorThread();
+ for (TimelineScenario.TimelineScenarioActor readyActor : readyActors) {
+ readyActor.play();
+ }
+ }
+ }
+
+ void playReverse(Timeline timeline, boolean reset, long msToSkip) {
+ synchronized (LOCK) {
+ getAnimatorThread();
+ if (timeline.isLooping) {
+ throw new IllegalArgumentException(
+ "Timeline must not be marked as looping");
+ }
+
+ // see if it's already tracked
+ Timeline existing = this.getRunningTimeline(timeline);
+ if (existing == null) {
+ TimelineState oldState = timeline.getState();
+ timeline.timeUntilPlay = timeline.initialDelay - msToSkip;
+ if (timeline.timeUntilPlay < 0) {
+ timeline.durationFraction = 1.0f
+ - (float) -timeline.timeUntilPlay
+ / (float) timeline.duration;
+ timeline.timelinePosition = timeline.ease
+ .map(timeline.durationFraction);
+ timeline.timeUntilPlay = 0;
+ } else {
+ timeline.durationFraction = 1.0f;
+ timeline.timelinePosition = 1.0f;
+ }
+ timeline.pushState(TimelineState.PLAYING_REVERSE);
+ timeline.pushState(TimelineState.READY);
+
+ this.addTimeline(timeline);
+ this.callbackCallTimelineStateChanged(timeline, oldState);
+ } else {
+ TimelineState oldState = existing.getState();
+ if (oldState == TimelineState.READY) {
+ // the timeline remains READY, but after that it will be
+ // PLAYING_REVERSE
+ existing.popState();
+ existing.replaceState(TimelineState.PLAYING_REVERSE);
+ existing.pushState(TimelineState.READY);
+ } else {
+ // change the timeline state
+ existing.replaceState(TimelineState.PLAYING_REVERSE);
+ if (oldState != existing.getState()) {
+ this.callbackCallTimelineStateChanged(timeline,
+ oldState);
+ }
+ }
+ if (reset) {
+ existing.durationFraction = 1.0f;
+ existing.timelinePosition = 1.0f;
+ this.callbackCallTimelinePulse(existing);
+ }
+ }
+ }
+ }
+
+ void playLoop(Timeline timeline, long msToSkip) {
+ synchronized (LOCK) {
+ getAnimatorThread();
+ if (!timeline.isLooping) {
+ throw new IllegalArgumentException(
+ "Timeline must be marked as looping");
+ }
+
+ // see if it's already tracked
+ Timeline existing = this.getRunningTimeline(timeline);
+ if (existing == null) {
+ TimelineState oldState = timeline.getState();
+ timeline.timeUntilPlay = timeline.initialDelay - msToSkip;
+ if (timeline.timeUntilPlay < 0) {
+ timeline.durationFraction = (float) -timeline.timeUntilPlay
+ / (float) timeline.duration;
+ timeline.timelinePosition = timeline.ease
+ .map(timeline.durationFraction);
+ timeline.timeUntilPlay = 0;
+ } else {
+ timeline.durationFraction = 0.0f;
+ timeline.timelinePosition = 0.0f;
+ }
+ timeline.pushState(TimelineState.PLAYING_FORWARD);
+ timeline.pushState(TimelineState.READY);
+ timeline.toCancelAtCycleBreak = false;
+
+ this.addTimeline(timeline);
+ this.callbackCallTimelineStateChanged(timeline, oldState);
+ } else {
+ existing.toCancelAtCycleBreak = false;
+ existing.repeatCount = timeline.repeatCount;
+ }
+ }
+ }
+
+ /**
+ * Stops tracking of all timelines. Note that this function <b>does not</b>
+ * stop the timeline engine thread ({@link #animatorThread}) and the
+ * timeline callback thread ({@link #callbackThread}).
+ */
+ public void cancelAllTimelines() {
+ synchronized (LOCK) {
+ getAnimatorThread();
+ for (Timeline timeline : this.runningTimelines) {
+ TimelineState oldState = timeline.getState();
+ while (timeline.getState() != TimelineState.IDLE)
+ timeline.popState();
+ timeline.pushState(TimelineState.CANCELLED);
+ this.callbackCallTimelineStateChanged(timeline, oldState);
+ timeline.popState();
+ this.callbackCallTimelineStateChanged(timeline,
+ TimelineState.CANCELLED);
+ }
+ this.runningTimelines.clear();
+ this.runningScenarios.clear();
+ }
+ }
+
+ /**
+ * Returns an instance of the animator thread.
+ *
+ * @return The animator thread.
+ */
+ private TridentAnimationThread getAnimatorThread() {
+ synchronized (threadSemaphore) {
+ if (this.animatorThread == null) {
+ this.animatorThread = new TridentAnimationThread();
+ this.animatorThread.start();
+ }
+ return this.animatorThread;
+ }
+ }
+
+ /**
+ * Returns an instance of the callback thread.
+ *
+ * @return The animator thread.
+ */
+ private TimelineCallbackThread getCallbackThread() {
+ synchronized (threadSemaphore) {
+ if (this.callbackThread == null) {
+ this.callbackThread = new TimelineCallbackThread();
+ this.callbackThread.start();
+ }
+ return this.callbackThread;
+ }
+ }
+
+ /**
+ * Cancels the specified timeline instance.
+ *
+ * @param timeline
+ * Timeline to cancel.
+ */
+ private void cancelTimeline(Timeline timeline) {
+ getAnimatorThread();
+ if (this.runningTimelines.contains(timeline)) {
+ this.runningTimelines.remove(timeline);
+ TimelineState oldState = timeline.getState();
+ while (timeline.getState() != TimelineState.IDLE)
+ timeline.popState();
+ timeline.pushState(TimelineState.CANCELLED);
+ this.callbackCallTimelineStateChanged(timeline, oldState);
+ timeline.popState();
+ this.callbackCallTimelineStateChanged(timeline,
+ TimelineState.CANCELLED);
+ }
+ }
+
+ /**
+ * Ends the specified timeline instance.
+ *
+ * @param timeline
+ * Timeline to end.
+ */
+ private void endTimeline(Timeline timeline) {
+ getAnimatorThread();
+ if (this.runningTimelines.contains(timeline)) {
+ this.runningTimelines.remove(timeline);
+ TimelineState oldState = timeline.getState();
+ float endPosition = timeline.timelinePosition;
+ while (timeline.getState() != TimelineState.IDLE) {
+ TimelineState state = timeline.popState();
+ if (state == TimelineState.PLAYING_FORWARD)
+ endPosition = 1.0f;
+ if (state == TimelineState.PLAYING_REVERSE)
+ endPosition = 0.0f;
+ }
+ timeline.durationFraction = endPosition;
+ timeline.timelinePosition = endPosition;
+ timeline.pushState(TimelineState.DONE);
+ this.callbackCallTimelineStateChanged(timeline, oldState);
+ timeline.popState();
+ this.callbackCallTimelineStateChanged(timeline, TimelineState.DONE);
+ }
+ }
+
+ /**
+ * Cancels the specified timeline instance.
+ *
+ * @param timeline
+ * Timeline to cancel.
+ */
+ private void abortTimeline(Timeline timeline) {
+ getAnimatorThread();
+ if (this.runningTimelines.contains(timeline)) {
+ this.runningTimelines.remove(timeline);
+ while (timeline.getState() != TimelineState.IDLE)
+ timeline.popState();
+ }
+ }
+
+ /**
+ * Suspends the specified timeline instance.
+ *
+ * @param timeline
+ * Timeline to suspend.
+ */
+ private void suspendTimeline(Timeline timeline) {
+ getAnimatorThread();
+ if (this.runningTimelines.contains(timeline)) {
+ TimelineState oldState = timeline.getState();
+ if ((oldState != TimelineState.PLAYING_FORWARD)
+ && (oldState != TimelineState.PLAYING_REVERSE)
+ && (oldState != TimelineState.READY)) {
+ return;
+ }
+ timeline.pushState(TimelineState.SUSPENDED);
+ this.callbackCallTimelineStateChanged(timeline, oldState);
+ }
+ }
+
+ /**
+ * Resume the specified timeline instance.
+ *
+ * @param timeline
+ * Timeline to resume.
+ */
+ private void resumeTimeline(Timeline timeline) {
+ getAnimatorThread();
+ if (this.runningTimelines.contains(timeline)) {
+ TimelineState oldState = timeline.getState();
+ if (oldState != TimelineState.SUSPENDED)
+ return;
+ timeline.popState();
+ this.callbackCallTimelineStateChanged(timeline, oldState);
+ }
+ }
+
+ void runTimelineOperation(Timeline timeline,
+ TimelineOperationKind operationKind, Runnable operationRunnable) {
+ synchronized (LOCK) {
+ this.getAnimatorThread();
+ switch (operationKind) {
+ case CANCEL:
+ this.cancelTimeline(timeline);
+ return;
+ case END:
+ this.endTimeline(timeline);
+ return;
+ case RESUME:
+ this.resumeTimeline(timeline);
+ return;
+ case SUSPEND:
+ this.suspendTimeline(timeline);
+ return;
+ case ABORT:
+ this.abortTimeline(timeline);
+ return;
+ }
+ operationRunnable.run();
+ }
+ }
+
+ void runTimelineScenario(TimelineScenario timelineScenario,
+ Runnable timelineScenarioRunnable) {
+ synchronized (LOCK) {
+ this.getAnimatorThread();
+ timelineScenarioRunnable.run();
+ }
+ }
+
+ static final Object LOCK = new Object();
+}
\ No newline at end of file
diff --git a/trident/src/main/java/org/pushingpixels/trident/TimelinePropertyBuilder.java b/trident/src/main/java/org/pushingpixels/trident/TimelinePropertyBuilder.java
new file mode 100644
index 0000000..b9d4699
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/TimelinePropertyBuilder.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.pushingpixels.trident.interpolator.KeyFrames;
+import org.pushingpixels.trident.interpolator.PropertyInterpolator;
+
+public class TimelinePropertyBuilder<T> {
+
+ /**
+ * Defines how to set a property.
+ */
+ public static interface PropertySetter<T> {
+ public void set(Object obj, String fieldName, T value);
+ }
+
+ /**
+ * Defines how to get a property.
+ */
+ public static interface PropertyGetter<T> {
+ public T get(Object obj, String fieldName);
+ }
+
+ /**
+ * Defines how to access a property.
+ */
+ public static interface PropertyAccessor<T> extends PropertyGetter<T>,
+ PropertySetter<T> {
+ }
+
+ /**
+ * Default property setter.
+ */
+ public static class DefaultPropertySetter<T> implements PropertySetter<T> {
+ private Method setterMethod;
+
+ public DefaultPropertySetter(Object obj, String fieldName) {
+ setterMethod = getSetter(obj, fieldName);
+ }
+
+ @Override
+ public void set(Object obj, String fieldName, T value) {
+ try {
+ setterMethod.invoke(obj, value);
+ } catch (Throwable t) {
+ throw new RuntimeException(
+ "Unable to set the value of the field '" + fieldName
+ + "'", t);
+ }
+ }
+ }
+
+ /**
+ * Default property getter.
+ */
+ public static class DefaultPropertyGetter<T> implements PropertyGetter<T> {
+ private Method getterMethod;
+
+ public DefaultPropertyGetter(Object obj, String fieldName) {
+ getterMethod = getGetter(obj, fieldName);
+ }
+
+ @Override
+ public T get(Object obj, String fieldName) {
+ try {
+ return (T) getterMethod.invoke(obj);
+ } catch (Throwable t) {
+ throw new RuntimeException(
+ "Unable to get the value of the field '" + fieldName
+ + "'", t);
+ }
+ }
+ }
+
+ private Object target; // may be null
+ private final String propertyName; // required
+ private T from; // optional
+ private boolean isFromCurrent;
+ private T to; // must be optional because of KeyFrames
+ private PropertyInterpolator<T> interpolator; // optional
+ private PropertyGetter<T> getter; // optional
+ private PropertySetter<T> setter; // optional
+ private KeyFrames<T> keyFrames; // optional
+
+ TimelinePropertyBuilder(String propertyName) {
+ this.propertyName = propertyName;
+ this.isFromCurrent = false;
+ }
+
+ public TimelinePropertyBuilder<T> from(T startValue) {
+ if (this.from != null) {
+ throw new IllegalArgumentException("from() can only be called once");
+ }
+ if (this.isFromCurrent) {
+ throw new IllegalArgumentException(
+ "from() cannot be called after fromCurrent()");
+ }
+ if (this.keyFrames != null) {
+ throw new IllegalArgumentException(
+ "from() cannot be called after goingThrough()");
+ }
+ this.from = startValue;
+ return this;
+ }
+
+ public TimelinePropertyBuilder<T> fromCurrent() {
+ if (this.isFromCurrent) {
+ throw new IllegalArgumentException(
+ "fromCurrent() can only be called once");
+ }
+ if (this.from != null) {
+ throw new IllegalArgumentException(
+ "fromCurrent() cannot be called after from()");
+ }
+ if (this.keyFrames != null) {
+ throw new IllegalArgumentException(
+ "fromCurrent() cannot be called after goingThrough()");
+ }
+ this.isFromCurrent = true;
+ return this;
+ }
+
+ public TimelinePropertyBuilder<T> to(T endValue) {
+ if (this.to != null) {
+ throw new IllegalArgumentException("to() can only be called once");
+ }
+ if (this.keyFrames != null) {
+ throw new IllegalArgumentException(
+ "to() cannot be called after goingThrough()");
+ }
+ this.to = endValue;
+ return this;
+ }
+
+ public TimelinePropertyBuilder<T> on(Object object) {
+ this.target = object;
+ return this;
+ }
+
+ public TimelinePropertyBuilder<T> interpolatedWith(
+ PropertyInterpolator<T> pInterpolator) {
+ if (this.interpolator != null) {
+ throw new IllegalArgumentException(
+ "interpolateWith() can only be called once");
+ }
+ this.interpolator = pInterpolator;
+ return this;
+ }
+
+ public TimelinePropertyBuilder<T> setWith(PropertySetter<T> pSetter) {
+ if (this.setter != null) {
+ throw new IllegalArgumentException(
+ "setWith() can only be called once");
+ }
+ this.setter = pSetter;
+ return this;
+ }
+
+ public TimelinePropertyBuilder<T> getWith(PropertyGetter<T> pGetter) {
+ if (this.getter != null) {
+ throw new IllegalArgumentException(
+ "getWith() can only be called once");
+ }
+ this.getter = pGetter;
+ return this;
+ }
+
+ public TimelinePropertyBuilder<T> accessWith(PropertyAccessor<T> pAccessor) {
+ if (this.setter != null || this.getter != null) {
+ throw new IllegalArgumentException(
+ "accessWith() can only be called once");
+ }
+ this.setter = pAccessor;
+ this.getter = pAccessor;
+ return this;
+ }
+
+ public TimelinePropertyBuilder<T> goingThrough(KeyFrames<T> keyFrames) {
+ if (this.keyFrames != null) {
+ throw new IllegalArgumentException(
+ "goingThrough() can only be called once");
+ }
+ if (this.isFromCurrent) {
+ throw new IllegalArgumentException(
+ "goingThrough() cannot be called after fromCurrent()");
+ }
+ if (this.from != null) {
+ throw new IllegalArgumentException(
+ "goingThrough() cannot be called after from()");
+ }
+ if (this.to != null) {
+ throw new IllegalArgumentException(
+ "goingThrough() cannot be called after to()");
+ }
+ this.keyFrames = keyFrames;
+ return this;
+ }
+
+ AbstractFieldInfo getFieldInfo(Timeline timeline) {
+ if (this.target == null) {
+ this.target = timeline.mainObject;
+ }
+
+ if (this.keyFrames != null) {
+ return new KeyFramesFieldInfo(this.target, this.propertyName,
+ this.keyFrames, this.setter);
+ }
+
+ if (this.isFromCurrent) {
+ if (this.interpolator == null) {
+ this.interpolator = TridentConfig.getInstance()
+ .getPropertyInterpolator(this.to);
+
+ if (this.interpolator == null) {
+ throw new IllegalArgumentException(
+ "No interpolator found for "
+ + this.to.getClass().getName());
+ }
+ }
+ return new GenericFieldInfoTo(this.target, this.propertyName,
+ this.to, this.interpolator, this.getter, this.setter);
+ }
+
+ if (this.interpolator == null) {
+ this.interpolator = TridentConfig.getInstance()
+ .getPropertyInterpolator(this.from, this.to);
+
+ if (this.interpolator == null) {
+ throw new IllegalArgumentException("No interpolator found for "
+ + this.from.getClass().getName() + ":"
+ + this.to.getClass().getName());
+ }
+ }
+ return new GenericFieldInfo(this.target, this.propertyName, this.from,
+ this.to, this.interpolator, this.setter);
+ }
+
+ abstract class AbstractFieldInfo<T> {
+ protected Object object;
+
+ protected String fieldName;
+
+ protected PropertyGetter getter;
+ protected PropertySetter setter;
+
+ protected T from;
+
+ protected T to;
+
+ AbstractFieldInfo(Object obj, String fieldName,
+ PropertyGetter<T> pGetter, PropertySetter<T> pSetter) {
+ this.object = obj;
+ this.fieldName = fieldName;
+
+ this.getter = pGetter;
+ this.setter = pSetter;
+ }
+
+ void setValues(T from, T to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ abstract void onStart();
+
+ abstract void updateFieldValue(float timelinePosition);
+ }
+
+ private static <T> PropertyGetter<T> getPropertyGetter(Object obj,
+ String fieldName, PropertyGetter<T> pGetter) {
+ if (pGetter != null)
+ return pGetter;
+ return new DefaultPropertyGetter(obj, fieldName);
+ }
+
+ private static <T> PropertySetter<T> getPropertySetter(Object obj,
+ String fieldName, PropertySetter<T> pSetter) {
+ if (pSetter != null)
+ return pSetter;
+ return new DefaultPropertySetter(obj, fieldName);
+ }
+
+ private class GenericFieldInfoTo extends AbstractFieldInfo<Object> {
+ private PropertyInterpolator propertyInterpolator;
+
+ private Object to;
+
+ GenericFieldInfoTo(Object obj, String fieldName, Object to,
+ PropertyInterpolator propertyInterpolator,
+ PropertyGetter propertyGetter, PropertySetter propertySetter) {
+ super(obj, fieldName, getPropertyGetter(obj, fieldName,
+ propertyGetter), getPropertySetter(obj, fieldName,
+ propertySetter));
+ this.propertyInterpolator = propertyInterpolator;
+ this.to = to;
+ }
+
+ @Override
+ void onStart() {
+ this.from = getter.get(object, fieldName);
+ }
+
+ @Override
+ void updateFieldValue(float timelinePosition) {
+ try {
+ Object value = this.propertyInterpolator.interpolate(from, to,
+ timelinePosition);
+ this.setter.set(this.object, this.fieldName, value);
+ } catch (Throwable exc) {
+ System.err.println("Exception occurred in updating field '"
+ + this.fieldName + "' of object "
+ + this.object.getClass().getCanonicalName()
+ + " at timeline position " + timelinePosition);
+ exc.printStackTrace();
+ }
+ }
+ }
+
+ private class GenericFieldInfo extends AbstractFieldInfo<Object> {
+ private PropertyInterpolator propertyInterpolator;
+
+ GenericFieldInfo(Object obj, String fieldName, Object from, Object to,
+ PropertyInterpolator propertyInterpolator,
+ PropertySetter propertySetter) {
+ super(obj, fieldName, null, getPropertySetter(obj, fieldName,
+ propertySetter));
+ this.propertyInterpolator = propertyInterpolator;
+ this.setValues(from, to);
+ }
+
+ @Override
+ void onStart() {
+ }
+
+ @Override
+ void updateFieldValue(float timelinePosition) {
+ try {
+ Object value = this.propertyInterpolator.interpolate(from, to,
+ timelinePosition);
+ this.setter.set(this.object, this.fieldName, value);
+ } catch (Throwable exc) {
+ System.err.println("Exception occurred in updating field '"
+ + this.fieldName + "' of object "
+ + this.object.getClass().getCanonicalName()
+ + " at timeline position " + timelinePosition);
+ exc.printStackTrace();
+ }
+ }
+ }
+
+ private class KeyFramesFieldInfo extends AbstractFieldInfo<Object> {
+ KeyFrames keyFrames;
+
+ KeyFramesFieldInfo(Object obj, String fieldName, KeyFrames keyFrames,
+ PropertySetter propertySetter) {
+ super(obj, fieldName, null, getPropertySetter(obj, fieldName,
+ propertySetter));
+ this.keyFrames = keyFrames;
+ }
+
+ @Override
+ void onStart() {
+ }
+
+ @Override
+ void updateFieldValue(float timelinePosition) {
+ if (this.setter != null) {
+ try {
+ Object value = this.keyFrames.getValue(timelinePosition);
+ this.setter.set(this.object, this.fieldName, value);
+ } catch (Throwable exc) {
+ exc.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private static Method getSetter(Object object, String propertyName) {
+ String setterMethodName = "set"
+ + Character.toUpperCase(propertyName.charAt(0))
+ + propertyName.substring(1);
+ Class oClazz = object.getClass();
+ while (oClazz != null) {
+ for (Method m : oClazz.getMethods()) {
+ if (setterMethodName.equals(m.getName())
+ && (m.getParameterTypes().length == 1)
+ && (m.getReturnType() == Void.TYPE)
+ && (!Modifier.isStatic(m.getModifiers()))) {
+ return m;
+ }
+ }
+ oClazz = oClazz.getSuperclass();
+ }
+ return null;
+ }
+
+ private static Method getGetter(Object object, String propertyName) {
+ String getterMethodName = "get"
+ + Character.toUpperCase(propertyName.charAt(0))
+ + propertyName.substring(1);
+ Class oClazz = object.getClass();
+ while (oClazz != null) {
+ for (Method m : oClazz.getMethods()) {
+ if (getterMethodName.equals(m.getName())
+ && (m.getParameterTypes().length == 0)
+ && (!Modifier.isStatic(m.getModifiers()))) {
+ return m;
+ }
+ }
+ oClazz = oClazz.getSuperclass();
+ }
+ return null;
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/TimelineRunnable.java b/trident/src/main/java/org/pushingpixels/trident/TimelineRunnable.java
new file mode 100644
index 0000000..2d790fc
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/TimelineRunnable.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident;
+
+import java.util.concurrent.*;
+
+import org.pushingpixels.trident.TimelineScenario.TimelineScenarioActor;
+
+public abstract class TimelineRunnable implements Runnable,
+ TimelineScenarioActor {
+ private static ExecutorService service = new ThreadPoolExecutor(0,
+ Integer.MAX_VALUE, 10L, TimeUnit.SECONDS,
+ new SynchronousQueue<Runnable>());
+
+ private Future<?> future;
+
+ @Override
+ public void play() {
+ this.future = service.submit(this);
+ }
+
+ @Override
+ public boolean isDone() {
+ if (this.future == null)
+ return false;
+ return this.future.isDone();
+ }
+
+ @Override
+ public boolean supportsReplay() {
+ return false;
+ }
+
+ @Override
+ public void resetDoneFlag() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/TimelineScenario.java b/trident/src/main/java/org/pushingpixels/trident/TimelineScenario.java
new file mode 100644
index 0000000..784d362
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/TimelineScenario.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident;
+
+import java.util.*;
+
+import org.pushingpixels.trident.callback.TimelineScenarioCallback;
+
+public class TimelineScenario {
+ private Set<TimelineScenarioActor> waitingActors;
+
+ private Set<TimelineScenarioActor> runningActors;
+
+ private Set<TimelineScenarioActor> doneActors;
+
+ private Map<TimelineScenarioActor, Set<TimelineScenarioActor>> dependencies;
+
+ Chain callback;
+
+ TimelineScenarioState state;
+
+ TimelineScenarioState statePriorToSuspension;
+
+ boolean isLooping;
+
+ public enum TimelineScenarioState {
+ DONE, PLAYING, IDLE, SUSPENDED
+ }
+
+ class Chain implements TimelineScenarioCallback {
+ private List<TimelineScenarioCallback> callbacks;
+
+ public Chain(TimelineScenarioCallback... callbacks) {
+ this.callbacks = new ArrayList<TimelineScenarioCallback>();
+ for (TimelineScenarioCallback callback : callbacks)
+ this.callbacks.add(callback);
+ }
+
+ public void addCallback(TimelineScenarioCallback callback) {
+ this.callbacks.add(callback);
+ }
+
+ @Override
+ public void onTimelineScenarioDone() {
+ for (TimelineScenarioCallback callback : this.callbacks)
+ callback.onTimelineScenarioDone();
+ }
+ }
+
+ public static interface TimelineScenarioActor {
+ public boolean isDone();
+
+ public boolean supportsReplay();
+
+ public void resetDoneFlag();
+
+ public void play();
+ }
+
+ public TimelineScenario() {
+ this.waitingActors = new HashSet<TimelineScenarioActor>();
+ this.runningActors = new HashSet<TimelineScenarioActor>();
+ this.doneActors = new HashSet<TimelineScenarioActor>();
+
+ this.dependencies = new HashMap<TimelineScenarioActor, Set<TimelineScenarioActor>>();
+ this.callback = new Chain();
+ this.state = TimelineScenarioState.IDLE;
+ }
+
+ public void addScenarioActor(TimelineScenarioActor actor) {
+ if (actor.isDone()) {
+ throw new IllegalArgumentException("Already finished");
+ }
+ this.waitingActors.add(actor);
+ }
+
+ public void addCallback(TimelineScenarioCallback callback) {
+ if (this.doneActors.size() > 0) {
+ throw new IllegalArgumentException(
+ "Cannot change state of non-idle timeline scenario");
+ }
+ this.callback.addCallback(callback);
+ }
+
+ private void checkDependencyParam(TimelineScenarioActor actor) {
+ if (!waitingActors.contains(actor)) {
+ throw new IllegalArgumentException(
+ "Must be first added with addScenarioActor() API");
+ }
+ }
+
+ public void addDependency(TimelineScenarioActor actor,
+ TimelineScenarioActor... waitFor) {
+ // check params
+ this.checkDependencyParam(actor);
+ for (TimelineScenarioActor wait : waitFor) {
+ this.checkDependencyParam(wait);
+ }
+
+ if (!this.dependencies.containsKey(actor))
+ this.dependencies.put(actor, new HashSet<TimelineScenarioActor>());
+ this.dependencies.get(actor).addAll(Arrays.asList(waitFor));
+ }
+
+ private void checkDoneActors() {
+ synchronized (TimelineEngine.LOCK) {
+ for (Iterator<TimelineScenarioActor> itRunning = this.runningActors
+ .iterator(); itRunning.hasNext();) {
+ TimelineScenarioActor stillRunning = itRunning.next();
+ if (stillRunning.isDone()) {
+ itRunning.remove();
+ this.doneActors.add(stillRunning);
+ }
+ }
+ }
+ }
+
+ Set<TimelineScenarioActor> getReadyActors() {
+ synchronized (TimelineEngine.LOCK) {
+ if (this.state == TimelineScenarioState.SUSPENDED)
+ return new HashSet<TimelineScenarioActor>();
+
+ this.checkDoneActors();
+
+ Set<TimelineScenarioActor> result = new HashSet<TimelineScenarioActor>();
+ for (Iterator<TimelineScenarioActor> itWaiting = this.waitingActors
+ .iterator(); itWaiting.hasNext();) {
+ TimelineScenarioActor waitingActor = itWaiting.next();
+ boolean canRun = true;
+ Set<TimelineScenarioActor> toWaitFor = this.dependencies
+ .get(waitingActor);
+ if (toWaitFor != null) {
+ for (TimelineScenarioActor actorToWaitFor : toWaitFor) {
+ if (!doneActors.contains(actorToWaitFor)) {
+ canRun = false;
+ break;
+ }
+ }
+ }
+ if (canRun) {
+ runningActors.add(waitingActor);
+ itWaiting.remove();
+ result.add(waitingActor);
+ }
+ }
+
+ if (this.waitingActors.isEmpty() && this.runningActors.isEmpty()) {
+ if (!this.isLooping) {
+ this.state = TimelineScenarioState.DONE;
+ } else {
+ for (TimelineScenarioActor done : this.doneActors)
+ done.resetDoneFlag();
+ this.waitingActors.addAll(this.doneActors);
+ this.doneActors.clear();
+ }
+ }
+
+ return result;
+ }
+ }
+
+ public void cancel() {
+ synchronized (TimelineEngine.LOCK) {
+ TimelineScenarioState oldState = this.state;
+ if (oldState != TimelineScenarioState.PLAYING)
+ return;
+ this.state = TimelineScenarioState.DONE;
+
+ for (TimelineScenarioActor waiting : this.waitingActors) {
+ if (waiting instanceof Timeline) {
+ ((Timeline) waiting).cancel();
+ }
+ }
+ for (TimelineScenarioActor running : this.runningActors) {
+ if (running instanceof Timeline) {
+ ((Timeline) running).cancel();
+ }
+ }
+ }
+ }
+
+ public void suspend() {
+ synchronized (TimelineEngine.LOCK) {
+ TimelineScenarioState oldState = this.state;
+ if (oldState != TimelineScenarioState.PLAYING)
+ return;
+ this.statePriorToSuspension = oldState;
+ this.state = TimelineScenarioState.SUSPENDED;
+
+ for (TimelineScenarioActor running : this.runningActors) {
+ if (running instanceof Timeline) {
+ ((Timeline) running).suspend();
+ }
+ }
+ }
+ }
+
+ public void resume() {
+ synchronized (TimelineEngine.LOCK) {
+ TimelineScenarioState oldState = this.state;
+ if (oldState != TimelineScenarioState.SUSPENDED)
+ return;
+ this.state = this.statePriorToSuspension;
+
+ for (TimelineScenarioActor running : this.runningActors) {
+ if (running instanceof Timeline) {
+ ((Timeline) running).resume();
+ }
+ }
+ }
+ }
+
+ public void play() {
+ this.isLooping = false;
+ this.state = TimelineScenarioState.PLAYING;
+
+ TimelineEngine.getInstance().runTimelineScenario(this, new Runnable() {
+ @Override
+ public void run() {
+ TimelineEngine.getInstance()
+ .playScenario(TimelineScenario.this);
+ }
+ });
+ }
+
+ public void playLoop() {
+ for (TimelineScenarioActor actor : this.waitingActors) {
+ if (!actor.supportsReplay())
+ throw new UnsupportedOperationException(
+ "Can't loop scenario with actor(s) that don't support replay");
+ }
+ this.isLooping = true;
+ this.state = TimelineScenarioState.PLAYING;
+ TimelineEngine.getInstance().runTimelineScenario(this, new Runnable() {
+ @Override
+ public void run() {
+ TimelineEngine.getInstance()
+ .playScenario(TimelineScenario.this);
+ }
+ });
+ }
+
+ public static class Parallel extends TimelineScenario {
+ @Override
+ public void addDependency(TimelineScenarioActor actor,
+ TimelineScenarioActor... waitFor) {
+ throw new UnsupportedOperationException(
+ "Explicit dependencies not supported");
+ }
+ }
+
+ public static class Sequence extends TimelineScenario {
+ private TimelineScenarioActor lastActor;
+
+ @Override
+ public void addDependency(TimelineScenarioActor actor,
+ TimelineScenarioActor... waitFor) {
+ throw new UnsupportedOperationException(
+ "Explicit dependencies not supported");
+ }
+
+ @Override
+ public void addScenarioActor(TimelineScenarioActor actor) {
+ super.addScenarioActor(actor);
+ if (this.lastActor != null) {
+ super.addDependency(actor, this.lastActor);
+ }
+ this.lastActor = actor;
+ }
+ }
+
+ public static class RendezvousSequence extends TimelineScenario {
+ private Set<TimelineScenarioActor> addedSinceLastRendezvous;
+
+ private Set<TimelineScenarioActor> addedPriorToLastRendezvous;
+
+ public RendezvousSequence() {
+ this.addedSinceLastRendezvous = new HashSet<TimelineScenarioActor>();
+ this.addedPriorToLastRendezvous = new HashSet<TimelineScenarioActor>();
+ }
+
+ @Override
+ public void addDependency(TimelineScenarioActor actor,
+ TimelineScenarioActor... waitFor) {
+ throw new UnsupportedOperationException(
+ "Explicit dependencies not supported");
+ }
+
+ @Override
+ public void addScenarioActor(TimelineScenarioActor actor) {
+ super.addScenarioActor(actor);
+ this.addedSinceLastRendezvous.add(actor);
+ }
+
+ public void rendezvous() {
+ // make all actors added since last rendezvous to wait for
+ // all actors added prior to last rendezvous
+ if (this.addedPriorToLastRendezvous.size() > 0) {
+ for (TimelineScenarioActor sinceLast : this.addedSinceLastRendezvous) {
+ for (TimelineScenarioActor beforeLast : this.addedPriorToLastRendezvous) {
+ super.addDependency(sinceLast, beforeLast);
+ }
+ }
+ }
+
+ this.addedPriorToLastRendezvous.clear();
+ this.addedPriorToLastRendezvous
+ .addAll(this.addedSinceLastRendezvous);
+ this.addedSinceLastRendezvous.clear();
+ }
+
+ @Override
+ public void play() {
+ // add last implicit rendezvous
+ this.rendezvous();
+ super.play();
+ }
+
+ @Override
+ public void playLoop() {
+ // add last implicit rendezvous
+ this.rendezvous();
+ super.playLoop();
+ }
+ }
+
+ public final TimelineScenarioState getState() {
+ return this.state;
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/TridentConfig.java b/trident/src/main/java/org/pushingpixels/trident/TridentConfig.java
new file mode 100755
index 0000000..220f9cc
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/TridentConfig.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident;
+
+import java.io.*;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+
+import org.pushingpixels.trident.TimelineEngine.TridentAnimationThread;
+import org.pushingpixels.trident.interpolator.PropertyInterpolator;
+import org.pushingpixels.trident.interpolator.PropertyInterpolatorSource;
+
+public class TridentConfig {
+ private static TridentConfig config;
+
+ private Set<UIToolkitHandler> uiToolkitHandlers;
+
+ private Set<PropertyInterpolator> propertyInterpolators;
+
+ private TridentConfig.PulseSource pulseSource;
+
+ public interface PulseSource {
+ public void waitUntilNextPulse();
+ }
+
+ public static class FixedRatePulseSource implements
+ TridentConfig.PulseSource {
+ private int msDelay;
+
+ public FixedRatePulseSource(int msDelay) {
+ this.msDelay = msDelay;
+ }
+
+ @Override
+ public void waitUntilNextPulse() {
+ try {
+ Thread.sleep(this.msDelay);
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ }
+
+ private class DefaultPulseSource extends FixedRatePulseSource {
+ DefaultPulseSource() {
+ super(40);
+ }
+ }
+
+ private TridentConfig() {
+ this.pulseSource = new DefaultPulseSource();
+
+ this.uiToolkitHandlers = new HashSet<UIToolkitHandler>();
+ this.propertyInterpolators = new HashSet<PropertyInterpolator>();
+ ClassLoader classLoader = getClassLoader();
+ try {
+ Enumeration urls = classLoader
+ .getResources("META-INF/trident-plugin.properties");
+ while (urls.hasMoreElements()) {
+ URL pluginUrl = (URL) urls.nextElement();
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(pluginUrl
+ .openStream()));
+ while (true) {
+ String line = reader.readLine();
+ if (line == null)
+ break;
+ String[] parts = line.split("=");
+ if (parts.length != 2)
+ continue;
+ String key = parts[0];
+ String value = parts[1];
+ if ("UIToolkitHandler".compareTo(key) == 0) {
+ try {
+ Class pluginClass = classLoader
+ .loadClass(value);
+ if (pluginClass == null)
+ continue;
+ if (UIToolkitHandler.class
+ .isAssignableFrom(pluginClass)) {
+ UIToolkitHandler uiToolkitHandler = (UIToolkitHandler) pluginClass
+ .newInstance();
+ uiToolkitHandler.isHandlerFor(new Object());
+ this.uiToolkitHandlers
+ .add(uiToolkitHandler);
+ }
+ } catch (NoClassDefFoundError ncdfe) {
+ // trying to initialize a plugin with a missing
+ // class
+ } catch (ClassNotFoundException ncdfe) {
+ // trying to initialize a plugin with a missing
+ // class
+ }
+ }
+ if ("PropertyInterpolatorSource".compareTo(key) == 0) {
+ try {
+ Class piSourceClass = classLoader
+ .loadClass(value);
+ if (piSourceClass == null)
+ continue;
+ if (PropertyInterpolatorSource.class
+ .isAssignableFrom(piSourceClass)) {
+ PropertyInterpolatorSource piSource = (PropertyInterpolatorSource) piSourceClass
+ .newInstance();
+ Set<PropertyInterpolator> interpolators = piSource
+ .getPropertyInterpolators();
+ for (PropertyInterpolator pi : interpolators) {
+ try {
+ Class basePropertyClass = pi
+ .getBasePropertyClass();
+ // is in classpath?
+ basePropertyClass.getClass();
+ this.propertyInterpolators.add(pi);
+ } catch (NoClassDefFoundError ncdfe) {
+ // trying to initialize a plugin
+ // with a missing
+ // class - just skip
+ }
+
+ }
+ // this.propertyInterpolators.addAll(piSource
+ // .getPropertyInterpolators());
+ }
+ } catch (NoClassDefFoundError ncdfe) {
+ // trying to initialize a plugin with a missing
+ // class
+ }
+ }
+ }
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }
+ } catch (Exception exc) {
+ exc.printStackTrace();
+ }
+ }
+
+ private static ClassLoader getClassLoader() {
+ ClassLoader cl = null;
+
+ try {
+ cl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ @Override
+ public ClassLoader run() {
+ return TridentConfig.class.getClassLoader();
+ }
+ });
+ } catch (SecurityException ignore) { }
+
+ if (cl == null) {
+ final Thread t = Thread.currentThread();
+
+ try {
+ cl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ @Override
+ public ClassLoader run() {
+ return t.getContextClassLoader();
+ }
+ });
+ } catch (SecurityException ignore) { }
+ }
+
+ if (cl == null) {
+ try {
+ cl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ @Override
+ public ClassLoader run() {
+ return ClassLoader.getSystemClassLoader();
+ }
+ });
+ } catch (SecurityException ignore) { }
+ }
+
+ return cl;
+ }
+
+ public static synchronized TridentConfig getInstance() {
+ if (config == null)
+ config = new TridentConfig();
+ return config;
+ }
+
+ public synchronized Collection<UIToolkitHandler> getUIToolkitHandlers() {
+ return Collections.unmodifiableSet(this.uiToolkitHandlers);
+ }
+
+ public synchronized Collection<PropertyInterpolator> getPropertyInterpolators() {
+ return Collections.unmodifiableSet(this.propertyInterpolators);
+ }
+
+ public synchronized PropertyInterpolator getPropertyInterpolator(
+ Object... values) {
+ for (PropertyInterpolator interpolator : this.propertyInterpolators) {
+ try {
+ Class basePropertyClass = interpolator.getBasePropertyClass();
+ boolean hasMatch = true;
+ for (Object value : values) {
+ if (!basePropertyClass.isAssignableFrom(value.getClass())) {
+ hasMatch = false;
+ }
+ }
+ if (hasMatch)
+ return interpolator;
+ } catch (NoClassDefFoundError ignore) {
+ }
+ }
+ return null;
+ }
+
+ public synchronized void addPropertyInterpolator(
+ PropertyInterpolator pInterpolator) {
+ this.propertyInterpolators.add(pInterpolator);
+ }
+
+ public synchronized void addPropertyInterpolatorSource(
+ PropertyInterpolatorSource pInterpolatorSource) {
+ this.propertyInterpolators.addAll(pInterpolatorSource
+ .getPropertyInterpolators());
+ }
+
+ public synchronized void removePropertyInterpolator(
+ PropertyInterpolator pInterpolator) {
+ this.propertyInterpolators.remove(pInterpolator);
+ }
+
+ public synchronized void addUIToolkitHandler(
+ UIToolkitHandler uiToolkitHandler) {
+ this.uiToolkitHandlers.add(uiToolkitHandler);
+ }
+
+ public synchronized void removeUIToolkitHandler(
+ UIToolkitHandler uiToolkitHandler) {
+ this.uiToolkitHandlers.remove(uiToolkitHandler);
+ }
+
+ public synchronized void setPulseSource(PulseSource pulseSource) {
+ TridentAnimationThread current = TimelineEngine.getInstance().animatorThread;
+ if ((current != null) && current.isAlive())
+ throw new IllegalStateException(
+ "Cannot replace the pulse source thread once it's running");
+ this.pulseSource = pulseSource;
+ }
+
+ public synchronized TridentConfig.PulseSource getPulseSource() {
+ return pulseSource;
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/UIToolkitHandler.java b/trident/src/main/java/org/pushingpixels/trident/UIToolkitHandler.java
new file mode 100644
index 0000000..6511800
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/UIToolkitHandler.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident;
+
+public interface UIToolkitHandler {
+ public boolean isHandlerFor(Object mainTimelineObject);
+
+ public boolean isInReadyState(Object mainTimelineObject);
+
+ public void runOnUIThread(Object mainTimelineObject, Runnable runnable);
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/android/AndroidPropertyInterpolators.java b/trident/src/main/java/org/pushingpixels/trident/android/AndroidPropertyInterpolators.java
new file mode 100644
index 0000000..ba01d42
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/android/AndroidPropertyInterpolators.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.android;
+
+import java.util.*;
+
+import org.pushingpixels.trident.interpolator.PropertyInterpolator;
+import org.pushingpixels.trident.interpolator.PropertyInterpolatorSource;
+
+import android.graphics.*;
+
+/**
+ * Built-in interpolators for Android classes.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class AndroidPropertyInterpolators implements PropertyInterpolatorSource {
+ private Set<PropertyInterpolator> interpolators;
+
+ public static final PropertyInterpolator<Integer> COLOR_INTERPOLATOR = new ColorInterpolator();
+
+ public AndroidPropertyInterpolators() {
+ this.interpolators = new HashSet<PropertyInterpolator>();
+ this.interpolators.add(COLOR_INTERPOLATOR);
+ this.interpolators.add(new PointInterpolator());
+ this.interpolators.add(new RectInterpolator());
+ this.interpolators.add(new RectFInterpolator());
+ }
+
+ @Override
+ public Set<PropertyInterpolator> getPropertyInterpolators() {
+ return Collections.unmodifiableSet(this.interpolators);
+ }
+
+ static class ColorInterpolator implements PropertyInterpolator<Integer> {
+ @Override
+ public Class getBasePropertyClass() {
+ return Color.class;
+ }
+
+ @Override
+ public Integer interpolate(Integer from, Integer to,
+ float timelinePosition) {
+ return getInterpolatedRGB(from, to, 1.0f - timelinePosition);
+ }
+
+ int getInterpolatedRGB(Integer color1, Integer color2,
+ float color1Likeness) {
+ if ((color1Likeness < 0.0) || (color1Likeness > 1.0))
+ throw new IllegalArgumentException(
+ "Color likeness should be in 0.0-1.0 range [is "
+ + color1Likeness + "]");
+
+ if (color1.equals(color2))
+ return color1;
+ if (color1Likeness == 1.0)
+ return color1;
+ if (color1Likeness == 0.0)
+ return color2;
+
+ int lr = Color.red(color1);
+ int lg = Color.green(color1);
+ int lb = Color.blue(color1);
+ int la = Color.alpha(color1);
+ int dr = Color.red(color2);
+ int dg = Color.green(color2);
+ int db = Color.blue(color2);
+ int da = Color.alpha(color2);
+
+ // using some interpolation values (such as 0.29 from issue 401)
+ // results in an incorrect final value without Math.round.
+ int r = (lr == dr) ? lr : (int) Math.round(color1Likeness * lr
+ + (1.0 - color1Likeness) * dr);
+ int g = (lg == dg) ? lg : (int) Math.round(color1Likeness * lg
+ + (1.0 - color1Likeness) * dg);
+ int b = (lb == db) ? lb : (int) Math.round(color1Likeness * lb
+ + (1.0 - color1Likeness) * db);
+ int a = (la == da) ? la : (int) Math.round(color1Likeness * la
+ + (1.0 - color1Likeness) * da);
+
+ return Color.argb(a, r, g, b);
+ }
+ }
+
+ static class PointInterpolator implements PropertyInterpolator<Point> {
+ @Override
+ public Point interpolate(Point from, Point to, float timelinePosition) {
+ int x = from.x + (int) (timelinePosition * (to.x - from.x));
+ int y = from.y + (int) (timelinePosition * (to.y - from.y));
+ return new Point(x, y);
+ }
+
+ @Override
+ public Class getBasePropertyClass() {
+ return Point.class;
+ }
+ }
+
+ static class RectInterpolator implements PropertyInterpolator<Rect> {
+ @Override
+ public Rect interpolate(Rect from, Rect to, float timelinePosition) {
+ int left = from.left
+ + (int) (timelinePosition * (to.left - from.left));
+ int top = from.top + (int) (timelinePosition * (to.top - from.top));
+ int right = from.right
+ + (int) (timelinePosition * (to.right - from.right));
+ int bottom = from.bottom
+ + (int) (timelinePosition * (to.bottom - from.bottom));
+ return new Rect(left, top, right, bottom);
+ }
+
+ @Override
+ public Class getBasePropertyClass() {
+ return Rect.class;
+ }
+ }
+
+ static class RectFInterpolator implements PropertyInterpolator<RectF> {
+ @Override
+ public RectF interpolate(RectF from, RectF to, float timelinePosition) {
+ float left = from.left
+ + (int) (timelinePosition * (to.left - from.left));
+ float top = from.top
+ + (int) (timelinePosition * (to.top - from.top));
+ float right = from.right
+ + (int) (timelinePosition * (to.right - from.right));
+ float bottom = from.bottom
+ + (int) (timelinePosition * (to.bottom - from.bottom));
+ return new RectF(left, top, right, bottom);
+ }
+
+ @Override
+ public Class getBasePropertyClass() {
+ return RectF.class;
+ }
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/android/AndroidRepaintCallback.java b/trident/src/main/java/org/pushingpixels/trident/android/AndroidRepaintCallback.java
new file mode 100644
index 0000000..4a59d72
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/android/AndroidRepaintCallback.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.android;
+
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.RunOnUIThread;
+import org.pushingpixels.trident.callback.TimelineCallback;
+
+import android.graphics.Rect;
+import android.view.View;
+
+ at RunOnUIThread
+public class AndroidRepaintCallback implements TimelineCallback {
+ private View view;
+
+ private Rect rect;
+
+ public AndroidRepaintCallback(View view) {
+ this(view, null);
+ }
+
+ public AndroidRepaintCallback(View view, Rect rect) {
+ if (view == null) {
+ throw new NullPointerException("View must be non-null");
+ }
+ this.view = view;
+ this.rect = rect;
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction, float timelinePosition) {
+ if (this.rect == null)
+ this.view.invalidate();
+ else
+ this.view.invalidate(this.rect.left, this.rect.top,
+ this.rect.right, this.rect.bottom);
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ if (this.rect == null)
+ this.view.invalidate();
+ else
+ this.view.invalidate(this.rect.left, this.rect.top,
+ this.rect.right, this.rect.bottom);
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/android/AndroidRepaintTimeline.java b/trident/src/main/java/org/pushingpixels/trident/android/AndroidRepaintTimeline.java
new file mode 100644
index 0000000..4d93206
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/android/AndroidRepaintTimeline.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.android;
+
+import org.pushingpixels.trident.Timeline;
+
+import android.graphics.Rect;
+import android.view.View;
+
+public class AndroidRepaintTimeline extends Timeline {
+ public AndroidRepaintTimeline(View mainTimelineView) {
+ this(mainTimelineView, null);
+ }
+
+ public AndroidRepaintTimeline(View mainTimelineView, Rect toRepaint) {
+ super(mainTimelineView);
+ this
+ .addCallback(new AndroidRepaintCallback(mainTimelineView,
+ toRepaint));
+ }
+
+ @Override
+ public void play() {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+
+ @Override
+ public void playReverse() {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+
+ @Override
+ public void replay() {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+
+ @Override
+ public void replayReverse() {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+
+ @Override
+ public void playLoop(int loopCount, RepeatBehavior repeatBehavior) {
+ if (loopCount >= 0) {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+ super.playLoop(loopCount, repeatBehavior);
+ }
+}
\ No newline at end of file
diff --git a/trident/src/main/java/org/pushingpixels/trident/android/AndroidToolkitHandler.java b/trident/src/main/java/org/pushingpixels/trident/android/AndroidToolkitHandler.java
new file mode 100644
index 0000000..b4e285c
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/android/AndroidToolkitHandler.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.android;
+
+import org.pushingpixels.trident.UIToolkitHandler;
+
+import android.view.View;
+
+public class AndroidToolkitHandler implements UIToolkitHandler {
+ @Override
+ public boolean isHandlerFor(Object mainTimelineObject) {
+ return (mainTimelineObject instanceof View);
+ }
+
+ @Override
+ public boolean isInReadyState(Object mainTimelineObject) {
+ return true;
+ }
+
+ @Override
+ public void runOnUIThread(Object mainTimelineObject, final Runnable runnable) {
+ ((View) mainTimelineObject).post(runnable);
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/android/TimelineAsyncTask.java b/trident/src/main/java/org/pushingpixels/trident/android/TimelineAsyncTask.java
new file mode 100644
index 0000000..76cbb0f
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/android/TimelineAsyncTask.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.android;
+
+import org.pushingpixels.trident.TimelineScenario;
+
+import android.os.AsyncTask;
+
+public abstract class TimelineAsyncTask<T, V, U> extends AsyncTask<T, V, U>
+ implements TimelineScenario.TimelineScenarioActor {
+ @Override
+ public void play() {
+ this.execute();
+ }
+
+ @Override
+ public boolean supportsReplay() {
+ return false;
+ }
+
+ @Override
+ public void resetDoneFlag() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/callback/RunOnUIThread.java b/trident/src/main/java/org/pushingpixels/trident/callback/RunOnUIThread.java
new file mode 100644
index 0000000..984bb83
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/callback/RunOnUIThread.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.callback;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to mark code that should run on UI thread.
+ *
+ * @author Kirill Grouchnikov
+ */
+ at Target(ElementType.TYPE)
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface RunOnUIThread {
+}
\ No newline at end of file
diff --git a/trident/src/main/java/org/pushingpixels/trident/callback/TimelineCallback.java b/trident/src/main/java/org/pushingpixels/trident/callback/TimelineCallback.java
new file mode 100644
index 0000000..f93327d
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/callback/TimelineCallback.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.callback;
+
+import org.pushingpixels.trident.Timeline;
+import org.pushingpixels.trident.Timeline.TimelineState;
+
+/**
+ * Callback for the fade tracker. Is used when the application (some UI
+ * delegate) wishes to execute some code on the fade.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface TimelineCallback {
+ /**
+ * Indicates that the timeline state has changed.
+ *
+ * @param oldState
+ * The old timeline state.
+ * @param newState
+ * The new timeline state.
+ * @param durationFraction
+ * The current timeline duration fraction.Is guaranteed to be in
+ * 0.0-1.0 range. The rate of change of this value is linear, and
+ * the value is proportional to
+ * {@link Timeline#setDuration(long)}.
+ * @param timelinePosition
+ * The current timeline position. Is guaranteed to be in 0.0-1.0
+ * range. The rate of change of this value is not necessarily
+ * linear and is affected by the
+ * {@link Timeline#setEase(org.pushingpixels.trident.ease.TimelineEase)}
+ * .
+ */
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition);
+
+ /**
+ * Indicates that the timeline pulse has happened.
+ *
+ * @param durationFraction
+ * The current timeline duration fraction.Is guaranteed to be in
+ * 0.0-1.0 range. The rate of change of this value is linear, and
+ * the value is proportional to
+ * {@link Timeline#setDuration(long)}.
+ * @param timelinePosition
+ * The current timeline position. Is guaranteed to be in 0.0-1.0
+ * range. The rate of change of this value is not necessarily
+ * linear and is affected by the
+ * {@link Timeline#setEase(org.pushingpixels.trident.ease.TimelineEase)}
+ * .
+ */
+ public void onTimelinePulse(float durationFraction, float timelinePosition);
+}
\ No newline at end of file
diff --git a/trident/src/main/java/org/pushingpixels/trident/callback/TimelineCallbackAdapter.java b/trident/src/main/java/org/pushingpixels/trident/callback/TimelineCallbackAdapter.java
new file mode 100644
index 0000000..ea15dad
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/callback/TimelineCallbackAdapter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.callback;
+
+import org.pushingpixels.trident.Timeline.TimelineState;
+
+/**
+ * Default implementation of {@link TimelineCallback} that does nothing.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class TimelineCallbackAdapter implements TimelineCallback {
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction, float timelinePosition) {
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/callback/TimelineScenarioCallback.java b/trident/src/main/java/org/pushingpixels/trident/callback/TimelineScenarioCallback.java
new file mode 100644
index 0000000..2ec8539
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/callback/TimelineScenarioCallback.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.callback;
+
+import org.pushingpixels.trident.TimelineScenario;
+
+/**
+ * Callback for tracking the {@link TimelineScenario}s.
+ *
+ * @author Kirill Grouchnikov
+ */
+public interface TimelineScenarioCallback {
+ /**
+ * Indicates that the all timelines and swing workers in the timeline
+ * scenario have finished.
+ */
+ public void onTimelineScenarioDone();
+}
\ No newline at end of file
diff --git a/trident/src/main/java/org/pushingpixels/trident/callback/UIThreadTimelineCallbackAdapter.java b/trident/src/main/java/org/pushingpixels/trident/callback/UIThreadTimelineCallbackAdapter.java
new file mode 100644
index 0000000..a4ae59e
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/callback/UIThreadTimelineCallbackAdapter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.callback;
+
+/**
+ * Empty implementation of {@link TimelineCallback} that does nothing but is
+ * marked to run on the EDT.
+ *
+ * @author Kirill Grouchnikov
+ */
+ at RunOnUIThread
+public class UIThreadTimelineCallbackAdapter extends TimelineCallbackAdapter {
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/ease/Linear.java b/trident/src/main/java/org/pushingpixels/trident/ease/Linear.java
new file mode 100644
index 0000000..b39c8a9
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/ease/Linear.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.ease;
+
+public class Linear implements TimelineEase {
+ @Override
+ public float map(float durationFraction) {
+ return durationFraction;
+ }
+
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/ease/Sine.java b/trident/src/main/java/org/pushingpixels/trident/ease/Sine.java
new file mode 100644
index 0000000..11952e7
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/ease/Sine.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.ease;
+
+public class Sine implements TimelineEase {
+ @Override
+ public float map(float durationFraction) {
+ return (float) Math.sin(durationFraction * Math.PI / 2.0);
+ }
+
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/ease/Spline.java b/trident/src/main/java/org/pushingpixels/trident/ease/Spline.java
new file mode 100644
index 0000000..d401a25
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/ease/Spline.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.ease;
+
+import java.util.ArrayList;
+
+/**
+ * Spline easer. Is based on the code from <a
+ * href="https://timingframework.dev.java.net">TimingFramework</a> by Chet Haase
+ * and Romain Guy.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class Spline implements TimelineEase {
+ // private float easeAmount;
+
+ public Spline(float easeAmount) {
+ this(easeAmount, 0, 1 - easeAmount, 1);
+ // this.easeAmount = easeAmount;
+ }
+
+ private static class FloatPoint {
+ public float x;
+ public float y;
+
+ public FloatPoint(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+ }
+
+ // Note: (x0,y0) and (x1,y1) are implicitly (0, 0) and (1,1) respectively
+ private float x1, y1, x2, y2;
+ private ArrayList lengths = new ArrayList();
+
+ /**
+ * Creates a new instance of SplineInterpolator with the control points
+ * defined by (x1, y1) and (x2, y2). The anchor points are implicitly
+ * defined as (0, 0) and (1, 1).
+ *
+ * @throws IllegalArgumentException
+ * This exception is thrown when values beyond the allowed [0,1]
+ * range are passed in
+ */
+ public Spline(float x1, float y1, float x2, float y2) {
+ if (x1 < 0 || x1 > 1.0f || y1 < 0 || y1 > 1.0f || x2 < 0 || x2 > 1.0f
+ || y2 < 0 || y2 > 1.0f) {
+ throw new IllegalArgumentException("Control points must be in "
+ + "the range [0, 1]:");
+ }
+
+ this.x1 = x1;
+ this.y1 = y1;
+ this.x2 = x2;
+ this.y2 = y2;
+
+ // Now contruct the array of all lengths to t in [0, 1.0]
+ float prevX = 0.0f;
+ float prevY = 0.0f;
+ float prevLength = 0.0f; // cumulative length
+ for (float t = 0.01f; t <= 1.0f; t += .01f) {
+ FloatPoint xy = getXY(t);
+ float length = prevLength
+ + (float) Math.sqrt((xy.x - prevX) * (xy.x - prevX)
+ + (xy.y - prevY) * (xy.y - prevY));
+ LengthItem lengthItem = new LengthItem(length, t);
+ lengths.add(lengthItem);
+ prevLength = length;
+ prevX = xy.x;
+ prevY = xy.y;
+ }
+ // Now calculate the fractions so that we can access the lengths
+ // array with values in [0,1]. prevLength now holds the total
+ // length of the spline.
+ for (int i = 0; i < lengths.size(); ++i) {
+ LengthItem lengthItem = (LengthItem) lengths.get(i);
+ lengthItem.setFraction(prevLength);
+ }
+ }
+
+ /**
+ * Calculates the XY point for a given t value.
+ *
+ * The general spline equation is: x = b0*x0 + b1*x1 + b2*x2 + b3*x3 y =
+ * b0*y0 + b1*y1 + b2*y2 + b3*y3 where: b0 = (1-t)^3 b1 = 3 * t * (1-t)^2 b2
+ * = 3 * t^2 * (1-t) b3 = t^3 We know that (x0,y0) == (0,0) and (x1,y1) ==
+ * (1,1) for our splines, so this simplifies to: x = b1*x1 + b2*x2 + b3 y =
+ * b1*x1 + b2*x2 + b3
+ *
+ * @param t
+ * parametric value for spline calculation
+ */
+ private FloatPoint getXY(float t) {
+ FloatPoint xy;
+ float invT = (1 - t);
+ float b1 = 3 * t * (invT * invT);
+ float b2 = 3 * (t * t) * invT;
+ float b3 = t * t * t;
+ xy = new FloatPoint((b1 * x1) + (b2 * x2) + b3, (b1 * y1)
+ + (b2 * y2) + b3);
+ return xy;
+ }
+
+ /**
+ * Utility function: When we are evaluating the spline, we only care about
+ * the Y values. See {@link #getXY(float)} for the details.
+ */
+ private float getY(float t) {
+ FloatPoint xy;
+ float invT = (1 - t);
+ float b1 = 3 * t * (invT * invT);
+ float b2 = 3 * (t * t) * invT;
+ float b3 = t * t * t;
+ return (b1 * y1) + (b2 * y2) + b3;
+ }
+
+ /**
+ * Given a fraction of time along the spline (which we can interpret as the
+ * length along a spline), return the interpolated value of the spline. We
+ * first calculate the t value for the length (by doing a lookup in our
+ * array of previousloy calculated values and then linearly interpolating
+ * between the nearest values) and then calculate the Y value for this t.
+ *
+ * @param lengthFraction
+ * Fraction of time in a given time interval.
+ * @return interpolated fraction between 0 and 1
+ */
+ @Override
+ public float map(float lengthFraction) {
+ // REMIND: speed this up with binary search
+ float interpolatedT = 1.0f;
+ float prevT = 0.0f;
+ float prevLength = 0.0f;
+ for (int i = 0; i < lengths.size(); ++i) {
+ LengthItem lengthItem = (LengthItem) lengths.get(i);
+ float fraction = lengthItem.getFraction();
+ float t = lengthItem.getT();
+ if (lengthFraction <= fraction) {
+ // answer lies between last item and this one
+ float proportion = (lengthFraction - prevLength)
+ / (fraction - prevLength);
+ interpolatedT = prevT + proportion * (t - prevT);
+ return getY(interpolatedT);
+ }
+ prevLength = fraction;
+ prevT = t;
+ }
+ return getY(interpolatedT);
+ }
+}
+
+/**
+ * Struct used to store information about length values. Specifically, each item
+ * stores the "length" (which can be thought of as the time elapsed along the
+ * spline path), the "t" value at this length (used to calculate the (x,y) point
+ * along the spline), and the "fraction" which is equal to the length divided by
+ * the total absolute length of the spline. After we calculate all LengthItems
+ * for a give spline, we have a list of entries which can return the t values
+ * for fractional lengths from 0 to 1.
+ */
+class LengthItem {
+ float length;
+ float t;
+ float fraction;
+
+ LengthItem(float length, float t, float fraction) {
+ this.length = length;
+ this.t = t;
+ this.fraction = fraction;
+ }
+
+ LengthItem(float length, float t) {
+ this.length = length;
+ this.t = t;
+ }
+
+ public float getLength() {
+ return length;
+ }
+
+ public float getT() {
+ return t;
+ }
+
+ public float getFraction() {
+ return fraction;
+ }
+
+ void setFraction(float totalLength) {
+ fraction = length / totalLength;
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/ease/TimelineEase.java b/trident/src/main/java/org/pushingpixels/trident/ease/TimelineEase.java
new file mode 100644
index 0000000..fd26e1b
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/ease/TimelineEase.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.ease;
+
+public interface TimelineEase {
+ public float map(float durationFraction);
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/interpolator/CorePropertyInterpolators.java b/trident/src/main/java/org/pushingpixels/trident/interpolator/CorePropertyInterpolators.java
new file mode 100644
index 0000000..9ff189c
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/interpolator/CorePropertyInterpolators.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.interpolator;
+
+import java.util.*;
+
+public class CorePropertyInterpolators implements PropertyInterpolatorSource {
+ private Set<PropertyInterpolator> interpolators;
+
+ public CorePropertyInterpolators() {
+ this.interpolators = new HashSet<PropertyInterpolator>();
+ this.interpolators.add(new IntegerPropertyInterpolator());
+ this.interpolators.add(new FloatPropertyInterpolator());
+ this.interpolators.add(new DoublePropertyInterpolator());
+ this.interpolators.add(new LongPropertyInterpolator());
+ }
+
+ @Override
+ public Set<PropertyInterpolator> getPropertyInterpolators() {
+ return Collections.unmodifiableSet(this.interpolators);
+ }
+
+ private static class FloatPropertyInterpolator implements
+ PropertyInterpolator<Float> {
+ @Override
+ public Class getBasePropertyClass() {
+ return Float.class;
+ }
+
+ @Override
+ public Float interpolate(Float from, Float to, float timelinePosition) {
+ return from + (to - from) * timelinePosition;
+ }
+ }
+
+ private static class DoublePropertyInterpolator implements
+ PropertyInterpolator<Double> {
+ @Override
+ public Class getBasePropertyClass() {
+ return Double.class;
+ }
+
+ @Override
+ public Double interpolate(Double from, Double to, float timelinePosition) {
+ return from + (to - from) * timelinePosition;
+ }
+ }
+
+ private static class IntegerPropertyInterpolator implements
+ PropertyInterpolator<Integer> {
+ @Override
+ public Class getBasePropertyClass() {
+ return Integer.class;
+ }
+
+ @Override
+ public Integer interpolate(Integer from, Integer to,
+ float timelinePosition) {
+ return (int) (from + (to - from) * timelinePosition);
+ }
+ }
+
+ private static class LongPropertyInterpolator implements
+ PropertyInterpolator<Long> {
+ @Override
+ public Class getBasePropertyClass() {
+ return Long.class;
+ }
+
+ @Override
+ public Long interpolate(Long from, Long to, float timelinePosition) {
+ return (long) (from + (to - from) * timelinePosition);
+ }
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/interpolator/KeyFrames.java b/trident/src/main/java/org/pushingpixels/trident/interpolator/KeyFrames.java
new file mode 100644
index 0000000..cd88858
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/interpolator/KeyFrames.java
@@ -0,0 +1,225 @@
+/**
+ * Copyright (c) 2006, Sun Microsystems, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of the TimingFramework project nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.trident.interpolator;
+
+import org.pushingpixels.trident.ease.TimelineEase;
+
+/**
+ *
+ * KeyFrames holds information about the times at which values are sampled
+ * (KeyTimes) and the values at those times (KeyValues). It also holds
+ * information about how to interpolate between these values for times that lie
+ * between the sampling points.
+ *
+ * @author Chet
+ */
+public class KeyFrames<T> {
+
+ private KeyValues<T> keyValues;
+ private KeyTimes keyTimes;
+ private KeyInterpolators interpolators;
+
+ /**
+ * Simplest variation; determine keyTimes based on even division of 0-1
+ * range based on number of keyValues. This constructor assumes LINEAR
+ * interpolation.
+ *
+ * @param keyValues
+ * values that will be assumed at each time in keyTimes
+ */
+ public KeyFrames(KeyValues<T> keyValues) {
+ init(keyValues, null, (TimelineEase) null);
+ }
+
+ /**
+ * This variant takes both keyValues (values at each point in time) and
+ * keyTimes (times at which values are sampled).
+ *
+ * @param keyValues
+ * values that the animation will assume at each of the
+ * corresponding times in keyTimes
+ * @param keyTimes
+ * times at which the animation will assume the corresponding
+ * values in keyValues
+ * @throws IllegalArgumentException
+ * keyTimes and keySizes must have the same number of elements
+ * since these structures are meant to have corresponding
+ * entries; an exception is thrown otherwise.
+ */
+ public KeyFrames(KeyValues<T> keyValues, KeyTimes keyTimes) {
+ init(keyValues, keyTimes, (TimelineEase) null);
+ }
+
+ /**
+ * Full constructor: caller provides an instance of all key* structures
+ * which will be used to calculate between all times in the keyTimes list. A
+ * null interpolator parameter is equivalent to calling
+ * {@link KeyFrames#KeyFrames(KeyValues, KeyTimes)}.
+ *
+ * @param keyValues
+ * values that the animation will assume at each of the
+ * corresponding times in keyTimes
+ * @param keyTimes
+ * times at which the animation will assume the corresponding
+ * values in keyValues
+ * @param interpolators
+ * collection of Interpolators that control the calculation of
+ * values in each of the intervals defined by keyFrames. If this
+ * value is null, a {@link org.pushingpixels.trident.ease.Linear} Interpolater will be used for
+ * all intervals. If there is only one interpolator, that
+ * interpolator will be used for all intervals. Otherwise, there
+ * must be a number of interpolators equal to the number of
+ * intervals (which is one less than the number of keyTimes).
+ * @throws IllegalArgumentException
+ * keyTimes and keyValues must have the same number of elements
+ * since these structures are meant to have corresponding
+ * entries; an exception is thrown otherwise.
+ * @throws IllegalArgumentException
+ * The number of interpolators must either be zero
+ * (interpolators == null), one, or one less than the size of
+ * keyTimes.
+ */
+ public KeyFrames(KeyValues<T> keyValues, KeyTimes keyTimes,
+ TimelineEase... interpolators) {
+ init(keyValues, keyTimes, interpolators);
+ }
+
+ /**
+ * Utility constructor that assumes even division of times according to size
+ * of keyValues and interpolation according to interpolators parameter.
+ *
+ * @param keyValues
+ * values that the animation will assume at each of the
+ * corresponding times in keyTimes
+ * @param interpolators
+ * collection of Interpolators that control the calculation of
+ * values in each of the intervals defined by keyFrames. If this
+ * value is null, a {@link org.pushingpixels.trident.ease.Linear} Interpolator will be used for
+ * all intervals. If there is only one interpolator, that
+ * interpolator will be used for all intervals. Otherwise, there
+ * must be a number of interpolators equal to the number of
+ * intervals (which is one less than the number of keyTimes).
+ * @throws IllegalArgumentException
+ * The number of interpolators must either be zero
+ * (interpolators == null), one, or one less than the size of
+ * keyTimes.
+ */
+ public KeyFrames(KeyValues<T> keyValues, TimelineEase... interpolators) {
+ init(keyValues, null, interpolators);
+ }
+
+ /**
+ * Utility function called by constructors to perform common initialization
+ * chores
+ */
+ private void init(KeyValues<T> keyValues, KeyTimes keyTimes,
+ TimelineEase... interpolators) {
+ int numFrames = keyValues.getSize();
+ // If keyTimes null, create our own
+ if (keyTimes == null) {
+ float keyTimesArray[] = new float[numFrames];
+ float timeVal = 0.0f;
+ keyTimesArray[0] = timeVal;
+ for (int i = 1; i < (numFrames - 1); ++i) {
+ timeVal += (1.0f / (numFrames - 1));
+ keyTimesArray[i] = timeVal;
+ }
+ keyTimesArray[numFrames - 1] = 1.0f;
+ this.keyTimes = new KeyTimes(keyTimesArray);
+ } else {
+ this.keyTimes = keyTimes;
+ }
+ this.keyValues = keyValues;
+ if (numFrames != this.keyTimes.getSize()) {
+ throw new IllegalArgumentException("keyValues and keyTimes"
+ + " must be of equal size");
+ }
+ if (interpolators != null && (interpolators.length != (numFrames - 1))
+ && (interpolators.length != 1)) {
+ throw new IllegalArgumentException(
+ "interpolators must be "
+ + "either null (implying interpolation for all intervals), "
+ + "a single interpolator (which will be used for all "
+ + "intervals), or a number of interpolators equal to "
+ + "one less than the number of times.");
+ }
+ this.interpolators = new KeyInterpolators(numFrames - 1, interpolators);
+ }
+
+ public Class getType() {
+ return keyValues.getType();
+ }
+
+ KeyValues getKeyValues() {
+ return keyValues;
+ }
+
+ KeyTimes getKeyTimes() {
+ return keyTimes;
+ }
+
+ /**
+ * Returns time interval that contains this time fraction
+ */
+ public int getInterval(float fraction) {
+ return keyTimes.getInterval(fraction);
+ }
+
+ /**
+ * Returns a value for the given fraction elapsed of the animation cycle.
+ * Given the fraction, this method will determine what interval the fraction
+ * lies within, how much of that interval has elapsed, what the boundary
+ * values are (from KeyValues), what the interpolated fraction is (from the
+ * Interpolator for the interval), and what the final interpolated
+ * intermediate value is (using the appropriate Evaluator). This method will
+ * call into the Interpolator for the time interval to get the interpolated
+ * method. To ensure that future operations succeed, the value received from
+ * the interpolation will be clamped to the interval [0,1].
+ */
+ public Object getValue(float fraction) {
+ // First, figure out the real fraction to use, given the
+ // interpolation type and keyTimes
+ int interval = getInterval(fraction);
+ float t0 = keyTimes.getTime(interval);
+ float t1 = keyTimes.getTime(interval + 1);
+ float t = (fraction - t0) / (t1 - t0);
+ float interpolatedT = interpolators.interpolate(interval, t);
+ // clamp to avoid problems with buggy Interpolators
+ if (interpolatedT < 0f) {
+ interpolatedT = 0f;
+ } else if (interpolatedT > 1f) {
+ interpolatedT = 1f;
+ }
+ return keyValues.getValue(interval, (interval + 1), interpolatedT);
+ }
+
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/interpolator/KeyInterpolators.java b/trident/src/main/java/org/pushingpixels/trident/interpolator/KeyInterpolators.java
new file mode 100644
index 0000000..0f5b3b6
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/interpolator/KeyInterpolators.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2006, Sun Microsystems, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of the TimingFramework project nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.trident.interpolator;
+
+import java.util.ArrayList;
+
+import org.pushingpixels.trident.ease.Linear;
+import org.pushingpixels.trident.ease.TimelineEase;
+
+/**
+ *
+ * @author Chet
+ */
+class KeyInterpolators {
+
+ private ArrayList<TimelineEase> interpolators = new ArrayList<TimelineEase>();
+
+ /**
+ * Creates a new instance of KeyInterpolators
+ */
+ KeyInterpolators(int numIntervals, TimelineEase... interpolators) {
+ if (interpolators == null || interpolators[0] == null) {
+ for (int i = 0; i < numIntervals; ++i) {
+ this.interpolators.add(new Linear());
+ }
+ } else if (interpolators.length < numIntervals) {
+ for (int i = 0; i < numIntervals; ++i) {
+ this.interpolators.add(interpolators[0]);
+ }
+ } else {
+ for (int i = 0; i < numIntervals; ++i) {
+ this.interpolators.add(interpolators[i]);
+ }
+ }
+ }
+
+ float interpolate(int interval, float fraction) {
+ return interpolators.get(interval).map(fraction);
+ }
+
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/interpolator/KeyTimes.java b/trident/src/main/java/org/pushingpixels/trident/interpolator/KeyTimes.java
new file mode 100644
index 0000000..3bbca1f
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/interpolator/KeyTimes.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2005-2006, Sun Microsystems, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of the TimingFramework project nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.trident.interpolator;
+
+import java.util.ArrayList;
+
+/**
+ * Stores a list of times from 0 to 1 (the elapsed fraction of an animation
+ * cycle) that are used in calculating interpolated
+ * values for PropertySetter given a matching set of KeyValues and
+ * Interpolators for those time intervals. In the simplest case, a
+ * KeyFrame will consist of just two times in KeyTimes: 0 and 1.
+ *
+ * @author Chet
+ */
+public class KeyTimes {
+
+ private ArrayList<Float> times = new ArrayList<Float>();
+
+ /**
+ * Creates a new instance of KeyTimes. Times should be in increasing
+ * order and should all be in the range [0,1], with the first value
+ * being zero and the last being 1
+ * @throws IllegalArgumentException Time values must be ordered in
+ * increasing value, the first value must be 0 and the last value
+ * must be 1
+ */
+ public KeyTimes(float... times) {
+ if (times[0] != 0) {
+ throw new IllegalArgumentException("First time value must" +
+ " be zero");
+ }
+ if (times[times.length - 1] != 1.0f) {
+ throw new IllegalArgumentException("Last time value must" +
+ " be one");
+ }
+ float prevTime = 0;
+ for (float time : times) {
+ if (time < prevTime) {
+ throw new IllegalArgumentException("Time values must be" +
+ " in increasing order");
+ }
+ this.times.add(time);
+ prevTime = time;
+ }
+ }
+
+ ArrayList getTimes() {
+ return times;
+ }
+
+ int getSize() {
+ return times.size();
+ }
+
+ /**
+ * Returns time interval that contains this time fraction
+ */
+ int getInterval(float fraction) {
+ int prevIndex = 0;
+ for (int i = 1; i < times.size(); ++i) {
+ float time = times.get(i);
+ if (time >= fraction) {
+ // inclusive of start time at next interval. So fraction==1
+ // will return the final interval (times.size() - 1)
+ return prevIndex;
+ }
+ prevIndex = i;
+ }
+ return prevIndex;
+ }
+
+ float getTime(int index) {
+ return times.get(index);
+ }
+}
\ No newline at end of file
diff --git a/trident/src/main/java/org/pushingpixels/trident/interpolator/KeyValues.java b/trident/src/main/java/org/pushingpixels/trident/interpolator/KeyValues.java
new file mode 100644
index 0000000..902af8e
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/interpolator/KeyValues.java
@@ -0,0 +1,185 @@
+/**
+ * Copyright (c) 2006, Sun Microsystems, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of the TimingFramework project nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.pushingpixels.trident.interpolator;
+
+import java.util.*;
+
+import org.pushingpixels.trident.TridentConfig;
+import org.pushingpixels.trident.TimelinePropertyBuilder.PropertySetter;
+
+/**
+ * Stores a list of values that correspond to the times in a {@link KeyTimes}
+ * object. These structures are then used to create a {@link KeyFrames} object,
+ * which is then used to create a {@link PropertySetter} for the purposes of
+ * modifying an object's property over time.
+ * <p>
+ * At each of the times in {@link KeyTimes}, the property will take on the
+ * corresponding value in the KeyValues object. Between these times, the
+ * property will take on a value based on the interpolation information stored
+ * in the KeyFrames object and the {@link PropertyInterpolator} for the type of the values
+ * in KeyValues.
+ * <p>
+ * This class has built-in support for various known types, as defined in
+ * {@link PropertyInterpolator}.
+ * <p>
+ * For a simple example using KeyValues to create a KeyFrames and PropertySetter
+ * object, see the class header comments in {@link PropertySetter}.
+ *
+ *
+ * @author Chet
+ */
+public class KeyValues<T> {
+
+ private final List<T> values = new ArrayList<T>();
+ private final PropertyInterpolator<T> evaluator;
+ private final Class<?> type;
+ private T startValue;
+
+ /**
+ * Constructs a KeyValues object from one or more values. The internal
+ * Evaluator is automatically determined by the type of the parameters.
+ *
+ * @param params
+ * the values to interpolate between. If there is only one
+ * parameter, this is assumed to be a "to" animation where the
+ * first value is dynamically determined at runtime when the
+ * animation is started.
+ * @throws IllegalArgumentException
+ * if a {@link PropertyInterpolator} cannot be found that can interpolate
+ * between the value types supplied
+ */
+ public static <T> KeyValues<T> create(T... params) {
+ return new KeyValues(params);
+ }
+
+ /**
+ * Constructs a KeyValues object from a Evaluator and one or more values.
+ *
+ * @param params
+ * the values to interpolate between. If there is only one
+ * parameter, this is assumed to be a "to" animation where the
+ * first value is dynamically determined at runtime when the
+ * animation is started.
+ * @throws IllegalArgumentException
+ * if params does not have at least one value.
+ */
+ public static <T> KeyValues<T> create(PropertyInterpolator evaluator,
+ T... params) {
+ return new KeyValues(evaluator, params);
+ }
+
+ /**
+ * Private constructor, called by factory method
+ */
+ private KeyValues(T... params) {
+ this(TridentConfig.getInstance().getPropertyInterpolator(params), params);
+ }
+
+ /**
+ * Private constructor, called by factory method
+ */
+ private KeyValues(PropertyInterpolator evaluator, T... params) {
+ if (params == null) {
+ throw new IllegalArgumentException("params array cannot be null");
+ } else if (params.length == 0) {
+ throw new IllegalArgumentException(
+ "params array must have at least one element");
+ }
+ if (params.length == 1) {
+ // this is a "to" animation; set first element to null
+ values.add(null);
+ }
+ Collections.addAll(values, params);
+ this.type = params.getClass().getComponentType();
+ this.evaluator = evaluator;
+ }
+
+ /**
+ * Returns the number of values stored in this object.
+ *
+ * @return the number of values stored in this object
+ */
+ int getSize() {
+ return values.size();
+ }
+
+ /**
+ * Returns the data type of the values stored in this object.
+ *
+ * @return a Class value representing the type of values stored in this
+ * object
+ */
+ Class<?> getType() {
+ return this.type;
+ }
+
+ /**
+ * Called at start of animation; sets starting value in simple "to"
+ * animations.
+ */
+ void setStartValue(T startValue) {
+ if (isToAnimation()) {
+ this.startValue = startValue;
+ }
+ }
+
+ /**
+ * Utility method for determining whether this is a "to" animation (true if
+ * the first value is null).
+ */
+ boolean isToAnimation() {
+ return (values.get(0) == null);
+ }
+
+ /**
+ * Returns value calculated from the value at the lower index, the value at
+ * the upper index, the fraction elapsed between these endpoints, and the
+ * evaluator set up by this object at construction time.
+ */
+ T getValue(int i0, int i1, float fraction) {
+ T value;
+ T lowerValue = values.get(i0);
+ if (lowerValue == null) {
+ // "to" animation
+ lowerValue = startValue;
+ }
+ if (i0 == i1) {
+ // trivial case
+ value = lowerValue;
+ } else {
+ T v0 = lowerValue;
+ T v1 = values.get(i1);
+ value = evaluator.interpolate(v0, v1, fraction);
+ }
+ return value;
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/interpolator/PropertyInterpolator.java b/trident/src/main/java/org/pushingpixels/trident/interpolator/PropertyInterpolator.java
new file mode 100644
index 0000000..fd7572e
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/interpolator/PropertyInterpolator.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.interpolator;
+
+public interface PropertyInterpolator<T> {
+ public Class getBasePropertyClass();
+
+ public T interpolate(T from, T to, float timelinePosition);
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/interpolator/PropertyInterpolatorSource.java b/trident/src/main/java/org/pushingpixels/trident/interpolator/PropertyInterpolatorSource.java
new file mode 100644
index 0000000..7738059
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/interpolator/PropertyInterpolatorSource.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.interpolator;
+
+import java.util.Set;
+
+public interface PropertyInterpolatorSource {
+ public Set<PropertyInterpolator> getPropertyInterpolators();
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/swing/AWTPropertyInterpolators.java b/trident/src/main/java/org/pushingpixels/trident/swing/AWTPropertyInterpolators.java
new file mode 100644
index 0000000..0e0d1e8
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/swing/AWTPropertyInterpolators.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.swing;
+
+import java.awt.*;
+import java.util.*;
+
+import org.pushingpixels.trident.interpolator.PropertyInterpolator;
+import org.pushingpixels.trident.interpolator.PropertyInterpolatorSource;
+
+/**
+ * Built-in interpolators for Swing / AWT / Java2D classes.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class AWTPropertyInterpolators implements PropertyInterpolatorSource {
+ private Set<PropertyInterpolator> interpolators;
+
+ public AWTPropertyInterpolators() {
+ this.interpolators = new HashSet<PropertyInterpolator>();
+ this.interpolators.add(new ColorInterpolator());
+ this.interpolators.add(new PointInterpolator());
+ this.interpolators.add(new RectangleInterpolator());
+ this.interpolators.add(new DimensionInterpolator());
+ }
+
+ @Override
+ public Set<PropertyInterpolator> getPropertyInterpolators() {
+ return Collections.unmodifiableSet(this.interpolators);
+ }
+
+ static class ColorInterpolator implements PropertyInterpolator<Color> {
+ @Override
+ public Class getBasePropertyClass() {
+ return Color.class;
+ }
+
+ @Override
+ public Color interpolate(Color from, Color to, float timelinePosition) {
+ return getInterpolatedColor(from, to, 1.0f - timelinePosition);
+ }
+
+ int getInterpolatedRGB(Color color1, Color color2, float color1Likeness) {
+ if ((color1Likeness < 0.0) || (color1Likeness > 1.0))
+ throw new IllegalArgumentException(
+ "Color likeness should be in 0.0-1.0 range [is "
+ + color1Likeness + "]");
+ int lr = color1.getRed();
+ int lg = color1.getGreen();
+ int lb = color1.getBlue();
+ int la = color1.getAlpha();
+ int dr = color2.getRed();
+ int dg = color2.getGreen();
+ int db = color2.getBlue();
+ int da = color2.getAlpha();
+
+ // using some interpolation values (such as 0.29 from issue 401)
+ // results in an incorrect final value without Math.round.
+ int r = (lr == dr) ? lr : (int) Math.round(color1Likeness * lr
+ + (1.0 - color1Likeness) * dr);
+ int g = (lg == dg) ? lg : (int) Math.round(color1Likeness * lg
+ + (1.0 - color1Likeness) * dg);
+ int b = (lb == db) ? lb : (int) Math.round(color1Likeness * lb
+ + (1.0 - color1Likeness) * db);
+ int a = (la == da) ? la : (int) Math.round(color1Likeness * la
+ + (1.0 - color1Likeness) * da);
+
+ return (a << 24) | (r << 16) | (g << 8) | b;
+ }
+
+ Color getInterpolatedColor(Color color1, Color color2,
+ float color1Likeness) {
+ if (color1.equals(color2))
+ return color1;
+ if (color1Likeness == 1.0)
+ return color1;
+ if (color1Likeness == 0.0)
+ return color2;
+ return new Color(
+ getInterpolatedRGB(color1, color2, color1Likeness), true);
+ }
+ }
+
+ static class PointInterpolator implements PropertyInterpolator<Point> {
+ @Override
+ public Point interpolate(Point from, Point to, float timelinePosition) {
+ int x = from.x + (int) (timelinePosition * (to.x - from.x));
+ int y = from.y + (int) (timelinePosition * (to.y - from.y));
+ return new Point(x, y);
+ }
+
+ @Override
+ public Class getBasePropertyClass() {
+ return Point.class;
+ }
+ }
+
+ static class RectangleInterpolator implements
+ PropertyInterpolator<Rectangle> {
+ @Override
+ public Rectangle interpolate(Rectangle from, Rectangle to,
+ float timelinePosition) {
+ int x = from.x + (int) (timelinePosition * (to.x - from.x));
+ int y = from.y + (int) (timelinePosition * (to.y - from.y));
+ int w = from.width
+ + (int) (timelinePosition * (to.width - from.width));
+ int h = from.height
+ + (int) (timelinePosition * (to.height - from.height));
+ return new Rectangle(x, y, w, h);
+ }
+
+ @Override
+ public Class getBasePropertyClass() {
+ return Rectangle.class;
+ }
+ }
+
+ static class DimensionInterpolator implements
+ PropertyInterpolator<Dimension> {
+ @Override
+ public Dimension interpolate(Dimension from, Dimension to,
+ float timelinePosition) {
+ int w = from.width
+ + (int) (timelinePosition * (to.width - from.width));
+ int h = from.height
+ + (int) (timelinePosition * (to.height - from.height));
+ return new Dimension(w, h);
+ }
+
+ @Override
+ public Class getBasePropertyClass() {
+ return Dimension.class;
+ }
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/swing/SwingRepaintCallback.java b/trident/src/main/java/org/pushingpixels/trident/swing/SwingRepaintCallback.java
new file mode 100644
index 0000000..eed30ab
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/swing/SwingRepaintCallback.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.swing;
+
+import java.awt.Component;
+import java.awt.Rectangle;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.TimelineCallbackAdapter;
+
+public class SwingRepaintCallback extends TimelineCallbackAdapter {
+ private Component comp;
+
+ private Rectangle rect;
+
+ private AtomicBoolean repaintGuard;
+
+ public SwingRepaintCallback(Component comp) {
+ this(comp, null);
+ }
+
+ public SwingRepaintCallback(Component comp, Rectangle rect) {
+ if (comp == null) {
+ throw new NullPointerException("Component must be non-null");
+ }
+ this.comp = comp;
+ if (rect != null) {
+ this.rect = new Rectangle(rect);
+ }
+ }
+
+ public synchronized void setAutoRepaintMode(boolean autoRepaintMode) {
+ if (autoRepaintMode) {
+ this.repaintGuard = null;
+ } else {
+ this.repaintGuard = new AtomicBoolean(false);
+ }
+ }
+
+ public synchronized void forceRepaintOnNextPulse() {
+ if (this.repaintGuard == null) {
+ throw new IllegalArgumentException(
+ "This method cannot be called on auto-repaint callback");
+ }
+ this.repaintGuard.set(true);
+ }
+
+ public synchronized void setRepaintRectangle(Rectangle rect) {
+ if (rect == null) {
+ this.rect = null;
+ } else {
+ this.rect = new Rectangle(rect);
+ }
+ }
+
+ @Override
+ public synchronized void onTimelinePulse(float durationFraction,
+ float timelinePosition) {
+ repaintAsNecessary();
+ }
+
+ @Override
+ public synchronized void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ repaintAsNecessary();
+ }
+
+ private void repaintAsNecessary() {
+ if (this.repaintGuard != null) {
+ if (!this.repaintGuard.compareAndSet(true, false)) {
+ // no need to repaint
+ return;
+ }
+ }
+
+ if (this.rect == null)
+ this.comp.repaint();
+ else
+ this.comp.repaint(this.rect.x, this.rect.y, this.rect.width,
+ this.rect.height);
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/swing/SwingRepaintTimeline.java b/trident/src/main/java/org/pushingpixels/trident/swing/SwingRepaintTimeline.java
new file mode 100644
index 0000000..1c7703f
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/swing/SwingRepaintTimeline.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.swing;
+
+import java.awt.Component;
+import java.awt.Rectangle;
+
+import org.pushingpixels.trident.Timeline;
+
+public class SwingRepaintTimeline extends Timeline {
+ private SwingRepaintCallback repaintCallback;
+
+ public SwingRepaintTimeline(Component mainTimelineComp) {
+ this(mainTimelineComp, null);
+ }
+
+ public SwingRepaintTimeline(Component mainTimelineComp, Rectangle toRepaint) {
+ super(mainTimelineComp);
+ this.repaintCallback = new SwingRepaintCallback(mainTimelineComp,
+ toRepaint);
+ this.addCallback(this.repaintCallback);
+ }
+
+ public void forceRepaintOnNextPulse() {
+ this.repaintCallback.forceRepaintOnNextPulse();
+ }
+
+ public void setAutoRepaintMode(boolean autoRepaintMode) {
+ this.repaintCallback.setAutoRepaintMode(autoRepaintMode);
+ }
+
+ public void setRepaintRectangle(Rectangle rect) {
+ this.repaintCallback.setRepaintRectangle(rect);
+ }
+
+ @Override
+ public void play() {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+
+ @Override
+ public void playReverse() {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+
+ @Override
+ public void replay() {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+
+ @Override
+ public void replayReverse() {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+
+ @Override
+ public void playLoop(int loopCount, RepeatBehavior repeatBehavior) {
+ if (loopCount >= 0) {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+ super.playLoop(loopCount, repeatBehavior);
+ }
+}
\ No newline at end of file
diff --git a/trident/src/main/java/org/pushingpixels/trident/swing/SwingToolkitHandler.java b/trident/src/main/java/org/pushingpixels/trident/swing/SwingToolkitHandler.java
new file mode 100644
index 0000000..c90e328
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/swing/SwingToolkitHandler.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.swing;
+
+import java.awt.Component;
+
+import javax.swing.SwingUtilities;
+
+import org.pushingpixels.trident.UIToolkitHandler;
+
+public class SwingToolkitHandler implements UIToolkitHandler {
+ @Override
+ public boolean isHandlerFor(Object mainTimelineObject) {
+ return (mainTimelineObject instanceof Component);
+ }
+
+ @Override
+ public boolean isInReadyState(Object mainTimelineObject) {
+ return ((Component) mainTimelineObject).isDisplayable();
+ }
+
+ @Override
+ public void runOnUIThread(Object mainTimelineObject, Runnable runnable) {
+ if (SwingUtilities.isEventDispatchThread())
+ runnable.run();
+ else
+ SwingUtilities.invokeLater(runnable);
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/swing/TimelineSwingWorker.java b/trident/src/main/java/org/pushingpixels/trident/swing/TimelineSwingWorker.java
new file mode 100644
index 0000000..b2c48c7
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/swing/TimelineSwingWorker.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.swing;
+
+import javax.swing.SwingWorker;
+
+import org.pushingpixels.trident.TimelineScenario;
+
+public abstract class TimelineSwingWorker<T, V> extends SwingWorker<T, V>
+ implements TimelineScenario.TimelineScenarioActor {
+ @Override
+ public void play() {
+ this.execute();
+ }
+
+ @Override
+ public boolean supportsReplay() {
+ return false;
+ }
+
+ @Override
+ public void resetDoneFlag() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/swt/SWTPropertyInterpolators.java b/trident/src/main/java/org/pushingpixels/trident/swt/SWTPropertyInterpolators.java
new file mode 100644
index 0000000..f20e85e
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/swt/SWTPropertyInterpolators.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.swt;
+
+import java.util.*;
+
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.Display;
+import org.pushingpixels.trident.interpolator.PropertyInterpolator;
+import org.pushingpixels.trident.interpolator.PropertyInterpolatorSource;
+
+/**
+ * Built-in interpolators for SWT classes.
+ *
+ * @author Kirill Grouchnikov
+ */
+public class SWTPropertyInterpolators implements PropertyInterpolatorSource {
+ private Set<PropertyInterpolator> interpolators;
+
+ public SWTPropertyInterpolators() {
+ this.interpolators = new HashSet<PropertyInterpolator>();
+ this.interpolators.add(new ColorInterpolator());
+ this.interpolators.add(new PointInterpolator());
+ this.interpolators.add(new RectangleInterpolator());
+ }
+
+ @Override
+ public Set<PropertyInterpolator> getPropertyInterpolators() {
+ return Collections.unmodifiableSet(this.interpolators);
+ }
+
+ static class ColorInterpolator implements PropertyInterpolator<Color> {
+ @Override
+ public Class getBasePropertyClass() {
+ return Color.class;
+ }
+
+ @Override
+ public Color interpolate(Color from, Color to, float timelinePosition) {
+ return getInterpolatedColor(from, to, 1.0f - timelinePosition);
+ }
+
+ RGB getInterpolatedRGB(Color color1, Color color2, float color1Likeness) {
+ if ((color1Likeness < 0.0) || (color1Likeness > 1.0))
+ throw new IllegalArgumentException(
+ "Color likeness should be in 0.0-1.0 range [is "
+ + color1Likeness + "]");
+ int lr = color1.getRed();
+ int lg = color1.getGreen();
+ int lb = color1.getBlue();
+ int dr = color2.getRed();
+ int dg = color2.getGreen();
+ int db = color2.getBlue();
+
+ // using some interpolation values (such as 0.29 from issue 401)
+ // results in an incorrect final value without Math.round.
+ int r = (lr == dr) ? lr : (int) Math.round(color1Likeness * lr
+ + (1.0 - color1Likeness) * dr);
+ int g = (lg == dg) ? lg : (int) Math.round(color1Likeness * lg
+ + (1.0 - color1Likeness) * dg);
+ int b = (lb == db) ? lb : (int) Math.round(color1Likeness * lb
+ + (1.0 - color1Likeness) * db);
+
+ return new RGB(r, g, b);
+ }
+
+ Color getInterpolatedColor(Color color1, Color color2,
+ float color1Likeness) {
+ if (color1.equals(color2))
+ return color1;
+ if (color1Likeness == 1.0)
+ return color1;
+ if (color1Likeness == 0.0)
+ return color2;
+ return new Color(Display.getDefault(), getInterpolatedRGB(color1,
+ color2, color1Likeness));
+ }
+ }
+
+ static class PointInterpolator implements PropertyInterpolator<Point> {
+ @Override
+ public Point interpolate(Point from, Point to, float timelinePosition) {
+ int x = from.x + (int) (timelinePosition * (to.x - from.x));
+ int y = from.y + (int) (timelinePosition * (to.y - from.y));
+ return new Point(x, y);
+ }
+
+ @Override
+ public Class getBasePropertyClass() {
+ return Point.class;
+ }
+ }
+
+ static class RectangleInterpolator implements
+ PropertyInterpolator<Rectangle> {
+ @Override
+ public Rectangle interpolate(Rectangle from, Rectangle to,
+ float timelinePosition) {
+ int x = from.x + (int) (timelinePosition * (to.x - from.x));
+ int y = from.y + (int) (timelinePosition * (to.y - from.y));
+ int w = from.width
+ + (int) (timelinePosition * (to.width - from.width));
+ int h = from.height
+ + (int) (timelinePosition * (to.height - from.height));
+ return new Rectangle(x, y, w, h);
+ }
+
+ @Override
+ public Class getBasePropertyClass() {
+ return Rectangle.class;
+ }
+ }
+
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/swt/SWTRepaintCallback.java b/trident/src/main/java/org/pushingpixels/trident/swt/SWTRepaintCallback.java
new file mode 100644
index 0000000..d2862fc
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/swt/SWTRepaintCallback.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.swt;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Control;
+import org.pushingpixels.trident.Timeline.TimelineState;
+import org.pushingpixels.trident.callback.RunOnUIThread;
+import org.pushingpixels.trident.callback.TimelineCallback;
+
+ at RunOnUIThread
+public class SWTRepaintCallback implements TimelineCallback {
+ private Control control;
+
+ private Rectangle rect;
+
+ private AtomicBoolean repaintGuard;
+
+ public SWTRepaintCallback(Control control) {
+ this(control, null);
+ }
+
+ public SWTRepaintCallback(Control control, Rectangle rect) {
+ if (control == null) {
+ throw new NullPointerException("Control must be non-null");
+ }
+ this.control = control;
+ if (rect != null) {
+ this.rect = new Rectangle(rect.x, rect.y, rect.width, rect.height);
+ }
+ }
+
+ public synchronized void setAutoRepaintMode(boolean autoRepaintMode) {
+ if (autoRepaintMode) {
+ this.repaintGuard = null;
+ } else {
+ this.repaintGuard = new AtomicBoolean(false);
+ }
+ }
+
+ public synchronized void forceRepaintOnNextPulse() {
+ if (this.repaintGuard == null) {
+ throw new IllegalArgumentException(
+ "This method cannot be called on auto-repaint callback");
+ }
+ this.repaintGuard.set(true);
+ }
+
+ public synchronized void setRepaintRectangle(Rectangle rect) {
+ if (rect == null) {
+ this.rect = null;
+ } else {
+ this.rect = new Rectangle(rect.x, rect.y, rect.width, rect.height);
+ }
+ }
+
+ @Override
+ public void onTimelinePulse(float durationFraction, float timelinePosition) {
+ redrawAsNecessary();
+ }
+
+ @Override
+ public void onTimelineStateChanged(TimelineState oldState,
+ TimelineState newState, float durationFraction,
+ float timelinePosition) {
+ redrawAsNecessary();
+ }
+
+ private void redrawAsNecessary() {
+ if (this.control.isDisposed())
+ return;
+
+ if (this.repaintGuard != null) {
+ if (!this.repaintGuard.compareAndSet(true, false)) {
+ // no need to repaint
+ return;
+ }
+ }
+
+ if (this.rect == null)
+ this.control.redraw();
+ else
+ this.control.redraw(this.rect.x, this.rect.y, this.rect.width,
+ this.rect.height, true);
+ }
+}
diff --git a/trident/src/main/java/org/pushingpixels/trident/swt/SWTRepaintTimeline.java b/trident/src/main/java/org/pushingpixels/trident/swt/SWTRepaintTimeline.java
new file mode 100644
index 0000000..5901028
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/swt/SWTRepaintTimeline.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.swt;
+
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Control;
+import org.pushingpixels.trident.Timeline;
+
+public class SWTRepaintTimeline extends Timeline {
+ private SWTRepaintCallback repaintCallback;
+
+ public SWTRepaintTimeline(Control mainTimelineComp) {
+ this(mainTimelineComp, null);
+ }
+
+ public SWTRepaintTimeline(Control mainTimelineComp, Rectangle toRepaint) {
+ super(mainTimelineComp);
+ this.repaintCallback = new SWTRepaintCallback(mainTimelineComp,
+ toRepaint);
+ this.addCallback(this.repaintCallback);
+ }
+
+ public void forceRepaintOnNextPulse() {
+ this.repaintCallback.forceRepaintOnNextPulse();
+ }
+
+ public void setAutoRepaintMode(boolean autoRepaintMode) {
+ this.repaintCallback.setAutoRepaintMode(autoRepaintMode);
+ }
+
+ public void setRepaintRectangle(Rectangle rect) {
+ this.repaintCallback.setRepaintRectangle(rect);
+ }
+
+ @Override
+ public void play() {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+
+ @Override
+ public void playReverse() {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+
+ @Override
+ public void replay() {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+
+ @Override
+ public void replayReverse() {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+
+ @Override
+ public void playLoop(int loopCount, RepeatBehavior repeatBehavior) {
+ if (loopCount >= 0) {
+ throw new UnsupportedOperationException(
+ "Only infinite looping is supported");
+ }
+ super.playLoop(loopCount, repeatBehavior);
+ }
+}
\ No newline at end of file
diff --git a/trident/src/main/java/org/pushingpixels/trident/swt/SWTToolkitHandler.java b/trident/src/main/java/org/pushingpixels/trident/swt/SWTToolkitHandler.java
new file mode 100644
index 0000000..22097df
--- /dev/null
+++ b/trident/src/main/java/org/pushingpixels/trident/swt/SWTToolkitHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005-2010 Trident Kirill Grouchnikov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * o Neither the name of Trident Kirill Grouchnikov nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.pushingpixels.trident.swt;
+
+import org.eclipse.swt.widgets.Widget;
+import org.pushingpixels.trident.UIToolkitHandler;
+
+public class SWTToolkitHandler implements UIToolkitHandler {
+ @Override
+ public boolean isHandlerFor(Object mainTimelineObject) {
+ return (mainTimelineObject instanceof Widget);
+ }
+
+ @Override
+ public boolean isInReadyState(Object mainTimelineObject) {
+ return !((Widget) mainTimelineObject).isDisposed();
+ }
+
+ @Override
+ public void runOnUIThread(Object mainTimelineObject, Runnable runnable) {
+ ((Widget) mainTimelineObject).getDisplay().asyncExec(runnable);
+ }
+}
diff --git a/trident/src/main/resources/META-INF/trident-plugin-android.properties b/trident/src/main/resources/META-INF/trident-plugin-android.properties
new file mode 100644
index 0000000..6a2620f
--- /dev/null
+++ b/trident/src/main/resources/META-INF/trident-plugin-android.properties
@@ -0,0 +1,4 @@
+UIToolkitHandler=org.pushingpixels.trident.android.AndroidToolkitHandler
+PropertyInterpolatorSource=org.pushingpixels.trident.android.AndroidPropertyInterpolators
+
+PropertyInterpolatorSource=org.pushingpixels.trident.interpolator.CorePropertyInterpolators
\ No newline at end of file
diff --git a/trident/src/main/resources/META-INF/trident-plugin-base.properties b/trident/src/main/resources/META-INF/trident-plugin-base.properties
new file mode 100644
index 0000000..d02238f
--- /dev/null
+++ b/trident/src/main/resources/META-INF/trident-plugin-base.properties
@@ -0,0 +1 @@
+PropertyInterpolatorSource=org.pushingpixels.trident.interpolator.CorePropertyInterpolators
\ No newline at end of file
diff --git a/trident/src/main/resources/META-INF/trident-plugin-swing.properties b/trident/src/main/resources/META-INF/trident-plugin-swing.properties
new file mode 100644
index 0000000..cd15a7a
--- /dev/null
+++ b/trident/src/main/resources/META-INF/trident-plugin-swing.properties
@@ -0,0 +1,4 @@
+UIToolkitHandler=org.pushingpixels.trident.swing.SwingToolkitHandler
+PropertyInterpolatorSource=org.pushingpixels.trident.swing.AWTPropertyInterpolators
+
+PropertyInterpolatorSource=org.pushingpixels.trident.interpolator.CorePropertyInterpolators
\ No newline at end of file
diff --git a/trident/src/main/resources/META-INF/trident-plugin-swt.properties b/trident/src/main/resources/META-INF/trident-plugin-swt.properties
new file mode 100644
index 0000000..b0a35ad
--- /dev/null
+++ b/trident/src/main/resources/META-INF/trident-plugin-swt.properties
@@ -0,0 +1,4 @@
+UIToolkitHandler=org.pushingpixels.trident.swt.SWTToolkitHandler
+PropertyInterpolatorSource=org.pushingpixels.trident.swt.SWTPropertyInterpolators
+
+PropertyInterpolatorSource=org.pushingpixels.trident.interpolator.CorePropertyInterpolators
\ No newline at end of file
diff --git a/trident/src/main/resources/META-INF/trident-plugin.properties b/trident/src/main/resources/META-INF/trident-plugin.properties
new file mode 100644
index 0000000..25a2ffa
--- /dev/null
+++ b/trident/src/main/resources/META-INF/trident-plugin.properties
@@ -0,0 +1,10 @@
+UIToolkitHandler=org.pushingpixels.trident.swing.SwingToolkitHandler
+PropertyInterpolatorSource=org.pushingpixels.trident.swing.AWTPropertyInterpolators
+
+UIToolkitHandler=org.pushingpixels.trident.swt.SWTToolkitHandler
+PropertyInterpolatorSource=org.pushingpixels.trident.swt.SWTPropertyInterpolators
+
+UIToolkitHandler=org.pushingpixels.trident.android.AndroidToolkitHandler
+PropertyInterpolatorSource=org.pushingpixels.trident.android.AndroidPropertyInterpolators
+
+PropertyInterpolatorSource=org.pushingpixels.trident.interpolator.CorePropertyInterpolators
\ No newline at end of file
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/insubstantial.git
More information about the pkg-java-commits
mailing list